Explorar o código

iconic fonts - improvements for kmp

iconic fonts - improvements for kmp

iconic fonts - improvements for kmp

iconic fonts - improvements for kmp

iconic fonts - improvements for kmp

iconic fonts - improvements for kmp

iconic fonts - improvements for kmp

iconic fonts - improvements for kmp
Milan Jurkulak hai 1 ano
pai
achega
cb0bde9317
Modificáronse 58 ficheiros con 687 adicións e 394 borrados
  1. 2 1
      .gitignore
  2. 1 1
      .intellijPlatform/self-update.lock
  3. 1 1
      .run/mjdev-desktop [jvm run].run.xml
  4. 2 2
      .run/mjdev-desktop [plugin installPlugin].run.xml
  5. 1 1
      .run/mjdev-desktop [wasm run].run.xml
  6. 6 10
      build.gradle.kts
  7. 51 0
      buildSrc/build.gradle.kts
  8. 2 0
      buildSrc/settings.gradle.kts
  9. 2 2
      buildSrc/src/main/kotlin/org/mjdev/gradle/extensions/ProjectExt.kt
  10. 9 0
      buildSrc/src/main/kotlin/org/mjdev/gradle/extensions/SourceSets.kt
  11. 10 0
      buildSrc/src/main/kotlin/org/mjdev/gradle/extensions/TaskContainerExt.kt
  12. 20 4
      composeApp/build.gradle.kts
  13. 4 10
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/activity/MainActivity.kt
  14. 3 3
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/data/App.kt
  15. 138 138
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/data/Theme.kt
  16. 1 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/data/User.kt
  17. 7 2
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/extensions/JSonExt.kt
  18. 19 0
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/helpers/font/CodePointsFile.kt
  19. 24 0
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/helpers/font/FontDescriptor.kt
  20. 12 29
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/helpers/font/MaterialIconFont.android.kt
  21. 1 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/helpers/permission/PermissonManager.kt
  22. 36 0
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/helpers/streams/ResourceStream.android.kt
  23. 0 11
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/helpers/streams/ResourceStream.kt
  24. 9 13
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/apps/AppsManager.kt
  25. 5 2
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/keys/KeysManager.kt
  26. 2 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/blur/BlurPanel.kt
  27. 41 0
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/extensions/CustomExt.kt
  28. 24 0
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/helpers/font/MaterialIconFont.kt
  29. 9 0
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/helpers/streams/ResourceStream.kt
  30. 1 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/icons/text/Send.kt
  31. 1 1
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/data/Theme.kt
  32. 0 45
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/extensions/Custom.kt
  33. 0 16
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/fonts/CodePointsFile.kt
  34. 0 18
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/fonts/FontFile.kt
  35. 19 0
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/helpers/font/CodePointsFile.kt
  36. 18 0
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/helpers/font/FontDescriptor.kt
  37. 39 0
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/helpers/font/MaterialIconFont.kt
  38. 37 0
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/helpers/streams/ResourceStream.desktop.kt
  39. 0 13
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/helpers/streams/ResourceStream.kt
  40. 1 1
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/managers/ai/plugins/AiPluginGemini.kt
  41. 1 1
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/managers/apps/AppsManager.kt
  42. 1 1
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/managers/keys/KeysManager.kt
  43. 15 0
      composeApp/src/wasmJsMain/kotlin/org/mjdev/desktop/helpers/font/MaterialIconFont.wasmJs.kt
  44. 12 0
      composeApp/src/wasmJsMain/kotlin/org/mjdev/desktop/helpers/streams/ResourceStream.wasmJs.kt
  45. 15 7
      gradle.properties
  46. 13 7
      gradle/libs.versions.toml
  47. 2 0
      settings.gradle.kts
  48. 5 2
      studio-plugin/build.gradle.kts
  49. 0 17
      studio-plugin/src/main/kotlin/org/jetbrains/plugins/template/services/MyProjectService.kt
  50. 0 11
      studio-plugin/src/main/kotlin/org/jetbrains/plugins/template/startup/MyProjectActivity.kt
  51. 1 1
      studio-plugin/src/main/kotlin/org/mjdev/plugins/project/MyBundle.kt
  52. 22 0
      studio-plugin/src/main/kotlin/org/mjdev/plugins/project/services/MyProjectService.kt
  53. 18 0
      studio-plugin/src/main/kotlin/org/mjdev/plugins/project/startup/MyProjectActivity.kt
  54. 15 6
      studio-plugin/src/main/kotlin/org/mjdev/plugins/project/toolWindow/MyToolWindowFactory.kt
  55. 5 5
      studio-plugin/src/main/resources/META-INF/plugin.xml
  56. 2 5
      studio-plugin/src/test/kotlin/org/mjdev/plugins/mjdev/MyPluginTest.kt
  57. 1 1
      studio-plugin/src/test/testData/rename/foo.xml
  58. 1 1
      studio-plugin/src/test/testData/rename/foo_after.xml

+ 2 - 1
.gitignore

@@ -46,4 +46,5 @@ MediaDeviceSalts-journal
 ### sources
 sources/
 /sources
-/sources/
+/sources/
+/.intellijPlatform/

+ 1 - 1
.intellijPlatform/self-update.lock

@@ -1 +1 @@
-2025-04-21
+2025-04-23

+ 1 - 1
.run/mjdev-desktop [jvm].run.xml → .run/mjdev-desktop [jvm run].run.xml

@@ -1,5 +1,5 @@
 <component name="ProjectRunConfigurationManager">
-  <configuration default="false" name="mjdev-desktop [jvm]" type="GradleRunConfiguration" factoryName="Gradle">
+  <configuration default="false" name="mjdev-desktop [jvm run]" type="GradleRunConfiguration" factoryName="Gradle">
     <ExternalSystemSettings>
       <option name="executionName" />
       <option name="externalProjectPath" value="$PROJECT_DIR$" />

+ 2 - 2
.run/mjdev-desktop [plugin build].run.xml → .run/mjdev-desktop [plugin installPlugin].run.xml

@@ -1,5 +1,5 @@
 <component name="ProjectRunConfigurationManager">
-  <configuration default="false" name="mjdev-desktop [plugin build]" type="GradleRunConfiguration" factoryName="Gradle">
+  <configuration default="false" name="mjdev-desktop [plugin installPlugin]" type="GradleRunConfiguration" factoryName="Gradle">
     <ExternalSystemSettings>
       <option name="executionName" />
       <option name="externalProjectPath" value="/mnt/fe2c4809-fb9c-44f1-aa6e-48b0323031f1/backups/IdeaProjects/mjdev-desktop" />
@@ -10,7 +10,7 @@
       </option>
       <option name="taskNames">
         <list>
-          <option value=":studio-plugin:buildPlugin" />
+          <option value=":buildSrc:installPlugin" />
         </list>
       </option>
       <option name="vmOptions" />

+ 1 - 1
.run/mjdev-desktop [wasm].run.xml → .run/mjdev-desktop [wasm run].run.xml

@@ -1,5 +1,5 @@
 <component name="ProjectRunConfigurationManager">
-  <configuration default="false" name="mjdev-desktop [wasm]" type="GradleRunConfiguration" factoryName="Gradle">
+  <configuration default="false" name="mjdev-desktop [wasm run]" type="GradleRunConfiguration" factoryName="Gradle">
     <ExternalSystemSettings>
       <option name="executionName" />
       <option name="externalProjectPath" value="$PROJECT_DIR$" />

+ 6 - 10
build.gradle.kts

@@ -1,26 +1,22 @@
 plugins {
     alias(libs.plugins.android.application) apply false
     alias(libs.plugins.android.library) apply false
-    alias(libs.plugins.compose.multiplatform) apply false
+    alias(libs.plugins.compose) apply false
     alias(libs.plugins.compose.compiler) apply false
     alias(libs.plugins.kotlin.jvm) apply false
     alias(libs.plugins.kotlin.multiplatform) apply false
     alias(libs.plugins.app.icon.generator) apply false
+    // auto applied
     alias(libs.plugins.qodana)
     alias(libs.plugins.kover)
     alias(libs.plugins.changelog)
+    alias(libs.plugins.gradle.ktlint)
+    alias(libs.plugins.manes.versions)
 }
+
 dependencies {
     kover(project(":composeApp"))
     kover(project(":shared"))
 }
 
-kover {
-    reports {
-        total {
-            xml {
-                onCheck = true
-            }
-        }
-    }
-}
+kover { reports { total { xml { onCheck = true } } } }

+ 51 - 0
buildSrc/build.gradle.kts

