From bad9bc6013ca5a7bbd4d9820274042a8b3f71e02 Mon Sep 17 00:00:00 2001 From: Sandesh Date: Sat, 21 Mar 2026 00:49:47 +0530 Subject: [PATCH 1/2] Minor fixes --- androidApp/build.gradle.kts | 47 +++ androidApp/src/main/AndroidManifest.xml | 21 + .../jsoncmpsample/JsonCmpSampleActivity.kt | 17 + androidApp/src/main/res/values/strings.xml | 4 + androidApp/src/main/res/values/themes.xml | 8 + .../dev/skymansandy/jsoncmpsample/App.kt | 116 ------ .../jsoncmpsample/data/SampleData.kt | 30 ++ .../jsoncmpsample/model/ThemeOption.kt | 11 + .../dev/skymansandy/jsoncmpsample/ui/App.kt | 116 ++++++ .../jsoncmpsample/MainViewController.kt | 1 + .../dev/skymansandy/jsoncmpsample/Main.kt | 1 + gradle.properties | 2 +- iosApp/Configuration/Config.xcconfig | 5 + iosApp/iosApp.xcodeproj/project.pbxproj | 372 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcschemes/xcschememanagement.plist | 5 + .../xcschemes/iosApp.xcscheme | 32 ++ .../xcschemes/xcschememanagement.plist | 14 + iosApp/iosApp/ContentView.swift | 18 + iosApp/iosApp/Info.plist | 13 + iosApp/iosApp/iOSApp.swift | 10 + .../jsoncmp/component/editor/CodeEditor.kt | 128 ++++-- .../jsoncmp/component/editor/EditorToolbar.kt | 55 ++- .../jsoncmp/component/viewer/JsonViewer.kt | 6 +- settings.gradle.kts | 1 + 25 files changed, 868 insertions(+), 172 deletions(-) create mode 100644 androidApp/build.gradle.kts create mode 100644 androidApp/src/main/AndroidManifest.xml create mode 100644 androidApp/src/main/kotlin/dev/skymansandy/jsoncmpsample/JsonCmpSampleActivity.kt create mode 100644 androidApp/src/main/res/values/strings.xml create mode 100644 androidApp/src/main/res/values/themes.xml delete mode 100644 composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/App.kt create mode 100644 composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/data/SampleData.kt create mode 100644 composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/model/ThemeOption.kt create mode 100644 composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/ui/App.kt create mode 100644 iosApp/Configuration/Config.xcconfig create mode 100644 iosApp/iosApp.xcodeproj/project.pbxproj create mode 100644 iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/gosandy.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 iosApp/iosApp.xcodeproj/xcuserdata/gosandy.xcuserdatad/xcschemes/iosApp.xcscheme create mode 100644 iosApp/iosApp.xcodeproj/xcuserdata/gosandy.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 iosApp/iosApp/ContentView.swift create mode 100644 iosApp/iosApp/Info.plist create mode 100644 iosApp/iosApp/iOSApp.swift diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts new file mode 100644 index 0000000..3b1741a --- /dev/null +++ b/androidApp/build.gradle.kts @@ -0,0 +1,47 @@ +plugins { + alias(libs.plugins.androidApplication) + alias(libs.plugins.composeCompiler) +} + +android { + namespace = "dev.skymansandy.jsoncmpsample" + compileSdk { + version = release(36) + } + + defaultConfig { + applicationId = "dev.skymansandy.jsoncmpsample" + minSdk = 24 + targetSdk = 36 + versionCode = 1 + versionName = "1.0" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + buildFeatures { + compose = true + } +} + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.compose.ui) + implementation(libs.compose.material3) + implementation(libs.compose.uiToolingPreview) + debugImplementation(libs.compose.uiTooling) + implementation(project(":composeApp")) +} diff --git a/androidApp/src/main/AndroidManifest.xml b/androidApp/src/main/AndroidManifest.xml new file mode 100644 index 0000000..386388d --- /dev/null +++ b/androidApp/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/androidApp/src/main/kotlin/dev/skymansandy/jsoncmpsample/JsonCmpSampleActivity.kt b/androidApp/src/main/kotlin/dev/skymansandy/jsoncmpsample/JsonCmpSampleActivity.kt new file mode 100644 index 0000000..23ddbee --- /dev/null +++ b/androidApp/src/main/kotlin/dev/skymansandy/jsoncmpsample/JsonCmpSampleActivity.kt @@ -0,0 +1,17 @@ +package dev.skymansandy.jsoncmpsample + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import dev.skymansandy.jsoncmpsample.ui.App + +internal class JsonCmpSampleActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { + App() + } + } +} diff --git a/androidApp/src/main/res/values/strings.xml b/androidApp/src/main/res/values/strings.xml new file mode 100644 index 0000000..2bf4624 --- /dev/null +++ b/androidApp/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + JsonCMP Sample + diff --git a/androidApp/src/main/res/values/themes.xml b/androidApp/src/main/res/values/themes.xml new file mode 100644 index 0000000..638cd9a --- /dev/null +++ b/androidApp/src/main/res/values/themes.xml @@ -0,0 +1,8 @@ + + + + diff --git a/composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/App.kt b/composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/App.kt deleted file mode 100644 index 5cafc61..0000000 --- a/composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/App.kt +++ /dev/null @@ -1,116 +0,0 @@ -package dev.skymansandy.jsoncmpsample - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.FilterChip -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Text -import androidx.compose.material3.darkColorScheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import dev.skymansandy.jsoncmp.JsonCMP -import dev.skymansandy.jsoncmp.config.rememberJsonEditorState -import dev.skymansandy.jsoncmp.helper.constants.colors.JsonCmpColors - -private val sampleJson = """ -{ - "name": "JsonCMP", - "version": "1.0.0", - "description": "Kotlin Multiplatform Compose JSON viewer and editor", - "platforms": ["Android", "iOS", "JVM Desktop"], - "features": { - "viewer": true, - "editor": true, - "search": true, - "folding": true, - "sorting": true - }, - "themes": [ - {"name": "Dark", "type": "dark"}, - {"name": "Light", "type": "light"}, - {"name": "Monokai", "type": "dark"}, - {"name": "Dracula", "type": "dark"}, - {"name": "Solarized Dark", "type": "dark"} - ], - "author": { - "name": "skymansandy", - "github": "https://github.com/skymansandy" - }, - "license": "Apache-2.0", - "stars": null -} -""".trimIndent() - -private enum class ThemeOption(val label: String, val colors: JsonCmpColors) { - Dark("Dark", JsonCmpColors.Dark), - Light("Light", JsonCmpColors.Light), - Monokai("Monokai", JsonCmpColors.Monokai), - Dracula("Dracula", JsonCmpColors.Dracula), - SolarizedDark("Solarized", JsonCmpColors.SolarizedDark), -} - -@Composable -fun App() { - - MaterialTheme( - colorScheme = darkColorScheme(), - ) { - var searchQuery by remember { mutableStateOf("") } - var selectedTheme by remember { mutableStateOf(ThemeOption.Dark) } - val state = rememberJsonEditorState( - initialJson = sampleJson, - isEditing = true, - ) - - Column( - modifier = Modifier - .fillMaxSize() - .background(selectedTheme.colors.background), - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp, vertical = 4.dp), - horizontalArrangement = Arrangement.spacedBy(4.dp), - ) { - ThemeOption.entries.forEach { theme -> - FilterChip( - selected = selectedTheme == theme, - onClick = { selectedTheme = theme }, - label = { Text(theme.label) }, - ) - } - } - - OutlinedTextField( - value = searchQuery, - onValueChange = { searchQuery = it }, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp, vertical = 4.dp), - label = { Text("Search") }, - singleLine = true, - ) - - JsonCMP( - modifier = Modifier - .fillMaxSize() - .weight(1f), - state = state, - searchQuery = searchQuery, - colors = selectedTheme.colors, - ) - } - } -} diff --git a/composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/data/SampleData.kt b/composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/data/SampleData.kt new file mode 100644 index 0000000..8f8dede --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/data/SampleData.kt @@ -0,0 +1,30 @@ +package dev.skymansandy.jsoncmpsample.data + +val sampleJson = """ +{ + "name": "JsonCMP", + "version": "1.0.0", + "description": "Kotlin Multiplatform Compose JSON viewer and editor", + "platforms": ["Android", "iOS", "JVM Desktop"], + "features": { + "viewer": true, + "editor": true, + "search": true, + "folding": true, + "sorting": true + }, + "themes": [ + {"name": "Dark", "type": "dark"}, + {"name": "Light", "type": "light"}, + {"name": "Monokai", "type": "dark"}, + {"name": "Dracula", "type": "dark"}, + {"name": "Solarized Dark", "type": "dark"} + ], + "author": { + "name": "skymansandy", + "github": "https://github.com/skymansandy" + }, + "license": "Apache-2.0", + "stars": null +} +""".trimIndent() diff --git a/composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/model/ThemeOption.kt b/composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/model/ThemeOption.kt new file mode 100644 index 0000000..8be8d75 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/model/ThemeOption.kt @@ -0,0 +1,11 @@ +package dev.skymansandy.jsoncmpsample.model + +import dev.skymansandy.jsoncmp.helper.constants.colors.JsonCmpColors + +enum class ThemeOption(val label: String, val colors: JsonCmpColors) { + Dark("Dark", JsonCmpColors.Dark), + Light("Light", JsonCmpColors.Light), + Monokai("Monokai", JsonCmpColors.Monokai), + Dracula("Dracula", JsonCmpColors.Dracula), + SolarizedDark("Solarized", JsonCmpColors.SolarizedDark), +} diff --git a/composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/ui/App.kt b/composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/ui/App.kt new file mode 100644 index 0000000..eec08b3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/ui/App.kt @@ -0,0 +1,116 @@ +package dev.skymansandy.jsoncmpsample.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilterChip +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.darkColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import dev.skymansandy.jsoncmp.JsonCMP +import dev.skymansandy.jsoncmp.config.rememberJsonEditorState +import dev.skymansandy.jsoncmpsample.data.sampleJson +import dev.skymansandy.jsoncmpsample.model.ThemeOption + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun App() { + MaterialTheme( + colorScheme = darkColorScheme(), + ) { + Scaffold( + topBar = { + TopAppBar( + title = { Text("JsonCMP Sample") }, + ) + }, + ) { + var searchQuery by remember { mutableStateOf("") } + var selectedTheme by remember { mutableStateOf(ThemeOption.Dark) } + val state = rememberJsonEditorState( + initialJson = sampleJson, + isEditing = true, + ) + + Column( + modifier = Modifier + .fillMaxSize() + .background(selectedTheme.colors.background) + .padding(it), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .horizontalScroll(rememberScrollState()) + .padding(horizontal = 8.dp, vertical = 4.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp), + ) { + ThemeOption.entries.forEach { theme -> + FilterChip( + selected = selectedTheme == theme, + onClick = { selectedTheme = theme }, + label = { Text(theme.label) }, + ) + } + } + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + OutlinedTextField( + value = searchQuery, + onValueChange = { searchQuery = it }, + modifier = Modifier.weight(1f), + label = { Text("Search") }, + singleLine = true, + ) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Switch( + checked = state.isEditing, + onCheckedChange = { state.isEditing = it }, + ) + Text( + text = if (state.isEditing) "Edit" else "View", + style = typography.labelSmall, + ) + } + } + + JsonCMP( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + state = state, + searchQuery = searchQuery, + colors = selectedTheme.colors, + ) + } + } + } +} diff --git a/composeApp/src/iosMain/kotlin/dev/skymansandy/jsoncmpsample/MainViewController.kt b/composeApp/src/iosMain/kotlin/dev/skymansandy/jsoncmpsample/MainViewController.kt index 3a89fa4..8387820 100644 --- a/composeApp/src/iosMain/kotlin/dev/skymansandy/jsoncmpsample/MainViewController.kt +++ b/composeApp/src/iosMain/kotlin/dev/skymansandy/jsoncmpsample/MainViewController.kt @@ -1,6 +1,7 @@ package dev.skymansandy.jsoncmpsample import androidx.compose.ui.window.ComposeUIViewController +import dev.skymansandy.jsoncmpsample.ui.App @Suppress("FunctionNaming") fun MainViewController() = ComposeUIViewController { App() } diff --git a/composeApp/src/jvmMain/kotlin/dev/skymansandy/jsoncmpsample/Main.kt b/composeApp/src/jvmMain/kotlin/dev/skymansandy/jsoncmpsample/Main.kt index 1adcf11..896da8a 100644 --- a/composeApp/src/jvmMain/kotlin/dev/skymansandy/jsoncmpsample/Main.kt +++ b/composeApp/src/jvmMain/kotlin/dev/skymansandy/jsoncmpsample/Main.kt @@ -2,6 +2,7 @@ package dev.skymansandy.jsoncmpsample import androidx.compose.ui.window.Window import androidx.compose.ui.window.application +import dev.skymansandy.jsoncmpsample.ui.App fun main() { application { diff --git a/gradle.properties b/gradle.properties index f94d15c..d6c7110 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,4 +17,4 @@ android.r8.strictFullModeForKeepRules=false #Publishing jsoncmp.group=dev.skymansandy -jsoncmp.version=1.0.0-alpha1 +jsoncmp.version=1.0.0-alpha2 diff --git a/iosApp/Configuration/Config.xcconfig b/iosApp/Configuration/Config.xcconfig new file mode 100644 index 0000000..f71ba00 --- /dev/null +++ b/iosApp/Configuration/Config.xcconfig @@ -0,0 +1,5 @@ +TEAM_ID= +PRODUCT_NAME=JsonCMP Sample +PRODUCT_BUNDLE_IDENTIFIER=dev.skymansandy.jsoncmp.JsonCMPSample$(TEAM_ID) +CURRENT_PROJECT_VERSION=1 +MARKETING_VERSION=1.0 diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj new file mode 100644 index 0000000..4f19daa --- /dev/null +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -0,0 +1,372 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 058557BB273AAA2400C3D2FC /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA2400C3D2FC /* iOSApp.swift */; }; + 058557BD273AAA2400C3D2FC /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 058557BC273AAA2400C3D2FC /* ContentView.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 058557BA273AAA2400C3D2FC /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; + 058557BC273AAA2400C3D2FC /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 7555FF7B242A565900829871 /* JsonCMP Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "JsonCMP Sample.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + B92378962B6B1156000C7307 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 058557D7273AAEEB004C7B11 /* Configuration */ = { + isa = PBXGroup; + children = ( + AB3632DC29227652001CCB65 /* Config.xcconfig */, + ); + path = Configuration; + sourceTree = ""; + }; + 7555FF72242A565900829871 = { + isa = PBXGroup; + children = ( + 7555FF7D242A565900829871 /* iosApp */, + 058557D7273AAEEB004C7B11 /* Configuration */, + 7555FF7C242A565900829871 /* Products */, + ); + sourceTree = ""; + }; + 7555FF7C242A565900829871 /* Products */ = { + isa = PBXGroup; + children = ( + 7555FF7B242A565900829871 /* JsonCMP Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 7555FF7D242A565900829871 /* iosApp */ = { + isa = PBXGroup; + children = ( + 058557BA273AAA2400C3D2FC /* iOSApp.swift */, + 058557BC273AAA2400C3D2FC /* ContentView.swift */, + 7555FF8C242A565B00829871 /* Info.plist */, + ); + path = iosApp; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 7555FF7A242A565900829871 /* iosApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */; + buildPhases = ( + F36B0B8F2B60B08F0080A992 /* Compile Kotlin Framework */, + 7555FF77242A565900829871 /* Sources */, + B92378962B6B1156000C7307 /* Frameworks */, + 7555FF79242A565900829871 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = iosApp; + productName = iosApp; + productReference = 7555FF7B242A565900829871 /* JsonCMP Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 7555FF73242A565900829871 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1540; + LastUpgradeCheck = 1540; + ORGANIZATIONNAME = ""; + }; + buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 7555FF72242A565900829871; + productRefGroup = 7555FF7C242A565900829871 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 7555FF7A242A565900829871 /* iosApp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 7555FF79242A565900829871 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + F36B0B8F2B60B08F0080A992 /* Compile Kotlin Framework */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Compile Kotlin Framework"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "cd \"$SRCROOT/..\"\n./gradlew :composeApp:embedAndSignAppleFrameworkForXcode\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 7555FF77242A565900829871 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 058557BB273AAA2400C3D2FC /* iOSApp.swift in Sources */, + 058557BD273AAA2400C3D2FC /* ContentView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 7555FFA3242A565B00829871 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 7555FFA4242A565B00829871 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 7555FFA6242A565B00829871 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = "${TEAM_ID}"; + ENABLE_PREVIEWS = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + ); + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = iosApp/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "${PRODUCT_NAME}"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-lsqlite3", + ); + PRODUCT_BUNDLE_IDENTIFIER = "${PRODUCT_BUNDLE_IDENTIFIER}"; + PRODUCT_NAME = "${PRODUCT_NAME}"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 7555FFA7242A565B00829871 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = "${TEAM_ID}"; + ENABLE_PREVIEWS = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + ); + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = iosApp/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "${PRODUCT_NAME}"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-lsqlite3", + ); + PRODUCT_BUNDLE_IDENTIFIER = "${PRODUCT_BUNDLE_IDENTIFIER}"; + PRODUCT_NAME = "${PRODUCT_NAME}"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7555FFA3242A565B00829871 /* Debug */, + 7555FFA4242A565B00829871 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7555FFA6242A565B00829871 /* Debug */, + 7555FFA7242A565B00829871 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 7555FF73242A565900829871 /* Project object */; +} diff --git a/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/gosandy.xcuserdatad/xcschemes/xcschememanagement.plist b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/gosandy.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..ee3458d --- /dev/null +++ b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/gosandy.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/iosApp/iosApp.xcodeproj/xcuserdata/gosandy.xcuserdatad/xcschemes/iosApp.xcscheme b/iosApp/iosApp.xcodeproj/xcuserdata/gosandy.xcuserdatad/xcschemes/iosApp.xcscheme new file mode 100644 index 0000000..94263e4 --- /dev/null +++ b/iosApp/iosApp.xcodeproj/xcuserdata/gosandy.xcuserdatad/xcschemes/iosApp.xcscheme @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + diff --git a/iosApp/iosApp.xcodeproj/xcuserdata/gosandy.xcuserdatad/xcschemes/xcschememanagement.plist b/iosApp/iosApp.xcodeproj/xcuserdata/gosandy.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..fa59f97 --- /dev/null +++ b/iosApp/iosApp.xcodeproj/xcuserdata/gosandy.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + iosApp.xcscheme + + orderHint + 0 + + + + diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift new file mode 100644 index 0000000..264c98d --- /dev/null +++ b/iosApp/iosApp/ContentView.swift @@ -0,0 +1,18 @@ +import UIKit +import SwiftUI +import JsonCMPSample + +struct ComposeView: UIViewControllerRepresentable { + func makeUIViewController(context: Context) -> UIViewController { + MainViewControllerKt.MainViewController() + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} +} + +struct ContentView: View { + var body: some View { + ComposeView() + .ignoresSafeArea() + } +} diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist new file mode 100644 index 0000000..ba6b757 --- /dev/null +++ b/iosApp/iosApp/Info.plist @@ -0,0 +1,13 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + + diff --git a/iosApp/iosApp/iOSApp.swift b/iosApp/iosApp/iOSApp.swift new file mode 100644 index 0000000..927e0b9 --- /dev/null +++ b/iosApp/iosApp/iOSApp.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct iOSApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/json-cmp/src/commonMain/kotlin/dev/skymansandy/jsoncmp/component/editor/CodeEditor.kt b/json-cmp/src/commonMain/kotlin/dev/skymansandy/jsoncmp/component/editor/CodeEditor.kt index 3d04685..41f6c2f 100644 --- a/json-cmp/src/commonMain/kotlin/dev/skymansandy/jsoncmp/component/editor/CodeEditor.kt +++ b/json-cmp/src/commonMain/kotlin/dev/skymansandy/jsoncmp/component/editor/CodeEditor.kt @@ -2,16 +2,14 @@ package dev.skymansandy.jsoncmp.component.editor import androidx.compose.foundation.background import androidx.compose.foundation.horizontalScroll -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -23,7 +21,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.layout.Layout import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -47,33 +47,90 @@ internal fun CodeEditor( } val horizontalScrollState = rememberScrollState() + val verticalScrollState = rememberScrollState() val lineCount = remember(textFieldValue.text) { textFieldValue.text.count { it == '\n' } + 1 } val numDigits = remember(lineCount) { lineCount.toString().length } + var textLayoutResult by remember { mutableStateOf(null) } + + val highlighted: AnnotatedString = remember(textFieldValue.text, searchQuery, colors) { + highlightJson( + text = textFieldValue.text, + searchQuery = searchQuery, + colors = colors, + ) + } Row( modifier = Modifier .fillMaxWidth() .defaultMinSize(minHeight = 200.dp) - .height(IntrinsicSize.Min) + .verticalScroll(verticalScrollState) .background(colors.background), ) { - // Line number gutter + // Line number gutter — positioned using actual text layout line offsets val borderColor = colors.gutterBorder - Column( + LineNumberGutter( + lineCount = lineCount, + numDigits = numDigits, + textLayoutResult = textLayoutResult, + colors = colors, + borderColor = borderColor, + ) + + BasicTextField( + value = textFieldValue.copy(annotatedString = highlighted), + onValueChange = { newValue -> + textFieldValue = newValue + lastSyncedRaw = newValue.text + state.updateRawJson(newValue.text) + }, + onTextLayout = { textLayoutResult = it }, + textStyle = monoStyle, + cursorBrush = SolidColor(colors.key), + modifier = Modifier + .weight(1f) + .horizontalScroll(horizontalScrollState) + .padding(start = 8.dp, end = 16.dp), + ) + } +} + +@Composable +private fun LineNumberGutter( + lineCount: Int, + numDigits: Int, + textLayoutResult: TextLayoutResult?, + colors: JsonCmpColors, + borderColor: androidx.compose.ui.graphics.Color, +) { + if (textLayoutResult == null || textLayoutResult.lineCount < lineCount) { + // Fallback: render without alignment until layout is available + Box( modifier = Modifier - .fillMaxHeight() .background(colors.gutterBackground) .drawBehind { val x = size.width - drawLine( - borderColor, - Offset(x, 0f), - Offset(x, size.height), - strokeWidth = 1.dp.toPx(), - ) + drawLine(borderColor, Offset(x, 0f), Offset(x, size.height), strokeWidth = 1.dp.toPx()) } .padding(start = 12.dp, end = 8.dp), ) { + androidx.compose.foundation.layout.Column { + for (i in 1..lineCount) { + Text( + text = i.toString().padStart(numDigits), + style = monoStyle, + color = colors.lineNumber, + softWrap = false, + ) + } + } + } + return + } + + // Use Layout to position each line number at the exact Y offset from the text layout + Layout( + content = { for (i in 1..lineCount) { Text( text = i.toString().padStart(numDigits), @@ -82,31 +139,30 @@ internal fun CodeEditor( softWrap = false, ) } + }, + modifier = Modifier + .background(colors.gutterBackground) + .drawBehind { + val x = size.width + drawLine(borderColor, Offset(x, 0f), Offset(x, size.height), strokeWidth = 1.dp.toPx()) + } + .padding(start = 12.dp, end = 8.dp), + ) { measurables, constraints -> + val textLayout = textLayoutResult + val placeables = measurables.map { it.measure(constraints.copy(minWidth = 0, minHeight = 0)) } + val width = placeables.maxOfOrNull { it.width } ?: 0 + val height = if (textLayout != null && textLayout.lineCount > 0) { + textLayout.getLineBottom(textLayout.lineCount - 1).toInt() + } else { + placeables.sumOf { it.height } } - // Text editor with syntax highlighting - val highlighted: AnnotatedString = remember(textFieldValue.text, searchQuery, colors) { - highlightJson( - text = textFieldValue.text, - searchQuery = searchQuery, - colors = colors, - ) + layout(width, height) { + placeables.forEachIndexed { index, placeable -> + val y = textLayout?.getLineTop(index)?.toInt() ?: (index * (placeables.firstOrNull()?.height ?: 0)) + placeable.placeRelative(0, y) + } } - - BasicTextField( - value = textFieldValue.copy(annotatedString = highlighted), - onValueChange = { newValue -> - textFieldValue = newValue - lastSyncedRaw = newValue.text - state.updateRawJson(newValue.text) - }, - textStyle = monoStyle, - cursorBrush = SolidColor(colors.key), - modifier = Modifier - .weight(1f) - .horizontalScroll(horizontalScrollState) - .padding(start = 8.dp, end = 16.dp), - ) } } diff --git a/json-cmp/src/commonMain/kotlin/dev/skymansandy/jsoncmp/component/editor/EditorToolbar.kt b/json-cmp/src/commonMain/kotlin/dev/skymansandy/jsoncmp/component/editor/EditorToolbar.kt index e39edd7..c4706f0 100644 --- a/json-cmp/src/commonMain/kotlin/dev/skymansandy/jsoncmp/component/editor/EditorToolbar.kt +++ b/json-cmp/src/commonMain/kotlin/dev/skymansandy/jsoncmp/component/editor/EditorToolbar.kt @@ -1,6 +1,7 @@ package dev.skymansandy.jsoncmp.component.editor import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -18,8 +19,12 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.PlainTooltip import androidx.compose.material3.Text +import androidx.compose.material3.TooltipBox +import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -50,33 +55,47 @@ internal fun EditorToolbar( .background(colors.gutterBackground) .padding(horizontal = 4.dp), verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.End, ) { // Format: Beautify / Compact toggle - IconButton( - onClick = { state.format(compact = !state.isCompact) }, - modifier = Modifier.size(36.dp), + val formatLabel = if (state.isCompact) "Beautify" else "Compact" + TooltipBox( + positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + tooltip = { PlainTooltip { Text(formatLabel) } }, + state = rememberTooltipState(), ) { - Icon( - imageVector = if (state.isCompact) Icons.AutoMirrored.Filled.FormatIndentIncrease else Icons.Default.FormatAlignJustify, - contentDescription = if (state.isCompact) "Beautify" else "Compact", - tint = colors.key, - modifier = Modifier.size(18.dp), - ) + IconButton( + onClick = { state.format(compact = !state.isCompact) }, + modifier = Modifier.size(36.dp), + ) { + Icon( + imageVector = if (state.isCompact) Icons.AutoMirrored.Filled.FormatIndentIncrease else Icons.Default.FormatAlignJustify, + contentDescription = formatLabel, + tint = colors.key, + modifier = Modifier.size(18.dp), + ) + } } ToolbarDivider(colors) // Sort - IconButton( - onClick = { showSortSheet = true }, - modifier = Modifier.size(36.dp), + TooltipBox( + positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + tooltip = { PlainTooltip { Text("Sort keys") } }, + state = rememberTooltipState(), ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.Sort, - contentDescription = "Sort keys", - tint = colors.key, - modifier = Modifier.size(18.dp), - ) + IconButton( + onClick = { showSortSheet = true }, + modifier = Modifier.size(36.dp), + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.Sort, + contentDescription = "Sort keys", + tint = colors.key, + modifier = Modifier.size(18.dp), + ) + } } } diff --git a/json-cmp/src/commonMain/kotlin/dev/skymansandy/jsoncmp/component/viewer/JsonViewer.kt b/json-cmp/src/commonMain/kotlin/dev/skymansandy/jsoncmp/component/viewer/JsonViewer.kt index a12af57..15d8b94 100644 --- a/json-cmp/src/commonMain/kotlin/dev/skymansandy/jsoncmp/component/viewer/JsonViewer.kt +++ b/json-cmp/src/commonMain/kotlin/dev/skymansandy/jsoncmp/component/viewer/JsonViewer.kt @@ -3,8 +3,10 @@ package dev.skymansandy.jsoncmp.component.viewer import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.selection.DisableSelection import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf @@ -52,7 +54,9 @@ internal fun JsonViewer( modifier = Modifier.fillMaxWidth(), ) { Column( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()), ) { for (line in visibleLines) { val isFolded = line.foldId != null && foldState[line.foldId] == true diff --git a/settings.gradle.kts b/settings.gradle.kts index 55b48cc..6a5d0d8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -33,3 +33,4 @@ plugins { include(":json-cmp") include(":composeApp") +include(":androidApp") From 8956dffa2cfa28ebd47d864d136ad250908adf4b Mon Sep 17 00:00:00 2001 From: Sandesh Date: Sat, 21 Mar 2026 00:53:01 +0530 Subject: [PATCH 2/2] Fix detekt --- .../commonMain/kotlin/dev/skymansandy/jsoncmpsample/ui/App.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/ui/App.kt b/composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/ui/App.kt index eec08b3..ad3a677 100644 --- a/composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/ui/App.kt +++ b/composeApp/src/commonMain/kotlin/dev/skymansandy/jsoncmpsample/ui/App.kt @@ -9,10 +9,10 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState -import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilterChip import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Switch