@@ -1,9 +1,14 @@
+@file:Suppress("PropertyName")
+
 plugins {
     `kotlin-dsl`
 }
 
 repositories {
+    mavenLocal()
+    gradlePluginPortal()
     mavenCentral()
+    google()
 }
 
 dependencies {
@@ -24,3 +29,49 @@ gradlePlugin {
         }
     }
 }
+
+val FOLDER_BUILD = "build"
+val FOLDER_DISTRIBUTION = "distribution"
+
+val PLUGIN_FILE_NAME = "plugin.zip"
+
+val TASK_GROUP = "mjdev"
+val TASK_BUILD_PLUGIN = "installPlugin"
+val PROJECT_IDE_PLUGIN = "studio-plugin"
+val DEPENDENCY_BUILD_SRC = ":$PROJECT_IDE_PLUGIN:$TASK_BUILD_PLUGIN"
+
+fun Task.execute(action: Action<ExecSpec>) = this.project.providers.exec(action)
+val Task.pluginProject
+    get() = this.project.rootProject.project(PROJECT_IDE_PLUGIN)
+val Task.pluginDistributionFile
+    get() = "${pluginProject.path}/$FOLDER_BUILD/$FOLDER_DISTRIBUTION/$PLUGIN_FILE_NAME"
+
+tasks {
+    // clean task enhancement
+    clean.configure {
+        delete(FOLDER_BUILD)
+    }
+//    build {
+//        finalizedBy(TASK_BUILD_PLUGIN)
+//    }
+    register(TASK_BUILD_PLUGIN) {
+        group = TASK_GROUP
+        description = "Builds and installs the IDEA plugin."
+        doLast {
+            println("Installing IDEA plugin...")
+            val pluginFile = file(pluginDistributionFile)
+            if (pluginFile.exists()) {
+                providers.exec {
+                    commandLine(
+                        "idea.sh",
+                        "installPlugins",
+                        pluginFile.absolutePath
+                    )
+                }
+                println("Plugin installed successfully.")
+            } else {
+                println("Plugin file not found: $pluginDistributionFile")
+            }
+        }
+    }
+}

+ 2 - 0
buildSrc/settings.gradle.kts

@@ -6,6 +6,8 @@
  *  w: https://mjdev.org
  */
 
+rootProject.name = "buildSrc"
+
 dependencyResolutionManagement {
     versionCatalogs {
         create("libs") {

+ 2 - 2
buildSrc/src/main/kotlin/org/mjdev/gradle/extensions/ProjectExt.kt

@@ -4,7 +4,7 @@ import org.gradle.api.Project
 import org.gradle.api.Task
 
 object ProjectExt {
-    val GROUP = "mjdev"
+    const val GROUP = "mjdev"
 
     inline fun <reified T : Task> Project.registerTask(
         name: String,
@@ -12,4 +12,4 @@ object ProjectExt {
     ) = tasks.register(name, T::class.java) {
         this.group = group
     }
-}
+}

+ 9 - 0
buildSrc/src/main/kotlin/org/mjdev/gradle/extensions/SourceSets.kt

@@ -0,0 +1,9 @@
+package org.mjdev.gradle.extensions
+
+import org.gradle.api.NamedDomainObjectContainer
+//import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
+import org.gradle.kotlin.dsl.getting
+
+object SourceSets {
+//    val NamedDomainObjectContainer<KotlinSourceSet>.desktopMain by getting
+}

+ 10 - 0
buildSrc/src/main/kotlin/org/mjdev/gradle/extensions/TaskContainerExt.kt

@@ -0,0 +1,10 @@
+package org.mjdev.gradle.extensions
+
+import org.gradle.api.Task
+import org.gradle.api.tasks.TaskContainer
+
+object TaskContainerExt {
+    fun TaskContainer.clean(
+        block: Task.() -> Unit
+    ) = findByName("clean")?.apply(block)
+}

+ 20 - 4
composeApp/build.gradle.kts

@@ -7,16 +7,21 @@ import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
 plugins {
     alias(libs.plugins.kotlin.multiplatform)
     alias(libs.plugins.android.application)
-    alias(libs.plugins.compose.multiplatform)
+    alias(libs.plugins.compose)
     alias(libs.plugins.compose.compiler)
 //    MultiPlatformPlugin
 }
 
+java {
+    toolchain {
+        languageVersion.set(JavaLanguageVersion.of(17))
+    }
+}
+
 kotlin {
     jvm("desktop")
     wasmJs {
         outputModuleName = "composeApp"
-//        moduleName = "composeApp"
         browser {
             val rootDirPath = project.rootDir.path
             val projectDirPath = project.projectDir.path
@@ -175,11 +180,11 @@ android {
     compileSdk = libs.versions.android.compile.sdk.get().toInt()
     sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
     sourceSets["main"].res.srcDirs("src/androidMain/res")
-    sourceSets["main"].resources.srcDirs("src/commonMain/resources")
+    sourceSets["main"].resources.srcDirs("src/commonMain/composeResources")
+    sourceSets["main"].assets.srcDirs("src/commonMain/resources")
     defaultConfig {
         applicationId = "org.mjdev.desktop"
         minSdk = libs.versions.android.min.sdk.get().toInt()
-        targetSdk = libs.versions.android.target.sdk.get().toInt()
         versionCode = libs.versions.app.pkg.version.get().replace(".", "").toInt()
         versionName = libs.versions.app.pkg.version.get()
         resValue("string", "app_name", libs.versions.app.name.get())
@@ -200,12 +205,18 @@ android {
     }
     buildFeatures {
         compose = true
+        buildConfig = true
     }
     dependencies {
         debugImplementation(libs.androidx.ui.tooling)
     }
 }
 
+// remove
+//dependencies {
+//    implementation(libs.generativeai)
+//}
+
 // desktop app config
 compose {
     desktop {
@@ -259,4 +270,9 @@ compose {
             }
         }
     }
+    resources {
+        publicResClass = true
+        packageOfResClass = "org.mjdev.desktop.resources"
+        generateResClass = auto
+    }
 }

+ 4 - 10
composeApp/src/androidMain/kotlin/org/mjdev/desktop/activity/MainActivity.kt

@@ -30,22 +30,15 @@ import org.mjdev.desktop.activity.MainActivity.Companion.TAG
 import org.mjdev.desktop.main.MainView
 import org.mjdev.desktop.context.DesktopContext.Companion.rememberDesktopContext
 import org.mjdev.desktop.context.LocalDesktopContext
-import org.mjdev.desktop.extensions.Compose.applyTransform
-import org.mjdev.desktop.extensions.Compose.dither
-import org.mjdev.desktop.extensions.Compose.grayScale
 import org.mjdev.desktop.extensions.Compose.preview
 import org.mjdev.desktop.helpers.permission.rememberPermissionManager
 import org.mjdev.desktop.helpers.theme.DesktopTheme
 import org.mjdev.desktop.helpers.WakeLockHelper
+import org.mjdev.desktop.BuildConfig
 
 @OptIn(ExperimentalPermissionsApi::class)
 class MainActivity : ComponentActivity() {
-    val wakeLockHelper by lazy { WakeLockHelper(this) }
-
-//    @Suppress("OVERRIDE_DEPRECATION")
-//    override fun onBackPressed() {
-//        // Disable back button
-//    }
+    private val wakeLockHelper by lazy { WakeLockHelper(this) }
 
     override fun onCreate(savedInstanceState: Bundle?) {
         wakeLockHelper.acquireWakeLock()
@@ -62,7 +55,7 @@ class MainActivity : ComponentActivity() {
             systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
         }
         setContent {
-            onBackPress()
+            onBackPress(!BuildConfig.DEBUG)
             rememberPermissionManager()
             CompositionLocalProvider(
                 LocalDesktopContext provides rememberDesktopContext(baseContext)
@@ -93,6 +86,7 @@ class MainActivity : ComponentActivity() {
 
 @Composable
 fun onBackPress(
+    enabled:Boolean = true,
     action: () -> Unit = {
         Log.d(TAG, "OnBackPressed")
     }

+ 3 - 3
composeApp/src/androidMain/kotlin/org/mjdev/desktop/data/App.kt

@@ -21,7 +21,7 @@ class App(
     override val fullAppName: String = info.label.toString(),
     override val comment: String = info.applicationInfo.packageName,
     override val cmd: String = info.applicationInfo.packageName, // todo intent
-    override val fullTextString: String = info.toJson(),
+    override val fullTextString: String = "${name}|${comment}|${cmd}",
     override val categories: List<Category> = listOf(guessCategory(context, info))
 ) : IApp {
     override var isStarting: Boolean = false
@@ -33,8 +33,8 @@ class App(
             Intent.ACTION_MAIN
         ).apply {
             addCategory(Intent.CATEGORY_LAUNCHER)
-            setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
-            setComponent(info.componentName)
+            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+            component = info.componentName
         })
     }
 

+ 138 - 138
composeApp/src/androidMain/kotlin/org/mjdev/desktop/data/Theme.kt

@@ -1,140 +1,140 @@
 package org.mjdev.desktop.data
 
-//import androidx.compose.runtime.mutableStateOf
-//import androidx.compose.ui.graphics.Color
-//import androidx.compose.ui.unit.Dp
-//import androidx.compose.ui.unit.DpSize
-//import androidx.compose.ui.unit.dp
-//import org.mjdev.desktop.extensions.Colors.SuperDarkGray
-//import org.mjdev.desktop.fonts.MaterialSymbolsSharp
-//import org.mjdev.desktop.interfaces.IFont
-//import org.mjdev.desktop.interfaces.ITheme
-//import org.mjdev.desktop.interfaces.IUser
-
-//// todo all components customizable
-//@Suppress("unused", "MemberVisibilityCanBePrivate")
-//class Theme(
-//    backgroundColor: Color,
-//    backgroundRotationDelay: Long,
-//
-//    panelLocation: PanelLocation,
-//    panelDividerWidth: Dp,
-//    panelContentPadding: Dp,
-//    panelHideDelay: Long,
-//
-//    controlCenterLocation: ControlCenterLocation,
-//    controlPanelHideDelay: Long,
-//    controlCenterExpandedWidthPercent: Int,
-//    controlCenterDividerColor: Color = backgroundColor,
-//    controlCenterDividerWidth: Dp,
-//    controlCenterIconColor: Color,
-//    controlCenterIconSize: DpSize,
-//    controlCenterBackgroundAlpha: Float,
-//
-//    iconSet: IFont,
-//
-//    appMenuMinWidthRatio:Float,
-//    appMenuMinHeightRatio:Float,
-//    appMenuOuterPadding: Dp,
-//) : ITheme {
-//
-//    val backgroundColorState = mutableStateOf(backgroundColor)
-//    val backgroundRotationDelayState = mutableStateOf(backgroundRotationDelay)
-//
-//    val panelLocationState = mutableStateOf(panelLocation)
-//    val panelHideDelayState = mutableStateOf(panelHideDelay)
-//    val panelDividerWidthState = mutableStateOf(panelDividerWidth)
-//    val panelContentPaddingState = mutableStateOf(panelContentPadding)
-//
-//    val controlCenterLocationState = mutableStateOf(controlCenterLocation)
-//    val controlPanelHideDelayState = mutableStateOf(controlPanelHideDelay)
-//    var controlCenterExpandedWidthState = mutableStateOf(controlCenterExpandedWidthPercent)
-//    val controlCenterDividerColorState = mutableStateOf(controlCenterDividerColor)
-//    val controlCenterDividerWidthState = mutableStateOf(controlCenterDividerWidth)
-//    val controlCenterIconColorState = mutableStateOf(controlCenterIconColor)
-//    val controlCenterIconSizeState = mutableStateOf(controlCenterIconSize)
-//
-//    val iconSetState = mutableStateOf(iconSet)
-//
-//    val appMenuMinWidthState = mutableStateOf(appMenuMinWidthRatio)
-//    val appMenuMinHeightState = mutableStateOf(appMenuMinHeightRatio)
-//    val appMenuOuterPaddingState = mutableStateOf(appMenuOuterPadding)
-//
-//    val controlCenterBackgroundAlphaState = mutableStateOf(controlCenterBackgroundAlpha)
-//
-//    override var backgroundColor = backgroundColorState.value
-//
-//    override var panelLocation = panelLocationState.value
-//
-//    override var panelDividerWidth = panelDividerWidthState.value
-//
-//    override var panelContentPadding = panelContentPaddingState.value
-//
-//    override var panelHideDelay = panelHideDelayState.value
-//
-//    override var controlCenterLocation = controlCenterLocationState.value
-//
-//    override var controlPanelHideDelay = controlPanelHideDelayState.value
-//
-//    override var controlCenterExpandedWidthPercent = controlCenterExpandedWidthState.value
-//
-//    override var controlCenterDividerColor = controlCenterDividerColorState.value
-//
-//    override var controlCenterDividerWidth = controlCenterDividerWidthState.value
-//
-//    override var controlCenterIconColor = controlCenterIconColorState.value
-//
-//    override var controlCenterIconSize = controlCenterIconSizeState.value
-//
-//    override var controlCenterBackgroundAlpha = controlCenterBackgroundAlphaState.value
-//
-//    override var iconSet : IFont = iconSetState.value
-//
-//    override var backgroundRotationDelay = backgroundRotationDelayState.value
-//
-//    override var appMenuOuterPadding = appMenuOuterPaddingState.value
-//
-//    override var appMenuMinWidthRatio: Float = appMenuMinWidthState.value
-//
-//    override var appMenuMinHeightRatio: Float = appMenuMinHeightState.value
-//
-//    override fun dispose() {
-//        // todo
-//    }
-//
-//    companion object {
-//        val themeCache = mutableMapOf<String, Theme>()
-//
-//        val DEFAULT = Theme(
-//            iconSet = MaterialSymbolsSharp,
-//
-//            backgroundColor = Color.SuperDarkGray,
-//            backgroundRotationDelay = 60000,
-//
-//            panelLocation = PanelLocation.Bottom,
-//            panelDividerWidth = 16.dp,
-//            panelContentPadding = 4.dp,
-//            panelHideDelay = 2000L,
-//
-//            controlCenterLocation = ControlCenterLocation.Right,
-//            controlPanelHideDelay = 2000L,
-//            controlCenterExpandedWidthPercent = 25,
-//            controlCenterDividerColor = Color.SuperDarkGray,
-//            controlCenterDividerWidth = 4.dp,
-//            controlCenterIconColor = Color.White,
-//            controlCenterIconSize = DpSize(32.dp, 32.dp),
-//            controlCenterBackgroundAlpha = 0.6f,
-//
-//            appMenuMinWidthRatio = 0.3f,
-//            appMenuMinHeightRatio = 0.8f,
-//            appMenuOuterPadding = 2.dp
-//        )
-//
-//        // todo load from user settings
-//        fun load(
-//            user: IUser
-//        ): Theme = themeCache[user.userName] ?: DEFAULT.apply {
-//            themeCache[user.userName] = this
-//        }
-//    }
-//}
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import org.mjdev.desktop.extensions.Colors.SuperDarkGray
+import org.mjdev.desktop.helpers.font.MaterialSymbolsSharp
+import org.mjdev.desktop.interfaces.IFont
+import org.mjdev.desktop.interfaces.ITheme
+import org.mjdev.desktop.interfaces.IUser
+
+// todo all components customizable
+@Suppress("unused", "MemberVisibilityCanBePrivate")
+class Theme(
+    backgroundColor: Color,
+    backgroundRotationDelay: Long,
+
+    panelLocation: PanelLocation,
+    panelDividerWidth: Dp,
+    panelContentPadding: Dp,
+    panelHideDelay: Long,
+
+    controlCenterLocation: ControlCenterLocation,
+    controlPanelHideDelay: Long,
+    controlCenterExpandedWidthPercent: Int,
+    controlCenterDividerColor: Color = backgroundColor,
+    controlCenterDividerWidth: Dp,
+    controlCenterIconColor: Color,
+    controlCenterIconSize: DpSize,
+    controlCenterBackgroundAlpha: Float,
+
+    iconSet: IFont,
+
+    appMenuMinWidthRatio:Float,
+    appMenuMinHeightRatio:Float,
+    appMenuOuterPadding: Dp,
+) : ITheme {
+
+    val backgroundColorState = mutableStateOf(backgroundColor)
+    val backgroundRotationDelayState = mutableStateOf(backgroundRotationDelay)
+
+    val panelLocationState = mutableStateOf(panelLocation)
+    val panelHideDelayState = mutableStateOf(panelHideDelay)
+    val panelDividerWidthState = mutableStateOf(panelDividerWidth)
+    val panelContentPaddingState = mutableStateOf(panelContentPadding)
+
+    val controlCenterLocationState = mutableStateOf(controlCenterLocation)
+    val controlPanelHideDelayState = mutableStateOf(controlPanelHideDelay)
+    var controlCenterExpandedWidthState = mutableStateOf(controlCenterExpandedWidthPercent)
+    val controlCenterDividerColorState = mutableStateOf(controlCenterDividerColor)
+    val controlCenterDividerWidthState = mutableStateOf(controlCenterDividerWidth)
+    val controlCenterIconColorState = mutableStateOf(controlCenterIconColor)
+    val controlCenterIconSizeState = mutableStateOf(controlCenterIconSize)
+
+    val iconSetState = mutableStateOf(iconSet)
+
+    val appMenuMinWidthState = mutableStateOf(appMenuMinWidthRatio)
+    val appMenuMinHeightState = mutableStateOf(appMenuMinHeightRatio)
+    val appMenuOuterPaddingState = mutableStateOf(appMenuOuterPadding)
+
+    val controlCenterBackgroundAlphaState = mutableStateOf(controlCenterBackgroundAlpha)
+
+    override var backgroundColor = backgroundColorState.value
+
+    override var panelLocation = panelLocationState.value
+
+    override var panelDividerWidth = panelDividerWidthState.value
+
+    override var panelContentPadding = panelContentPaddingState.value
+
+    override var panelHideDelay = panelHideDelayState.value
+
+    override var controlCenterLocation = controlCenterLocationState.value
+
+    override var controlPanelHideDelay = controlPanelHideDelayState.value
+
+    override var controlCenterExpandedWidthPercent = controlCenterExpandedWidthState.value
+
+    override var controlCenterDividerColor = controlCenterDividerColorState.value
+
+    override var controlCenterDividerWidth = controlCenterDividerWidthState.value
+
+    override var controlCenterIconColor = controlCenterIconColorState.value
+
+    override var controlCenterIconSize = controlCenterIconSizeState.value
+
+    override var controlCenterBackgroundAlpha = controlCenterBackgroundAlphaState.value
+
+    override var iconSet : IFont = iconSetState.value
+
+    override var backgroundRotationDelay = backgroundRotationDelayState.value
+
+    override var appMenuOuterPadding = appMenuOuterPaddingState.value
+
+    override var appMenuMinWidthRatio: Float = appMenuMinWidthState.value
+
+    override var appMenuMinHeightRatio: Float = appMenuMinHeightState.value
+
+    override fun dispose() {
+        // todo
+    }
+
+    companion object {
+        val themeCache = mutableMapOf<String, Theme>()
+
+        val DEFAULT = Theme(
+            iconSet = MaterialSymbolsSharp,
+
+            backgroundColor = Color.SuperDarkGray,
+            backgroundRotationDelay = 60000,
+
+            panelLocation = PanelLocation.Bottom,
+            panelDividerWidth = 16.dp,
+            panelContentPadding = 4.dp,
+            panelHideDelay = 2000L,
+
+            controlCenterLocation = ControlCenterLocation.Right,
+            controlPanelHideDelay = 2000L,
+            controlCenterExpandedWidthPercent = 25,
+            controlCenterDividerColor = Color.SuperDarkGray,
+            controlCenterDividerWidth = 4.dp,
+            controlCenterIconColor = Color.White,
+            controlCenterIconSize = DpSize(32.dp, 32.dp),
+            controlCenterBackgroundAlpha = 0.6f,
+
+            appMenuMinWidthRatio = 0.3f,
+            appMenuMinHeightRatio = 0.8f,
+            appMenuOuterPadding = 2.dp
+        )
+
+        // todo load from user settings
+        fun load(
+            user: IUser
+        ): Theme = themeCache[user.userName] ?: DEFAULT.apply {
+            themeCache[user.userName] = this
+        }
+    }
+}

+ 1 - 1
composeApp/src/androidMain/kotlin/org/mjdev/desktop/data/User.kt

@@ -41,7 +41,7 @@ class User(
     override val config: DesktopConfig
         get() = DesktopConfig.load(this) // todo load
     override val theme: ITheme
-        get() =  ITheme.DEFAULT // Theme.load(this) // todo load
+        get() =  Theme.load(this) // todo load
     override val backgrounds
         get() = config.desktopBackgrounds
 

+ 7 - 2
composeApp/src/androidMain/kotlin/org/mjdev/desktop/extensions/JSonExt.kt

@@ -3,9 +3,14 @@ package org.mjdev.desktop.extensions
 import com.google.gson.GsonBuilder
 
 object JSonExt {
-    val gson = GsonBuilder().create()
+    val gson = GsonBuilder()
+        .disableInnerClassSerialization()
+        .disableJdkUnsafe()
+        .setLenient()
+        .setPrettyPrinting()
+        .create()
 
-    fun <T : Any> T.toJson() =
+    fun <T : Any> T.toJson(): String =
         gson.toJson(this)
 
     inline fun <reified T : Any> String.fromJson(): T =

+ 19 - 0
composeApp/src/androidMain/kotlin/org/mjdev/desktop/helpers/font/CodePointsFile.kt

@@ -0,0 +1,19 @@
+package org.mjdev.desktop.helpers.font
+
+import org.mjdev.desktop.helpers.streams.ResourceStream
+
+class CodePointsFile(
+    private val codepointsFileName: String
+) {
+    val icons: Map<String, Int> by lazy {
+        ResourceStream(codepointsFileName)
+            .text
+            .split("\n")
+            .map { it.split(" ") }
+            .mapNotNull {
+                if (it.size == 2) Pair(it[0], it[1].toInt(radix = 16))
+                else null
+            }
+            .toMap()
+    }
+}

+ 24 - 0
composeApp/src/androidMain/kotlin/org/mjdev/desktop/helpers/font/FontDescriptor.kt

@@ -0,0 +1,24 @@
+package org.mjdev.desktop.helpers.font
+
+import android.annotation.SuppressLint
+import android.graphics.Typeface
+import androidx.compose.ui.text.font.FontFamily
+import org.mjdev.desktop.helpers.streams.ResourceStream.Companion.openResource
+import java.io.File
+
+@SuppressLint("DiscouragedApi")
+class FontDescriptor(ttfFileName: String) {
+    private val fontTypeFace: Typeface? = runCatching {
+        val tempFile = File.createTempFile("font", ".ttf")
+        openResource(ttfFileName).use { inputStream ->
+            tempFile.outputStream().use { outputStream ->
+                inputStream.copyTo(outputStream)
+            }
+        }
+        Typeface.createFromFile(tempFile)
+    }.getOrNull()
+
+    val fontFamily: FontFamily = fontTypeFace?.let { typeFace ->
+        FontFamily(typeFace)
+    } ?: FontFamily.Default
+}

+ 12 - 29
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/fonts/MaterialIconFont.kt → composeApp/src/androidMain/kotlin/org/mjdev/desktop/helpers/font/MaterialIconFont.android.kt

@@ -1,17 +1,21 @@
-@file:Suppress("unused")
-
-package org.mjdev.desktop.fonts
+package org.mjdev.desktop.helpers.font
 
 import androidx.compose.ui.text.font.FontFamily
-import org.mjdev.desktop.interfaces.IFont
 import org.mjdev.desktop.helpers.fuzzywuzzy.FuzzySearch
+import org.mjdev.desktop.interfaces.IFont
 
-class MaterialIconFont(
+@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
+actual class MaterialIconFont actual constructor(
     private val ttfFileName: String,
     private val codepointsFileName: String,
-    private val fontFile: FontFile = FontFile(ttfFileName),
-    private val codePointsFile: CodePointsFile = CodePointsFile(codepointsFileName)
 ) : IFont {
+    private val fontFile: FontDescriptor by lazy {
+        FontDescriptor(ttfFileName)
+    }
+    private val codePointsFile: CodePointsFile by lazy {
+        CodePointsFile(codepointsFileName)
+    }
+
     override val fontFamily: FontFamily
         get() = fontFile.fontFamily
 
@@ -32,25 +36,4 @@ class MaterialIconFont(
             }
         } else null
     }.getOrNull()
-}
-
-val MaterialSymbolsOutlined by lazy {
-    MaterialIconFont(
-        "icons/MaterialSymbolsOutlined.ttf",
-        "icons/MaterialSymbolsOutlined.codepoints"
-    )
-}
-
-val MaterialSymbolsRounded by lazy {
-    MaterialIconFont(
-        "icons/MaterialSymbolsRounded.ttf",
-        "icons/MaterialSymbolsRounded.codepoints"
-    )
-}
-
-val MaterialSymbolsSharp by lazy {
-    MaterialIconFont(
-        "icons/MaterialSymbolsSharp.ttf",
-        "icons/MaterialSymbolsSharp.codepoints"
-    )
-}
+}

+ 1 - 1
composeApp/src/androidMain/kotlin/org/mjdev/desktop/helpers/permission/PermissonManager.kt

@@ -18,8 +18,8 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.LifecycleEventObserver
 import com.google.accompanist.permissions.ExperimentalPermissionsApi
 import com.google.accompanist.permissions.MultiplePermissionsState

+ 36 - 0
composeApp/src/androidMain/kotlin/org/mjdev/desktop/helpers/streams/ResourceStream.android.kt

@@ -0,0 +1,36 @@
+package org.mjdev.desktop.helpers.streams
+
+import java.io.InputStream
+
+@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "MemberVisibilityCanBePrivate")
+actual class ResourceStream actual constructor(
+    val resourcePath: String
+) {
+    actual val bytes: ByteArray
+        get() = useResource(resourcePath) { stream -> stream.readBytes() }
+    actual val text get() = String(bytes)
+
+    companion object {
+        inline fun <T> useResource(
+            resourcePath: String,
+            block: (InputStream) -> T
+        ): T = openResource(resourcePath).use(block)
+
+        fun openResource(
+            resourcePath: String,
+        ): InputStream = ClassLoaderResourceLoader.load(resourcePath)
+
+        private object ClassLoaderResourceLoader {
+            fun load(resourcePath: String): InputStream {
+                val resource = Thread
+                    .currentThread()
+                    .contextClassLoader
+                    ?.getResourceAsStream("assets/$resourcePath")
+                    ?: this::class.java.getResourceAsStream(resourcePath)
+                return requireNotNull(resource) {
+                    "Resource $resourcePath not found"
+                }
+            }
+        }
+    }
+}

+ 0 - 11
composeApp/src/androidMain/kotlin/org/mjdev/desktop/helpers/streams/ResourceStream.kt

@@ -1,11 +0,0 @@
-package org.mjdev.desktop.helpers.streams
-
-class ResourceStream(
-    private val resName: String,
-    val bytes: ByteArray = ByteArray(0) // to
-//        useResource(resName) { stream ->
-//        stream.readAllBytes()
-//    }
-) {
-    val string get() = String(bytes)
-}

+ 9 - 13
composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/apps/AppsManager.kt

@@ -32,23 +32,18 @@ class AppsManager(
     private val scope: CoroutineScope = context.scope
 ) : IAppsManager {
     val TAG = AppsManager::class.simpleName
+    private val androidContext = (context as? DesktopContext)?.context
+    private val userManger = androidContext?.getSystemService(Context.USER_SERVICE) as UserManager
 
-    private val androidContext by lazy {
-        (context as? DesktopContext)?.context
-    }
-    private val userManger by lazy {
-        runCatching {
-            androidContext?.getSystemService(Context.USER_SERVICE) as UserManager
-        }.getOrNull()
-    }
-    private val launcherApps by lazy {
-        runCatching {
+    private val launcherApps
+        get() = runCatching {
             androidContext?.getSystemService(LauncherApps::class.java)
         }.getOrNull()
-    }
 
     val userHandles: List<UserHandle>?
-        get() = userManger?.getUserProfiles()
+        get() = runCatching {
+            userManger.getUserProfiles()
+        }.getOrNull()
 
     override val currentLocale: ILocale
         get() = Locale.getDefault().toILocale()
@@ -83,6 +78,7 @@ class AppsManager(
         get() = emptyList()
 }
 
-private fun Locale.toILocale(): ILocale = ILocale.from(displayCountry, displayLanguage)
+private fun Locale.toILocale(): ILocale =
+    ILocale.from(displayCountry, displayLanguage)
 
 

+ 5 - 2
composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/keys/KeysManager.kt

@@ -1,11 +1,14 @@
 package org.mjdev.desktop.managers.keys
 
-import org.mjdev.desktop.helpers.streams.ResourceStream
+import org.jetbrains.compose.resources.ExperimentalResourceApi
+import org.mjdev.desktop.resources.Res
+import java.io.File
 
 class KeysManager {
 
+    @OptIn(ExperimentalResourceApi::class)
     fun loadKey(key: String): String = runCatching {
-        ResourceStream("keys/$key.key").string
+        File(Res.getUri("keys/$key.key")).readText()
     }.getOrNull().orEmpty()
 
 }

+ 2 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/blur/BlurPanel.kt

@@ -27,12 +27,12 @@ import androidx.compose.ui.unit.dp
 import org.jetbrains.compose.resources.DrawableResource
 import org.jetbrains.compose.resources.painterResource
 import org.mjdev.desktop.components.image.ImageAny
-import org.mjdev.desktop.composeapp.generated.resources.Res
-import org.mjdev.desktop.composeapp.generated.resources.blur4
 import org.mjdev.desktop.extensions.Colors.alpha
 import org.mjdev.desktop.extensions.Compose.preview
 import org.mjdev.desktop.context.DesktopContextScope.Companion.withDesktopContext
 import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.mjdev.desktop.resources.Res
+import org.mjdev.desktop.resources.blur4
 
 // todo real blur
 @Composable

+ 41 - 0
composeApp/src/commonMain/kotlin/org/mjdev/desktop/extensions/CustomExt.kt

@@ -10,6 +10,11 @@ import kotlinx.datetime.Instant
 import kotlinx.datetime.toLocalDateTime
 import kotlinx.datetime.TimeZone
 import kotlinx.datetime.TimeZone.Companion.currentSystemDefault
+import okio.Path
+import org.mjdev.desktop.extensions.PathExt.exists
+import org.mjdev.desktop.extensions.PathExt.text
+import org.mjdev.desktop.helpers.streams.ResourceStream
+import org.mjdev.desktop.interfaces.ILocale
 
 object CustomExt {
 
@@ -65,4 +70,40 @@ object CustomExt {
                 } while (true)
             }
         }.collectAsState(initial = "")
+
+    fun loadKey(key: String): String = runCatching {
+        ResourceStream("keys/$key.key").text
+    }.getOrNull().orEmpty()
+
+    //    inline fun <T, K> distinctList(
+//        vararg lists: List<T>,
+//        selector: (T) -> K
+//    ): List<T> = mutableListOf<T>().apply {
+//        lists.forEach { l -> addAll(l) }
+//    }.distinctBy(selector)
+
+//    fun String.notStartsWith(
+//        prefix: String,
+//        ignoreCase: Boolean = false
+//    ) = !startsWith(prefix, ignoreCase)
+
+//    fun String.trimIsEmpty() =
+//        trim().isEmpty()
+
+//    fun String.trimIsNotEmpty() =
+//        trim().isNotEmpty()
+
+//    fun State<String>.trimIsEmpty() =
+//        value.trim().isEmpty()
+
+//    fun String.trimStartsWith(
+//        prefix: String,
+//        ignoreCase: Boolean = false
+//    ) = trim().startsWith(prefix, ignoreCase)
+
+//    fun String.trimNotStartsWith(
+//        prefix: String,
+//        ignoreCase: Boolean = false
+//    ) = !trim().startsWith(prefix, ignoreCase)
+
 }

+ 24 - 0
composeApp/src/commonMain/kotlin/org/mjdev/desktop/helpers/font/MaterialIconFont.kt

@@ -0,0 +1,24 @@
+package org.mjdev.desktop.helpers.font
+
+import org.mjdev.desktop.interfaces.IFont
+
+@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
+expect class MaterialIconFont(
+    ttfFileName: String,
+    codepointsFileName: String,
+) : IFont
+
+val MaterialSymbolsOutlined = MaterialIconFont(
+    "icons/MaterialSymbolsOutlined.ttf",
+    "icons/MaterialSymbolsOutlined.codepoints"
+)
+
+val MaterialSymbolsRounded = MaterialIconFont(
+    "icons/MaterialSymbolsRounded.ttf",
+    "icons/MaterialSymbolsRounded.codepoints"
+)
+
+val MaterialSymbolsSharp = MaterialIconFont(
+    "icons/MaterialSymbolsSharp.ttf",
+    "icons/MaterialSymbolsSharp.codepoints"
+)

+ 9 - 0
composeApp/src/commonMain/kotlin/org/mjdev/desktop/helpers/streams/ResourceStream.kt

@@ -0,0 +1,9 @@
+package org.mjdev.desktop.helpers.streams
+
+@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
+expect class ResourceStream(
+    resourcePath: String
+) {
+    val bytes: ByteArray
+    val text: String
+}

+ 1 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/icons/text/Send.kt

@@ -4,7 +4,7 @@ import androidx.compose.material.icons.materialIcon
 import androidx.compose.material.icons.materialPath
 import androidx.compose.ui.graphics.vector.ImageVector
 
-public val Send: ImageVector
+val Send: ImageVector
     get() {
         if (_send != null) {
             return _send!!

+ 1 - 1
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/data/Theme.kt

@@ -6,7 +6,7 @@ import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import org.mjdev.desktop.extensions.Colors.SuperDarkGray
-import org.mjdev.desktop.fonts.MaterialSymbolsSharp
+import org.mjdev.desktop.helpers.font.MaterialSymbolsSharp
 import org.mjdev.desktop.interfaces.IFont
 import org.mjdev.desktop.interfaces.ITheme
 import org.mjdev.desktop.interfaces.IUser

+ 0 - 45
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/extensions/Custom.kt

@@ -1,15 +1,7 @@
 package org.mjdev.desktop.extensions
 
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.snapshots.SnapshotStateList
 import androidx.compose.ui.graphics.NativePaint
 import com.google.gson.Gson
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.channelFlow
-import kotlinx.coroutines.launch
-import org.mjdev.desktop.helpers.fuzzywuzzy.FuzzySearch
 import okio.Path
 import org.jetbrains.skia.FilterBlurMode.NORMAL
 import org.jetbrains.skia.MaskFilter.Companion.makeBlur
@@ -18,11 +10,8 @@ import org.mjdev.desktop.extensions.Locale.toLocale
 import org.mjdev.desktop.extensions.PathExt.exists
 import org.mjdev.desktop.extensions.PathExt.listFiles
 import org.mjdev.desktop.extensions.PathExt.text
-import org.mjdev.desktop.helpers.streams.ResourceStream
 import java.io.BufferedReader
 import java.lang.System.lineSeparator
-import java.text.DateFormat
-import java.util.Date
 import java.util.Locale
 import java.util.StringJoiner
 import kotlin.jvm.optionals.getOrNull
@@ -67,36 +56,6 @@ object Custom {
     val Path.desktopFiles: List<DesktopFile>
         get() = if (this.exists) listDesktopFiles() else emptyList()
 
-//    inline fun <T, K> distinctList(
-//        vararg lists: List<T>,
-//        selector: (T) -> K
-//    ): List<T> = mutableListOf<T>().apply {
-//        lists.forEach { l -> addAll(l) }
-//    }.distinctBy(selector)
-
-//    fun String.notStartsWith(
-//        prefix: String,
-//        ignoreCase: Boolean = false
-//    ) = !startsWith(prefix, ignoreCase)
-
-//    fun String.trimIsEmpty() =
-//        trim().isEmpty()
-
-//    fun String.trimIsNotEmpty() =
-//        trim().isNotEmpty()
-
-//    fun State<String>.trimIsEmpty() =
-//        value.trim().isEmpty()
-
-//    fun String.trimStartsWith(
-//        prefix: String,
-//        ignoreCase: Boolean = false
-//    ) = trim().startsWith(prefix, ignoreCase)
-
-//    fun String.trimNotStartsWith(
-//        prefix: String,
-//        ignoreCase: Boolean = false
-//    ) = !trim().startsWith(prefix, ignoreCase)
 
     fun NativePaint.setMaskFilter(
         blurRadius: Float
@@ -104,10 +63,6 @@ object Custom {
         maskFilter = makeBlur(NORMAL, blurRadius / 2, true)
     }
 
-    fun loadKey(key: String): String = runCatching {
-        ResourceStream("keys/$key.key").string
-    }.getOrNull().orEmpty()
-
     inline fun <reified T> String.jsonToList(): List<T> = runCatching {
         Gson().fromJson(this, List::class.java).mapNotNull { item -> item as? T }
     }.getOrNull() ?: emptyList()

+ 0 - 16
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/fonts/CodePointsFile.kt

@@ -1,16 +0,0 @@
-package org.mjdev.desktop.fonts
-
-import org.mjdev.desktop.helpers.streams.ResourceStream
-
-class CodePointsFile(
-    private val codepointsFileName: String,
-    private val codepointsResourceResource: ResourceStream = ResourceStream(codepointsFileName),
-
-    ) {
-    val icons: Map<String, Int> by lazy {
-        codepointsResourceResource.string.split("\n")
-            .map { it.split(" ") }
-            .mapNotNull { if (it.size == 2) Pair(it[0], it[1].toInt(radix = 16)) else null }
-            .toMap()
-    }
-}

+ 0 - 18
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/fonts/FontFile.kt

@@ -1,18 +0,0 @@
-package org.mjdev.desktop.fonts
-
-import androidx.compose.ui.text.font.Font
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.platform.Font
-import org.mjdev.desktop.helpers.streams.ResourceStream
-
-class FontFile(
-    private val ttfFileName: String,
-    private val ttfResource: ResourceStream = ResourceStream(ttfFileName),
-    private val fontWeight: FontWeight = FontWeight.Normal,
-    private val font: Font = Font(ttfFileName, ttfResource.bytes, fontWeight),
-) {
-    val fontFamily by lazy {
-        FontFamily(font)
-    }
-}

+ 19 - 0
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/helpers/font/CodePointsFile.kt

@@ -0,0 +1,19 @@
+package org.mjdev.desktop.helpers.font
+
+import org.mjdev.desktop.helpers.streams.ResourceStream
+
+class CodePointsFile(
+    private val codepointsFileName: String
+) {
+    val icons: Map<String, Int> by lazy {
+        ResourceStream(codepointsFileName)
+            .text
+            .split("\n")
+            .map { it.split(" ") }
+            .mapNotNull {
+                if (it.size == 2) Pair(it[0], it[1].toInt(radix = 16))
+                else null
+            }
+            .toMap()
+    }
+}

+ 18 - 0
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/helpers/font/FontDescriptor.kt

@@ -0,0 +1,18 @@
+package org.mjdev.desktop.helpers.font
+
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.platform.Font
+import androidx.compose.ui.text.font.toFontFamily
+import org.mjdev.desktop.log.Log
+
+class FontDescriptor(ttfFileName: String) {
+    private val font: Font? = runCatching {
+        Font(ttfFileName)
+    }.onFailure { e ->
+        Log.e(e)
+    }.getOrNull()
+
+    val fontFamily: FontFamily =
+        font?.toFontFamily() ?: FontFamily.Default
+}

+ 39 - 0
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/helpers/font/MaterialIconFont.kt

@@ -0,0 +1,39 @@
+package org.mjdev.desktop.helpers.font
+
+import androidx.compose.ui.text.font.FontFamily
+import org.mjdev.desktop.helpers.fuzzywuzzy.FuzzySearch
+import org.mjdev.desktop.interfaces.IFont
+
+@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
+actual class MaterialIconFont actual constructor(
+    ttfFileName: String,
+    codepointsFileName: String,
+) : IFont {
+    private val fontFile: FontDescriptor by lazy {
+        FontDescriptor(ttfFileName)
+    }
+    private val codePointsFile: CodePointsFile by lazy {
+        CodePointsFile(codepointsFileName)
+    }
+
+    override val fontFamily: FontFamily
+        get() = fontFile.fontFamily
+
+    private val cache: MutableMap<String, Int> = mutableMapOf()
+
+    override fun iconForName(
+        iconName: String?,
+    ): Int? = runCatching {
+        if (iconName != null) {
+            if (cache.containsKey(iconName)) {
+                cache[iconName]
+            } else {
+                codePointsFile.icons.map { icon ->
+                    Pair(FuzzySearch.ratio(iconName, icon.key), icon)
+                }.maxByOrNull { it.first }?.second?.value?.also {
+                    cache[iconName] = it
+                }
+            }
+        } else null
+    }.getOrNull()
+}

+ 37 - 0
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/helpers/streams/ResourceStream.desktop.kt

@@ -0,0 +1,37 @@
+package org.mjdev.desktop.helpers.streams
+
+import java.io.InputStream
+
+@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING", "MemberVisibilityCanBePrivate")
+actual class ResourceStream actual constructor(
+    val resourcePath: String
+) {
+    actual val bytes: ByteArray
+        get() = useResource(resourcePath) { stream -> stream.readBytes() }
+    actual val text
+        get() = String(bytes)
+
+    companion object {
+        inline fun <T> useResource(
+            resourcePath: String,
+            block: (InputStream) -> T
+        ): T = openResource(resourcePath).use(block)
+
+        fun openResource(
+            resourcePath: String,
+        ): InputStream = ClassLoaderResourceLoader.load(resourcePath)
+
+        private object ClassLoaderResourceLoader {
+            fun load(resourcePath: String): InputStream {
+                val resource = Thread
+                    .currentThread()
+                    .contextClassLoader
+                    ?.getResourceAsStream(resourcePath)
+                    ?: this::class.java.getResourceAsStream(resourcePath)
+                return requireNotNull(resource) {
+                    "Resource $resourcePath not found"
+                }
+            }
+        }
+    }
+}

+ 0 - 13
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/helpers/streams/ResourceStream.kt

@@ -1,13 +0,0 @@
-package org.mjdev.desktop.helpers.streams
-
-import androidx.compose.ui.res.useResource
-
-@Suppress("DEPRECATION")
-class ResourceStream(
-    private val resName: String,
-    val bytes: ByteArray = useResource(resName) { stream ->
-        stream.readAllBytes()
-    }
-) {
-    val string get() = String(bytes)
-}

+ 1 - 1
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/managers/ai/plugins/AiPluginGemini.kt

@@ -12,9 +12,9 @@ import dev.shreyaspatil.ai.client.generativeai.GenerativeModel
 import dev.shreyaspatil.ai.client.generativeai.type.content
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.async
-import org.mjdev.desktop.extensions.Custom.loadKey
 import org.mjdev.desktop.interfaces.IDesktopContext
 import org.mjdev.desktop.managers.ai.AIPlugin
+import org.mjdev.desktop.extensions.CustomExt.loadKey
 
 /*
 * Gemini key : https://makersuite.google.com/app/apikey

+ 1 - 1
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/managers/apps/AppsManager.kt

@@ -61,7 +61,7 @@ class AppsManager(
         get() = context.currentUser
 
     private val homeDir
-        get() = currentUser?.homeDir ?: "~".toPath()
+        get() = currentUser.homeDir
 
     private val rootDir: Path = DIR_NAME_ROOT.toPath()
     private val usrDir: Path = DIR_NAME_USR.toPath()

+ 1 - 1
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/managers/keys/KeysManager.kt

@@ -4,6 +4,6 @@ import org.mjdev.desktop.helpers.streams.ResourceStream
 
 class KeysManager : IKeyManager {
     override fun loadKey(key: String): String = runCatching {
-        ResourceStream("keys/$key.key").string
+        ResourceStream("keys/$key.key").text
     }.getOrNull().orEmpty()
 }

+ 15 - 0
composeApp/src/wasmJsMain/kotlin/org/mjdev/desktop/helpers/font/MaterialIconFont.wasmJs.kt

@@ -0,0 +1,15 @@
+package org.mjdev.desktop.helpers.font
+
+import androidx.compose.ui.text.font.FontFamily
+import org.mjdev.desktop.interfaces.IFont
+
+@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
+actual class MaterialIconFont actual constructor(
+    ttfFileName: String,
+    codepointsFileName: String,
+) : IFont {
+    override val fontFamily: FontFamily
+        get() = FontFamily.Default
+
+    override fun iconForName(iconName: String?): Int? = null
+}

+ 12 - 0
composeApp/src/wasmJsMain/kotlin/org/mjdev/desktop/helpers/streams/ResourceStream.wasmJs.kt

@@ -0,0 +1,12 @@
+package org.mjdev.desktop.helpers.streams
+
+// todo: implement this for wasmJs
+@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
+actual class ResourceStream actual constructor(
+    val resourcePath: String
+) {
+    actual val bytes: ByteArray
+        get() = byteArrayOf()
+    actual val text: String
+        get() = ""
+}

+ 15 - 7
gradle.properties

@@ -1,3 +1,4 @@
+####################################################################################################
 # Base
 kotlin.code.style=official
 # do not check or build disabled
@@ -6,9 +7,13 @@ kotlin.native.ignoreDisabledTargets=true
 #kotlin.mpp.androidGradlePluginCompatibility.nowarn=true
 # gradle
 org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
-# android non transitive R class
+# android options
 android.nonTransitiveRClass=true
-# android use androidx
+android.defaults.buildfeatures.aidl=false
+android.defaults.buildfeatures.renderscript=false
+android.defaults.buildfeatures.resvalues=true
+android.defaults.buildfeatures.shaders=false
+android.enableJetifier=true
 android.useAndroidX=true
 # kotlin Multiplatform
 kotlin.mpp.enableCInteropCommonization=true
@@ -20,16 +25,19 @@ kotlin.mpp.applyDefaultHierarchyTemplate=false
 org.gradle.configuration-cache=true
 org.gradle.configuration-cache.problems=warn
 org.gradle.configuration-cache.parallel=true
-# plugin
-pluginGroup = org.mjdev.testplugin
-pluginName = IntelliJ Platform Plugin Test
+org.gradle.caching=true
+org.gradle.configureondemand=true
+org.gradle.kotlin.dsl.caching.buildcache=true
+# project plugin
+pluginGroup = org.mjdev.plugins.project
+pluginName = ProjectDetails
 pluginRepositoryUrl = -
 pluginVersion = 1.0.0
-pluginDescription = Test plugin for studio
+pluginDescription = Project details plugin for ide
 pluginSinceBuild = 251.23774.16
 pluginUntilBuild = 252.*
 platformType = AI
 platformVersion = 2025.1.1.7
 platformPlugins =
 platformBundledPlugins =
-#
+####################################################################################################

+ 13 - 7
gradle/libs.versions.toml

@@ -10,11 +10,11 @@ app-pkg-name = "org.mjdev.desktop"
 app-pkg-version = "1.0.2"
 app-vendor = "mjdev"
 # app specific android
-android-compile-sdk = "35"
+android-compile-sdk = "36"
 android-min-sdk = "24"
-android-target-sdk = "35"
+#android-target-sdk = "36"
 # lib versions
-gradle = "8.9.1"
+gradle = "8.9.2"
 #gradle-kotlinter = "4.3.0"
 androidx-activity-compose = "1.10.1"
 androidx-lifecycle = "2.8.4"
@@ -40,7 +40,7 @@ okio = "3.11.0"
 qrose = "1.0.1"
 tts = "3.0.0-beta.1"
 ychat = "1.4.1"
-generativeai = "0.9.0"
+#generativeai = "0.9.0"
 alert-kmp = "2.0.0"
 #emoji2 = "1.5.0"
 accompanist-permissions = "0.37.2"
@@ -48,6 +48,9 @@ kover = "0.9.1"
 qodana = "2024.3.4"
 intelliJPlatform = "2.5.0"
 changelog = "2.2.1"
+gradle-ktlint="12.2.0"
+foojay-resolver-convention="0.9.0"
+manes-versions="0.48.0"
 
 [libraries]
 # gradle
@@ -96,11 +99,11 @@ ini4j = { module = "org.ini4j:ini4j", version.ref = "ini4j" }
 tts = { module = "nl.marc-apps:tts", version.ref = "tts" }
 # ai
 google-generative-ai = { module = "dev.shreyaspatil.generativeai:generativeai-google-jvm", version.ref = "generative-ai" }
-generativeai = { group = "com.google.ai.client.generativeai", name = "generativeai", version.ref = "generativeai" }
+#generativeai = { group = "com.google.ai.client.generativeai", name = "generativeai", version.ref = "generativeai" }
 # strings
 human-readable = { module = "nl.jacobras:Human-Readable", version.ref = "human-readable" }
 # alerts
-alert-kmp = { module = "io.github.khubaibkhan4:alert-kmp", version.ref="alert-kmp" }
+alert-kmp = { module = "io.github.khubaibkhan4:alert-kmp", version.ref = "alert-kmp" }
 #androidx-emoji2 = { group = "androidx.emoji2", name = "emoji2", version.ref = "emoji2" }
 accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist-permissions" }
 
@@ -108,7 +111,7 @@ accompanist-permissions = { module = "com.google.accompanist:accompanist-permiss
 android-application = { id = "com.android.application", version.ref = "gradle" }
 android-library = { id = "com.android.library", version.ref = "gradle" }
 app-icon-generator = { id = "io.github.qamarelsafadi.kmp.app.icon.generator", version.ref = "app-icon" }
-compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }
+compose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }
 compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
 kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
 kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
@@ -116,3 +119,6 @@ kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
 qodana = { id = "org.jetbrains.qodana", version.ref = "qodana" }
 intellij-platform = { id = "org.jetbrains.intellij.platform", version.ref = "intelliJPlatform" }
 changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" }
+gradle-ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "gradle-ktlint" }
+#foojay-resolver-convention = { id ="org.gradle.toolchains.foojay-resolver-convention", version.ref = "foojay-resolver-convention" }
+manes-versions = { id = "com.github.ben-manes.versions", version.ref  = "manes-versions" }

+ 2 - 0
settings.gradle.kts

@@ -11,6 +11,7 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
 //<editor-fold desc="plugin management">------------------------------------------------------------
 pluginManagement {
     repositories {
+        mavenLocal()
         gradlePluginPortal()
         mavenCentral()
         maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
@@ -42,6 +43,7 @@ pluginManagement {
 //<editor-fold desc="dependency management">--------------------------------------------------------
 dependencyResolutionManagement {
     repositories {
+        mavenLocal()
         gradlePluginPortal()
         mavenCentral()
         maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")

+ 5 - 2
studio-plugin/build.gradle.kts

@@ -9,6 +9,7 @@ plugins {
     alias(libs.plugins.changelog)
     alias(libs.plugins.qodana)
     alias(libs.plugins.kover)
+    alias(libs.plugins.gradle.ktlint)
 }
 
 group = providers.gradleProperty("pluginGroup").get()
@@ -17,7 +18,9 @@ version = providers.gradleProperty("pluginVersion").get()
 //kotlin {
 //    jvmToolchain(17)
 //}
+
 repositories {
+    mavenLocal()
     mavenCentral()
     intellijPlatform {
         defaultRepositories()
@@ -38,7 +41,7 @@ dependencies {
         plugins(providers.gradleProperty("platformPlugins").map {
             it.split(',')
         })
-        testFramework(TestFrameworkType.Platform)
+//        testFramework(TestFrameworkType.Platform)
     }
 }
 
@@ -112,4 +115,4 @@ intellijPlatformTesting {
             }
         }
     }
-}
+}

+ 0 - 17
studio-plugin/src/main/kotlin/org/jetbrains/plugins/template/services/MyProjectService.kt

@@ -1,17 +0,0 @@
-package org.jetbrains.plugins.template.services
-
-import com.intellij.openapi.components.Service
-import com.intellij.openapi.diagnostic.thisLogger
-import com.intellij.openapi.project.Project
-import org.jetbrains.plugins.template.MyBundle
-
-@Service(Service.Level.PROJECT)
-class MyProjectService(
-    project: Project
-) {
-    init {
-        thisLogger().info(MyBundle.message("projectService", project.name))
-        thisLogger().warn("Don't forget to remove all non-needed sample code files with their corresponding registration entries in `plugin.xml`.")
-    }
-    fun getRandomNumber() = (1..100).random()
-}

+ 0 - 11
studio-plugin/src/main/kotlin/org/jetbrains/plugins/template/startup/MyProjectActivity.kt

@@ -1,11 +0,0 @@
-package org.jetbrains.plugins.template.startup
-
-import com.intellij.openapi.diagnostic.thisLogger
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.startup.ProjectActivity
-
-class MyProjectActivity : ProjectActivity {
-    override suspend fun execute(project: Project) {
-        thisLogger().warn("Don't forget to remove all non-needed sample code files with their corresponding registration entries in `plugin.xml`.")
-    }
-}

+ 1 - 1
studio-plugin/src/main/kotlin/org/jetbrains/plugins/template/MyBundle.kt → studio-plugin/src/main/kotlin/org/mjdev/plugins/project/MyBundle.kt

@@ -1,4 +1,4 @@
-package org.jetbrains.plugins.template
+package org.mjdev.plugins.project
 
 import com.intellij.DynamicBundle
 import org.jetbrains.annotations.NonNls

+ 22 - 0
studio-plugin/src/main/kotlin/org/mjdev/plugins/project/services/MyProjectService.kt

@@ -0,0 +1,22 @@
+package org.mjdev.plugins.project.services
+
+import com.intellij.openapi.components.Service
+import com.intellij.openapi.diagnostic.thisLogger
+import com.intellij.openapi.project.Project
+import org.jetbrains.plugins.template.MyBundle
+
+@Service(Service.Level.PROJECT)
+class MyProjectService(project: Project) {
+    val log
+        get() = thisLogger()
+
+    init {
+        log.info(MyBundle.message("projectService", project.name))
+        log.warn(
+            "Don't forget to remove all non-needed sample code files with their corresponding " +
+                    "registration entries in `plugin.xml`."
+        )
+    }
+
+    fun getRandomNumber() = (1..100).random()
+}

+ 18 - 0
studio-plugin/src/main/kotlin/org/mjdev/plugins/project/startup/MyProjectActivity.kt

@@ -0,0 +1,18 @@
+package org.mjdev.plugins.project.startup
+
+import com.intellij.openapi.diagnostic.thisLogger
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.startup.ProjectActivity
+
+class MyProjectActivity : ProjectActivity {
+    val log
+        get() = thisLogger()
+    override suspend fun execute(
+        project: Project
+    ) {
+        log.warn(
+            "Don't forget to remove all non-needed sample code files with their corresponding " +
+                    "registration entries in `plugin.xml`."
+        )
+    }
+}

+ 15 - 6
studio-plugin/src/main/kotlin/org/jetbrains/plugins/template/toolWindow/MyToolWindowFactory.kt → studio-plugin/src/main/kotlin/org/mjdev/plugins/project/toolWindow/MyToolWindowFactory.kt

@@ -1,4 +1,4 @@
-package org.jetbrains.plugins.template.toolWindow
+package org.mjdev.plugins.project.toolWindow
 
 import com.intellij.openapi.components.service
 import com.intellij.openapi.diagnostic.thisLogger
@@ -13,20 +13,29 @@ import org.jetbrains.plugins.template.services.MyProjectService
 import javax.swing.JButton
 
 class MyToolWindowFactory : ToolWindowFactory {
+    val log
+        get() = thisLogger()
+
     init {
-        thisLogger()
-            .warn("Don't forget to remove all non-needed sample code files with their corresponding registration entries in `plugin.xml`.")
+        log.warn(
+            "Don't forget to remove all non-needed sample code files with their corresponding "+
+                    "registration entries in `plugin.xml`."
+        )
     }
-    override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
+
+    override fun createToolWindowContent(
+        project: Project,
+        toolWindow: ToolWindow
+    ) {
         val myToolWindow = MyToolWindow(toolWindow)
-        val content = ContentFactory.getInstance().createContent(myToolWindow.getContent(), null, false)
+        val content = ContentFactory.getInstance()
+            .createContent(myToolWindow.getContent(), null, false)
         toolWindow.contentManager.addContent(content)
     }
 
     override fun shouldBeAvailable(project: Project) = true
 
     class MyToolWindow(toolWindow: ToolWindow) {
-
         private val service = toolWindow.project.service<MyProjectService>()
 
         fun getContent() = JBPanel<JBPanel<*>>().apply {

+ 5 - 5
studio-plugin/src/main/resources/META-INF/plugin.xml

@@ -1,12 +1,12 @@
 <!-- Plugin Configuration File. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html -->
 <idea-plugin>
-    <id>org.jetbrains.plugins.template</id>
-    <name>IntelliJ Platform Plugin Template</name>
-    <vendor>JetBrains</vendor>
+    <id>org.mjdev.plugins.project</id>
+    <name>Project</name>
+    <vendor>mjdev</vendor>
     <depends>com.intellij.modules.platform</depends>
     <resource-bundle>messages.MyBundle</resource-bundle>
     <extensions defaultExtensionNs="com.intellij">
-        <toolWindow factoryClass="org.jetbrains.plugins.template.toolWindow.MyToolWindowFactory" id="MyToolWindow"/>
-        <postStartupActivity implementation="org.jetbrains.plugins.template.startup.MyProjectActivity" />
+        <toolWindow factoryClass="org.mjdev.plugins.project.toolWindow.MyToolWindowFactory" id="MyToolWindow"/>
+        <postStartupActivity implementation="org.mjdev.plugins.project.startup.MyProjectActivity" />
     </extensions>
 </idea-plugin>

+ 2 - 5
studio-plugin/src/test/kotlin/org/jetbrains/plugins/template/MyPluginTest.kt → studio-plugin/src/test/kotlin/org/mjdev/plugins/mjdev/MyPluginTest.kt

@@ -1,4 +1,4 @@
-package org.jetbrains.plugins.template
+package org.mjdev.plugins.project
 
 import com.intellij.ide.highlighter.XmlFileType
 import com.intellij.openapi.components.service
@@ -6,7 +6,7 @@ import com.intellij.psi.xml.XmlFile
 import com.intellij.testFramework.TestDataPath
 import com.intellij.testFramework.fixtures.BasePlatformTestCase
 import com.intellij.util.PsiErrorElementUtil
-import org.jetbrains.plugins.template.services.MyProjectService
+import org.mjdev.plugins.project.services.MyProjectService
 
 @TestDataPath("\$CONTENT_ROOT/src/test/testData")
 class MyPluginTest : BasePlatformTestCase() {
@@ -14,11 +14,8 @@ class MyPluginTest : BasePlatformTestCase() {
     fun testXMLFile() {
         val psiFile = myFixture.configureByText(XmlFileType.INSTANCE, "<foo>bar</foo>")
         val xmlFile = assertInstanceOf(psiFile, XmlFile::class.java)
-
         assertFalse(PsiErrorElementUtil.hasErrors(project, xmlFile.virtualFile))
-
         assertNotNull(xmlFile.rootTag)
-
         xmlFile.rootTag?.let {
             assertEquals("foo", it.name)
             assertEquals("bar", it.value.text)

+ 1 - 1
studio-plugin/src/test/testData/rename/foo.xml

@@ -1,3 +1,3 @@
 <root>
-    <a<caret>1>Foo</a1>
+    <a1><caret>1>Foo</caret></a1>
 </root>

+ 1 - 1
studio-plugin/src/test/testData/rename/foo_after.xml

@@ -1,3 +1,3 @@
 <root>
-    <a2>Foo</a2>
+    <a2><caret>2>Foo</caret></a2>
 </root>