فهرست منبع

storage provider

Milan Jurkulak 1 سال پیش
والد
کامیت
fd4cdad43a
100فایلهای تغییر یافته به همراه748 افزوده شده و 502 حذف شده
  1. 1 1
      .intellijPlatform/self-update.lock
  2. 0 2
      README.md
  3. 11 0
      build.gradle.kts
  4. 65 11
      composeApp/composeApp.gradle.kts
  5. 0 0
      composeApp/composeApp.pro
  6. 2 1
      composeApp/src/androidMain/AndroidManifest.xml
  7. 28 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/activity/MainActivity.kt
  8. 24 8
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/context/DesktopContext.kt
  9. 0 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/context/LocalDesktopContext.android.kt
  10. 0 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/data/App.kt
  11. 1 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/data/User.kt
  12. 1 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/extensions/ImageBitmapExt.kt
  13. 0 18
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/extensions/JSonExt.kt
  14. 44 25
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/helpers/persistence/StorageProvider.kt
  15. 1 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/helpers/system/meminfo/MemInfo.android.kt
  16. 1 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/helpers/system/meminfo/MemInfoAndroid.kt
  17. 0 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/main/MainView.kt
  18. 1 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/ai/tts/TTSPluginAndroid.kt
  19. 27 23
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/apps/AppsManager.kt
  20. 1 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/connectivity/ConnectivityManager.kt
  21. 1 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/connectivity/ConnectivityManagerStub.kt
  22. 1 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/keys/KeysManager.kt
  23. 1 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/os/OSManager.kt
  24. 1 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/processes/ProcessManager.kt
  25. 1 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/theme/ThemeManager.kt
  26. 1 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/theme/base/ThemeManagerStub.kt
  27. 1 1
      composeApp/src/androidMain/kotlin/org/mjdev/desktop/windows/ChromeWindow.kt
  28. 4 0
      composeApp/src/commonMain/composeResources/values/strings.xml
  29. 16 19
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/appsmenu/AppsMenu.kt
  30. 0 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/appsmenu/components/AppsList.kt
  31. 9 9
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/appsmenu/components/AppsMenuActions.kt
  32. 6 8
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/appsmenu/components/AppsMenuApp.kt
  33. 1 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/card/PhotoCard.kt
  34. 14 22
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/ControlCenter.kt
  35. 16 9
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/base/ControlCenterPage.kt
  36. 14 7
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/base/ControlCenterPageScope.kt
  37. 5 5
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/base/PageCache.kt
  38. 3 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/base/PageHeader.kt
  39. 30 0
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/base/PersistentPageSaver.kt
  40. 6 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/about/AboutPage.kt
  41. 19 17
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/ai/AIPage.kt
  42. 6 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/bluetooth/BluetoothSettingsPage.kt
  43. 6 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/devices/Devices.kt
  44. 6 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/display/DisplaySettingsPage.kt
  45. 6 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/ethernet/EthSettingsPage.kt
  46. 8 7
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/main/MainSettingsPage.kt
  47. 6 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/remotes/RemotesSettingsPage.kt
  48. 6 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/sound/SoundSettingsPage.kt
  49. 6 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/theme/ThemeSettingsPage.kt
  50. 25 11
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/wifi/WifiSettingsPage.kt
  51. 7 15
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/custom/Clock.kt
  52. 1 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/custom/VerticalScrollableBox.kt
  53. 2 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/desktoppanel/DesktopPanel.kt
  54. 12 3
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/desktoppanel/applets/DesktopPanelFavoriteApps.kt
  55. 0 5
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/file/FolderView.kt
  56. 18 21
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/image/ImageAny.kt
  57. 0 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/info/Info.kt
  58. 0 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/installer/Installer.kt
  59. 4 5
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/text/TextArea.kt
  60. 9 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/context/DesktopContextScope.kt
  61. 0 69
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/context/EmptyDesktopContext.kt
  62. 61 22
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/context/IDesktopContext.kt
  63. 0 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/context/LocalDesktopContext.kt
  64. 24 28
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/extensions/Compose.kt
  65. 87 4
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/extensions/CustomExt.kt
  66. 1 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/extensions/ImageBitmapExt.kt
  67. 2 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/extensions/LaunchedEffect.kt
  68. 2 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/helpers/generic/Queue.kt
  69. 0 8
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/helpers/persistence/KeyValueStorage.kt
  70. 10 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/helpers/persistence/StorageProvider.kt
  71. 1 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/helpers/system/meminfo/MemInfo.kt
  72. 1 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/helpers/system/meminfo/MemInfoStub.kt
  73. 2 0
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/interfaces/IControlCenterPageDataSaver.kt
  74. 9 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/interfaces/IPage.kt
  75. 1 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/AiManager.kt
  76. 1 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/IAiManager.kt
  77. 2 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/actions/base/ActionProviderScope.kt
  78. 1 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/actions/base/ActionsProvider.kt
  79. 1 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/plugins/AiPluginEmpty.kt
  80. 1 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/plugins/AiPluginGemini.kt
  81. 1 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/plugins/AiPluginOllama.kt
  82. 1 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/stt/STTPluginEmpty.kt
  83. 1 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/tts/TTSPluginEmpty.kt
  84. 1 3
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/tts/TTSPluginMain.kt
  85. 5 0
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/apps/IAppsManager.kt
  86. 5 4
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/base/ManagerCache.kt
  87. 0 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/connectivity/IConnectivityManager.kt
  88. 1 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/language/Translator.kt
  89. 1 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/os/base/OSManagerStub.kt
  90. 2 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/palette/IPalette.kt
  91. 1 2
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/palette/Palette.kt
  92. 3 10
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/process/IProcessManager.kt
  93. 0 1
      composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/translations/ITranslator.kt
  94. 0 1
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/components/controlcenter/ControlCenterWindow.kt
  95. 2 1
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/components/desktoppanel/DesktopPanelWindow.kt
  96. 0 1
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/components/greeter/GreeterWindow.kt
  97. 0 1
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/components/main/MainWindow.kt
  98. 30 21
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/context/DeskptopContext.kt
  99. 0 1
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/context/LocalDesktopContext.desktop.kt
  100. 1 1
      composeApp/src/desktopMain/kotlin/org/mjdev/desktop/data/User.kt

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

@@ -1 +1 @@
-2025-04-23
+2025-06-10

+ 0 - 2
README.md

@@ -60,8 +60,6 @@ Build on compose kotlin and jvm, multiplatform, for android, web & desktop.
   Image loader
 - [Coroutines Swing] (https://github.com/Kotlin/kotlinx.coroutines)
   Coroutines in JVM
-- [Google Gson] (https://github.com/google/gson)
-  JSON Parser
 - [FuzzyWuzzy] (https://github.com/xdrop/fuzzywuzzy)
   Fuzzy search
 - [Thechance101 Chart] (https://github.com/TheChance101/AAY-chart)

+ 11 - 0
build.gradle.kts

@@ -14,8 +14,19 @@ plugins {
     alias(libs.plugins.gradle.ktlint)
     alias(libs.plugins.manes.versions) apply false
     alias(libs.plugins.kotlin.android) apply false
+//    alias(libs.plugins.vlc.setup) apply true
 }
 
+// vlc setup
+//vlcSetup {
+//    vlcVersion = "3.0.21"
+//    shouldCompressVlcFiles = true
+//    shouldIncludeAllVlcFiles = false
+//    pathToCopyVlcLinuxFilesTo = rootDir.resolve("resources/linux/")
+//    pathToCopyVlcMacosFilesTo = rootDir.resolve("resources/macos/")
+//    pathToCopyVlcWindowsFilesTo = rootDir.resolve("resources/windows/")
+//}
+
 //dependencies {
 //    kover(project(":composeApp"))
 //}

+ 65 - 11
composeApp/build.gradle.kts → composeApp/composeApp.gradle.kts

@@ -1,3 +1,5 @@
+//<editor-fold desc="imports">----------------------------------------------------------------------
+
 import org.jetbrains.compose.ComposeExtension
 import org.jetbrains.compose.desktop.DesktopExtension
 import org.jetbrains.compose.desktop.application.dsl.TargetFormat
@@ -12,14 +14,22 @@ import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinWasmJsTargetDsl
 import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
 import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
 
+//</editor-fold>------------------------------------------------------------------------------------
+
+//<editor-fold desc="plugins">----------------------------------------------------------------------
+
 plugins {
     alias(libs.plugins.kotlin.multiplatform)
     alias(libs.plugins.android.application)
     alias(libs.plugins.compose)
     alias(libs.plugins.compose.compiler)
-//    MultiPlatformPlugin
+    alias(libs.plugins.openjfx)
 }
 
+//</editor-fold>------------------------------------------------------------------------------------
+
+//<editor-fold desc="helpers">----------------------------------------------------------------------
+
 fun NamedDomainObjectContainer<KotlinSourceSet>.getOrCreate(
     name: String
 ): KotlinSourceSet = findByName(name) ?: create(name)
@@ -68,16 +78,44 @@ fun Project.kotlinSourceSets(
     sourceSets(config)
 }
 
+//</editor-fold>------------------------------------------------------------------------------------
+
+//<editor-fold desc="configurations">---------------------------------------------------------------
+
+// java version
 setJavaLanguageVersion(libs.versions.java.language.version)
 
+// resources config
+composeResources {
+    publicResClass = true
+    packageOfResClass = "org.mjdev.desktop.resources"
+    generateResClass = always
+}
+
+javafx {
+    version = "21.0.3"
+    modules = listOf(
+        "javafx.controls",
+        "javafx.web",
+        "javafx.graphics",
+        "javafx.media"
+    )
+}
+
+//</editor-fold>------------------------------------------------------------------------------------
+
+//<editor-fold desc="targets">----------------------------------------------------------------------
+
 targets {
     desktopTarget {
     }
+
     androidTarget {
         compilerOptions {
             jvmTarget.set(JvmTarget.JVM_17)
         }
     }
+
     wasmJsTarget {
         outputModuleName = "composeApp"
         browser {
@@ -98,8 +136,13 @@ targets {
     }
 }
 
+//</editor-fold>------------------------------------------------------------------------------------
+
+//<editor-fold desc="source sets / dependencies">---------------------------------------------------
+
 kotlin {
     sourceSets {
+
         // common dependencies
         commonMain {
             dependencies {
@@ -131,12 +174,18 @@ kotlin {
                 // qr code
                 implementation(libs.qrose)
                 // json
-                implementation(libs.google.gson)
-                // desktop file linux todo move to desktop
+                implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1")
+                // desktop file linux
                 implementation(libs.ini4j)
                 // tts
                 implementation(libs.tts)
                 implementation(libs.tts.compose)
+                // notification
+                // implementation("io.github.shadmanadman:knotif:0.56.0")
+                // media player
+                implementation("io.github.shadmanadman:kmpShaPlayer:1.0.2")
+                // sensors
+                //implementation("io.github.shadmanadman:KSensor:0.59.0")
                 // flowmvi
 //                implementation(libs.flowmvi.core)
 //                implementation(libs.flowmvi.compose)
@@ -175,6 +224,7 @@ kotlin {
                 implementation("com.squareup.okio:okio:3.10.2")
             }
         }
+
         // android dependencies
         androidMain {
             dependencies {
@@ -194,8 +244,11 @@ kotlin {
                 implementation(libs.accompanist.permissions)
                 // ai gemini
                 implementation(libs.sh.google.generative.ai)
+                // sensors
+                //implementation("io.github.shadmanadman:KSensor:0.59.0")
             }
         }
+
         // wasm dependencies
         wasmJsMain {
             dependencies {
@@ -207,6 +260,7 @@ kotlin {
                 implementation(libs.sh.google.generative.ai)
             }
         }
+
         // desktop dependencies
         desktopMain {
             dependencies {
@@ -230,15 +284,13 @@ kotlin {
                 implementation(libs.ini4j)
             }
         }
+
     }
 }
 
-// resources config
-composeResources {
-    publicResClass = true
-    packageOfResClass = "org.mjdev.desktop.resources"
-    generateResClass = always
-}
+//</editor-fold>------------------------------------------------------------------------------------
+
+//<editor-fold desc="platforms">--------------------------------------------------------------------
 
 // android app config
 android {
@@ -324,9 +376,9 @@ desktop {
                 TargetFormat.Rpm
             )
             buildTypes.release.proguard {
-                configurationFiles.from("compose-desktop.pro")
+                configurationFiles.from("composeApp.pro")
             }
-//                appResourcesRootDir.set(project.rootDir.resolve("resources"))
+//            appResourcesRootDir.set(project.rootDir.resolve("resources"))
         }
     }
 }
@@ -334,3 +386,5 @@ desktop {
 // wasm config
 wasmjs {
 }
+
+//</editor-fold>------------------------------------------------------------------------------------

+ 0 - 0
composeApp/compose-desktop.pro → composeApp/composeApp.pro


+ 2 - 1
composeApp/src/androidMain/AndroidManifest.xml

@@ -48,7 +48,8 @@
         android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="true"
         android:theme="@style/Theme.SplashScreen.Splash"
-        android:usesCleartextTraffic="true">
+        android:usesCleartextTraffic="true"
+        tools:replace="android:theme">
 
         <activity
             android:name=".activity.MainActivity"

+ 28 - 1
composeApp/src/androidMain/kotlin/org/mjdev/desktop/activity/MainActivity.kt

@@ -13,7 +13,7 @@ import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.safeDrawing
 import androidx.compose.foundation.layout.windowInsetsPadding
-import androidx.compose.material.Scaffold
+import androidx.compose.material3.Scaffold
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.Modifier
@@ -34,6 +34,15 @@ import org.mjdev.desktop.extensions.ComponentActivityExt.setBackgroundColor
 @Suppress("unused")
 @OptIn(ExperimentalPermissionsApi::class)
 class MainActivity : ComponentActivity() {
+//    val sensors = listOf(
+//        SensorType.ACCELEROMETER,
+//        SensorType.GYROSCOPE,
+//        SensorType.MAGNETOMETER,
+//        SensorType.BAROMETER,
+//        SensorType.STEP_COUNTER,
+//        SensorType.LOCATION
+//    )
+
     override fun onCreate(savedInstanceState: Bundle?) {
         installSplashScreen()
         super.onCreate(savedInstanceState)
@@ -53,6 +62,24 @@ class MainActivity : ComponentActivity() {
         }
     }
 
+//    fun enableSensors() {
+//        runBlocking {
+//            KSensor.registerSensors(
+//                types = sensors,
+//                locationIntervalMillis = 1000L
+//            ).collect { sensorUpdate ->
+//                when (sensorUpdate) {
+//                    is SensorUpdate.Data -> Log.d(TAG, sensorUpdate.data.toString())
+//                    is SensorUpdate.Error -> Log.d(TAG, sensorUpdate.exception.toString())
+//                }
+//            }
+//        }
+//    }
+
+//    fun disableSensors() {
+//        KSensor.unregisterSensors(sensors)
+//    }
+
     companion object {
         val TAG = MainActivity::class.simpleName
     }

+ 24 - 8
composeApp/src/androidMain/kotlin/org/mjdev/desktop/context/DesktopContext.kt

@@ -16,19 +16,19 @@ import org.mjdev.desktop.data.Category
 import org.mjdev.desktop.data.User
 import org.mjdev.desktop.extensions.Compose.isDesign
 import org.mjdev.desktop.helpers.image.ImageLoader.asyncImageLoader
+import org.mjdev.desktop.helpers.persistence.StorageProvider
 import org.mjdev.desktop.interfaces.IApp
 import org.mjdev.desktop.managers.apps.IAppsManager
 import org.mjdev.desktop.managers.connectivity.IConnectivityManager
-import org.mjdev.desktop.interfaces.IDesktopContext
 import org.mjdev.desktop.interfaces.ILocale
-import org.mjdev.desktop.interfaces.IPalette
+import org.mjdev.desktop.managers.palette.IPalette
 import org.mjdev.desktop.managers.process.IProcessManager
 import org.mjdev.desktop.interfaces.ITheme
 import org.mjdev.desktop.interfaces.IUser
 import org.mjdev.desktop.managers.language.Translator
 import org.mjdev.desktop.log.Log
 import org.mjdev.desktop.managers.ai.AiManager
-import org.mjdev.desktop.managers.ai.base.IAiManager
+import org.mjdev.desktop.managers.ai.IAiManager
 import org.mjdev.desktop.managers.os.IOSManager
 import org.mjdev.desktop.managers.theme.IThemeManager
 import org.mjdev.desktop.managers.translations.ITranslator
@@ -60,17 +60,33 @@ class DesktopContext(
     val androidContext
         get() = context
 
+    // todo
     override var isFirstStart: Boolean = false
-    override var isInstalled: Boolean = false
+
+    override var isInstalled: Boolean = true
+
+    // todo
     override val machineName: String = ""
+
+    // todo
     override val appArgs: List<String> = emptyList()
+
+    // todo
     override val currentUser: IUser = allUsers.firstOrNull { u -> u.isLoggedIn } ?: User.DEFAULT
+
+    // todo
     override val currentLocale: ILocale = ILocale.DEFAULT
+
     override val theme: ITheme = ITheme.DEFAULT
-    override val appCategories: List<Category> = emptyList()
-    override val allApps: List<IApp> = emptyList()
-    override val favoriteApps: List<IApp> = emptyList()
-    override val isLandscapeMode: Boolean = true
+
+    override val appCategories: List<Category>
+        get() = appsManager.categories
+
+    override val allApps: List<IApp>
+        get() = appsManager.allApps
+
+    override val favoriteApps: List<IApp>
+        get() = appsManager.favoriteApps
 
     override val containerSize: DpSize = androidContext?.resources
         ?.displayMetrics

+ 0 - 1
composeApp/src/androidMain/kotlin/org/mjdev/desktop/context/LocalDesktopContext.android.kt

@@ -2,7 +2,6 @@ package org.mjdev.desktop.context
 
 import androidx.compose.runtime.ProvidableCompositionLocal
 import androidx.compose.runtime.compositionLocalOf
-import org.mjdev.desktop.interfaces.IDesktopContext
 
 actual val LocalDesktopContext: ProvidableCompositionLocal<IDesktopContext> = compositionLocalOf {
     DesktopContext()

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

@@ -9,7 +9,6 @@ import android.content.pm.LauncherActivityInfo
 import android.content.pm.PackageInfo
 import android.os.Build
 import androidx.annotation.RequiresApi
-import org.mjdev.desktop.extensions.JSonExt.toJson
 import org.mjdev.desktop.interfaces.IApp
 
 

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

@@ -7,7 +7,7 @@ import okio.Path
 import okio.Path.Companion.toPath
 import org.mjdev.desktop.context.DesktopContext
 import org.mjdev.desktop.icons.user.AccountCircle
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 import org.mjdev.desktop.interfaces.ITheme
 import org.mjdev.desktop.interfaces.IUser
 import org.mjdev.desktop.interfaces.IUserDirs

+ 1 - 1
composeApp/src/androidMain/kotlin/org/mjdev/desktop/extensions/ImageBitmapExt.kt

@@ -7,7 +7,7 @@ import androidx.compose.ui.graphics.asImageBitmap
 import coil3.request.ImageRequest
 import coil3.toBitmap
 import okio.Path
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 
 @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
 actual object ImageBitmapExt {

+ 0 - 18
composeApp/src/androidMain/kotlin/org/mjdev/desktop/extensions/JSonExt.kt

@@ -1,18 +0,0 @@
-package org.mjdev.desktop.extensions
-
-import com.google.gson.GsonBuilder
-
-object JSonExt {
-    val gson = GsonBuilder()
-        .disableInnerClassSerialization()
-        .disableJdkUnsafe()
-//        .setLenient()
-        .setPrettyPrinting()
-        .create()
-
-    fun <T : Any> T.toJson(): String =
-        gson.toJson(this)
-
-    inline fun <reified T : Any> String.fromJson(): T =
-        gson.fromJson(this, T::class.java)
-}

+ 44 - 25
composeApp/src/androidMain/kotlin/org/mjdev/desktop/helpers/persistence/StorageProvider.kt

@@ -1,36 +1,55 @@
 package org.mjdev.desktop.helpers.persistence
 
-import android.content.ContextWrapper
-import android.app.Application
-import org.mjdev.desktop.BuildConfig
+import org.mjdev.desktop.resources.Res
+import androidx.core.content.edit
+import kotlinx.coroutines.runBlocking
+import org.jetbrains.compose.resources.getString
+import org.mjdev.desktop.context.DesktopContext
+import org.mjdev.desktop.context.IDesktopContext
+import org.mjdev.desktop.resources.app_id
 
 @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
-actual object StorageProvider {
+actual open class StorageProvider actual constructor(
+    val context: IDesktopContext
+) {
+    private val applicationContext
+        get() = (context as? DesktopContext)?.androidContext?.applicationContext
+
     private val prefs by lazy {
-        ContextWrapper(Application()).getSharedPreferences(BuildConfig.APPLICATION_ID, 0)
+        runBlocking {
+            val appId = getString(Res.string.app_id)
+            applicationContext?.getSharedPreferences(appId, 0)
+        }
     }
 
-    actual val instance: KeyValueStorage = object : KeyValueStorage {
-        override fun put(
-            key: String,
-            value: String
-        ) {
-            prefs.edit().putString(key, value).apply()
-        }
+    actual open fun put(
+        key: String,
+        value: String
+    ) = prefs?.edit {
+        putString(key, value)
+    } ?: throw IllegalStateException("SharedPreferences not initialized")
 
-        override fun get(
-            key: String,
-            default: String?
-        ) = prefs.getString(key, default)
+    actual open fun get(
+        key: String,
+        default: String?
+    ) : String? = prefs?.getString(
+        key,
+        default
+    ) ?: throw IllegalStateException("SharedPreferences not initialized")
 
-        override fun remove(
-            key: String
-        ) {
-            prefs.edit().remove(key).apply()
-        }
+    actual open fun remove(
+        key: String
+    ) = prefs?.edit {
+        remove(key)
+    } ?: throw IllegalStateException("SharedPreferences not initialized")
+
+    actual open fun clear() = prefs?.edit {
+        clear()
+    } ?: throw IllegalStateException("SharedPreferences not initialized")
+
+    actual open fun getAll(): Map<String, String> =
+        prefs?.all?.mapValues { entry ->
+            entry.value?.toString() ?: ""
+        } ?: emptyMap()
 
-        override fun clear() {
-            prefs.edit().clear().apply()
-        }
-    }
 }

+ 1 - 1
composeApp/src/androidMain/kotlin/org/mjdev/desktop/helpers/system/meminfo/MemInfo.android.kt

@@ -1,5 +1,5 @@
 package org.mjdev.desktop.helpers.system.meminfo
 
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 
 actual fun MemInfo(context: IDesktopContext): MemInfoStub  = MemInfoAndroid(context)

+ 1 - 1
composeApp/src/androidMain/kotlin/org/mjdev/desktop/helpers/system/meminfo/MemInfoAndroid.kt

@@ -4,7 +4,7 @@ import android.app.ActivityManager
 import android.content.Context
 import android.content.Context.ACTIVITY_SERVICE
 import org.mjdev.desktop.context.DesktopContext
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 
 class MemInfoAndroid(
     context: IDesktopContext

+ 0 - 1
composeApp/src/androidMain/kotlin/org/mjdev/desktop/main/MainView.kt

@@ -26,7 +26,6 @@ import org.mjdev.desktop.components.tooltip.rememberTooltipState
 import org.mjdev.desktop.context.DesktopContextScope.Companion.withDesktopContext
 import org.mjdev.desktop.extensions.Compose.isDesign
 import org.mjdev.desktop.extensions.Compose.preview
-import org.mjdev.desktop.extensions.LaunchedEffect.runAsync
 import org.mjdev.desktop.extensions.MutableStateExt.rememberCalculated
 import org.mjdev.desktop.components.desktop.widgets.MemoryChart
 

+ 1 - 1
composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/ai/tts/TTSPluginAndroid.kt

@@ -14,7 +14,7 @@ import kotlinx.coroutines.launch
 import nl.marc_apps.tts.TextToSpeechEngine
 import nl.marc_apps.tts.TextToSpeechFactory
 import org.mjdev.desktop.context.DesktopContext
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 
 @Suppress("unused")
 class TTSPluginAndroid(

+ 27 - 23
composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/apps/AppsManager.kt

@@ -10,19 +10,18 @@ package org.mjdev.desktop.managers.apps
 
 import android.content.Context
 import android.content.pm.LauncherApps
-import android.os.Environment
 import android.os.UserHandle
 import android.os.UserManager
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.firstOrNull
 import kotlinx.coroutines.runBlocking
-import okio.Path.Companion.toPath
 import org.mjdev.desktop.context.DesktopContext
 import org.mjdev.desktop.data.App
+import org.mjdev.desktop.data.Category
+import org.mjdev.desktop.extensions.Compose.addIfNotExists
 import org.mjdev.desktop.extensions.MutableStateExt.mutableStateListFlow
-import org.mjdev.desktop.extensions.PathExt.filesOnly
-import org.mjdev.desktop.extensions.PathExt.sortedByName
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.interfaces.IApp
+import org.mjdev.desktop.context.IDesktopContext
 import org.mjdev.desktop.interfaces.ILocale
 import java.util.Locale
 
@@ -31,8 +30,12 @@ class AppsManager(
     private val context: IDesktopContext,
     private val scope: CoroutineScope = context.scope
 ) : IAppsManager {
-    val TAG = AppsManager::class.simpleName
+    companion object {
+        val TAG = AppsManager::class.simpleName
+    }
+
     private val androidContext = (context as? DesktopContext)?.context
+
     private val userManger = androidContext?.getSystemService(Context.USER_SERVICE) as UserManager
 
     private val launcherApps
@@ -42,14 +45,14 @@ class AppsManager(
 
     val userHandles: List<UserHandle>?
         get() = runCatching {
-            userManger.getUserProfiles()
+            userManger.userProfiles
         }.getOrNull()
 
     override val currentLocale: ILocale
         get() = Locale.getDefault().toILocale()
 
     private val allAppsState = mutableStateListFlow {
-        mutableListOf<App>().apply {
+        mutableListOf<IApp>().apply {
             userHandles?.forEach { handle ->
                 launcherApps?.getActivityList(null, handle)?.map { aInfo ->
                     App(androidContext, aInfo)
@@ -60,25 +63,26 @@ class AppsManager(
         }
     }
 
-    override val allApps: List<App>
+    override val allApps: List<IApp>
         get() = runBlocking {
             allAppsState.firstOrNull() ?: emptyList()
         }
 
-    override val categories
-        get() = allApps.asSequence().flatMap { app ->
-            app.categories
-        }.distinct().toList().sortedBy { c ->
-            c.name
-        }.sortedByDescending { c ->
-            c.priority
-        }.toList()
+    override val categories: List<Category>
+        get() = allApps
+            .asSequence()
+            .flatMap { app -> app.categories }
+            .distinct()
+            .toList()
+            .sortedBy { c -> c.name }
+            .sortedByDescending { c -> c.priority }
 
-    override val favoriteApps: List<App>
-        get() = emptyList()
-}
-
-private fun Locale.toILocale(): ILocale =
-    ILocale.from(displayCountry, displayLanguage)
+    override val favoriteApps: MutableList<IApp> = mutableListOf()
 
+    override suspend fun startApp(app: IApp) {
+        favoriteApps.addIfNotExists(app)
+        app.start()
+    }
+}
 
+private fun Locale.toILocale(): ILocale = ILocale.from(displayCountry, displayLanguage)

+ 1 - 1
composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/connectivity/ConnectivityManager.kt

@@ -1,6 +1,6 @@
 package org.mjdev.desktop.managers.connectivity
 
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 
 @Suppress("FunctionName")
 fun ConnectivityManager(

+ 1 - 1
composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/connectivity/ConnectivityManagerStub.kt

@@ -12,7 +12,7 @@ import org.mjdev.desktop.data.EthNetwork
 import org.mjdev.desktop.data.WifiNetwork
 import org.mjdev.desktop.data.BthDevice
 import org.mjdev.desktop.data.NetDevice
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 
 open class ConnectivityManagerStub(
     private val context: IDesktopContext

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

@@ -2,7 +2,7 @@ package org.mjdev.desktop.managers.keys
 
 import kotlinx.coroutines.CoroutineScope
 import org.mjdev.desktop.helpers.streams.ResourceStream
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 
 class KeysManager(
     val context: IDesktopContext,

+ 1 - 1
composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/os/OSManager.kt

@@ -9,7 +9,7 @@
 package org.mjdev.desktop.managers.os
 
 import org.mjdev.desktop.managers.os.base.OSManagerStub
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 
 @Suppress("FunctionName")
 fun OsManager(

+ 1 - 1
composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/processes/ProcessManager.kt

@@ -9,7 +9,7 @@
 package org.mjdev.desktop.managers.processes
 
 import org.mjdev.desktop.interfaces.IApp
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 import org.mjdev.desktop.interfaces.IProcessListener
 import org.mjdev.desktop.managers.process.IProcessManager
 

+ 1 - 1
composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/theme/ThemeManager.kt

@@ -9,7 +9,7 @@
 package org.mjdev.desktop.managers.theme
 
 import org.mjdev.desktop.managers.theme.base.ThemeManagerStub
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 
 // todo other platforms
 @Suppress("FunctionName")

+ 1 - 1
composeApp/src/androidMain/kotlin/org/mjdev/desktop/managers/theme/base/ThemeManagerStub.kt

@@ -8,7 +8,7 @@
 
 package org.mjdev.desktop.managers.theme.base
 
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 import org.mjdev.desktop.managers.theme.IThemeManager
 
 @Suppress("unused")

+ 1 - 1
composeApp/src/androidMain/kotlin/org/mjdev/desktop/windows/ChromeWindow.kt

@@ -12,7 +12,7 @@ import androidx.compose.ui.unit.DpOffset
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import org.mjdev.desktop.context.DesktopContextScope.Companion.withDesktopContext
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 
 @SuppressLint("UnusedBoxWithConstraintsScope")
 @Composable

+ 4 - 0
composeApp/src/commonMain/composeResources/values/strings.xml

@@ -0,0 +1,4 @@
+<resources>
+    <string name="app_name">mjdev desktop</string>
+    <string name="app_id">org.mjdev.desktop</string>
+</resources>

+ 16 - 19
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/appsmenu/AppsMenu.kt

@@ -16,7 +16,7 @@ import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Divider
+import androidx.compose.material3.HorizontalDivider
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.getValue
@@ -52,7 +52,6 @@ import org.mjdev.desktop.extensions.Compose.sortByRelevance
 import org.mjdev.desktop.extensions.Compose.trimIsNotEmpty
 import org.mjdev.desktop.extensions.LaunchedEffect.flowBlock
 import org.mjdev.desktop.extensions.LaunchedEffect.launchedEffect
-import org.mjdev.desktop.extensions.LaunchedEffect.runAsync
 import org.mjdev.desktop.helpers.animation.Animations.AppsMenuEnterAnimation
 import org.mjdev.desktop.helpers.animation.Animations.AppsMenuExitAnimation
 import org.mjdev.desktop.interfaces.IApp
@@ -66,7 +65,11 @@ fun AppsMenu(
     panelState: VisibilityState = rememberVisibilityState(),
     enterAnimation: EnterTransition = AppsMenuEnterAnimation,
     exitAnimation: ExitTransition = AppsMenuExitAnimation,
-    onAppClick: DesktopContextScope.(IApp) -> Unit = { app -> runAsync { app.start() } },
+    onAppClick: DesktopContextScope.(IApp) -> Unit = { app ->
+        runAsync {
+            appsManager.startApp(app)
+        }
+    },
     onAppContextMenuClick: DesktopContextScope.(IApp) -> Unit = {},
     onCategoryContextMenuClick: DesktopContextScope.(Category) -> Unit = {},
     onUserAvatarClick: () -> Unit = {},
@@ -172,14 +175,11 @@ fun AppsMenu(
                         onTooltip = onTooltip
                     )
                 }
-                Divider(
-                    modifier = Modifier
-                        .fillMaxWidth()
-                        .height(2.dp)
-                        .padding(
-                            start = 2.dp,
-                            end = 2.dp
-                        ),
+                HorizontalDivider(
+                    modifier = Modifier.padding(
+                        start = 2.dp,
+                        end = 2.dp
+                    ),
                     color = borderColor,
                     thickness = 2.dp
                 )
@@ -204,14 +204,11 @@ fun AppsMenu(
                     .wrapContentHeight()
                     .align(Alignment.BottomStart),
             ) {
-                Divider(
-                    modifier = Modifier
-                        .fillMaxWidth()
-                        .height(2.dp)
-                        .padding(
-                            start = 2.dp,
-                            end = 2.dp
-                        ),
+                HorizontalDivider(
+                    modifier = Modifier.padding(
+                        start = 2.dp,
+                        end = 2.dp
+                    ),
                     color = borderColor,
                     thickness = 2.dp
                 )

+ 0 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/appsmenu/components/AppsList.kt

@@ -26,7 +26,6 @@ import org.mjdev.desktop.extensions.Compose.preview
 import org.mjdev.desktop.helpers.compose.rememberForeverLazyListState
 import org.mjdev.desktop.context.DesktopContextScope
 import org.mjdev.desktop.context.DesktopContextScope.Companion.withDesktopContext
-import org.mjdev.desktop.extensions.LaunchedEffect.runAsync
 import org.mjdev.desktop.interfaces.IApp
 import org.jetbrains.compose.ui.tooling.preview.Preview
 

+ 9 - 9
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/appsmenu/components/AppsMenuActions.kt

@@ -54,9 +54,9 @@ fun AppsMenuActions(
             onClick = {
                 onActionClick()
                 // todo : dialog
-//                runAsync(Dispatchers.Default) {
-//                    context.restart()
-//                }
+                runAsync {
+                    context.restart()
+                }
             }
         )
         ShapedIcon(
@@ -68,9 +68,9 @@ fun AppsMenuActions(
             onClick = {
                 onActionClick()
                 // todo : dialog
-//                runAsync(Dispatchers.Default) {
-//                    context.suspend()
-//                }
+                runAsync {
+                    context.suspend()
+                }
             }
         )
         ShapedIcon(
@@ -82,9 +82,9 @@ fun AppsMenuActions(
             onClick = {
                 onActionClick()
                 // todo : dialog
-//                runAsync(Dispatchers.Default) {
-//                    context.logOut()
-//                }
+                runAsync {
+                    context.logOut()
+                }
             }
         )
     }

+ 6 - 8
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/appsmenu/components/AppsMenuApp.kt

@@ -5,10 +5,9 @@ import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Divider
+import androidx.compose.material3.HorizontalDivider
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
@@ -25,7 +24,6 @@ import org.mjdev.desktop.components.text.TextAny
 import org.mjdev.desktop.context.DesktopContextScope.Companion.withDesktopContext
 import org.mjdev.desktop.extensions.Colors.SuperDarkGray
 import org.mjdev.desktop.extensions.Compose.preview
-import org.mjdev.desktop.extensions.LaunchedEffect.runAsync
 import org.mjdev.desktop.extensions.Modifier.circleBorder
 import org.mjdev.desktop.extensions.Modifier.onLeftClick
 import org.mjdev.desktop.extensions.Modifier.onMousePress
@@ -47,7 +45,7 @@ fun AppsMenuApp(
     dividerPadding: Dp = 48.dp,
     dividerColor: Color = Color.Black,
     onContextMenuClick: () -> Unit = {},
-    onClick: () -> Unit = { runAsync { app?.start() } },
+    onClick: () -> Unit = {},
     onTooltip: (item: Any?) -> Unit = {}
 ) = withDesktopContext {
     val appIconName = remember(app, icon) { app?.name ?: icon }
@@ -90,15 +88,15 @@ fun AppsMenuApp(
             }
         }
         if (showDivider) {
-            Divider(
-                modifier = Modifier.fillMaxWidth()
+            HorizontalDivider(
+                modifier = Modifier
                     .padding(start = dividerPadding)
-                    .height(1.dp)
                     .background(
                         color = dividerColor,
                         shape = DottedShape(5.dp)
                     ),
-                color = Color.Transparent
+                color = Color.Transparent,
+                thickness = 1.dp
             )
         }
     }

+ 1 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/card/PhotoCard.kt

@@ -24,7 +24,7 @@ import org.mjdev.desktop.extensions.FocusState.rememberFocusRequester
 import org.mjdev.desktop.extensions.FocusState.rememberFocusState
 import org.mjdev.desktop.helpers.compose.FocusHelper
 import org.mjdev.desktop.components.image.PhotoImage
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 import org.jetbrains.compose.ui.tooling.preview.Preview
 
 @Composable

+ 14 - 22
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/ControlCenter.kt

@@ -17,8 +17,8 @@ import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.itemsIndexed
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Divider
-import androidx.compose.material.Icon
+import androidx.compose.material3.Icon
+import androidx.compose.material3.VerticalDivider
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.getValue
@@ -34,7 +34,6 @@ import org.mjdev.desktop.components.sliding.base.VisibilityState
 import org.mjdev.desktop.components.sliding.base.VisibilityState.Companion.rememberVisibilityState
 import org.mjdev.desktop.context.DesktopContextScope.Companion.withDesktopContext
 import org.mjdev.desktop.extensions.Compose.orElse
-import org.mjdev.desktop.extensions.LaunchedEffect.runAsync
 import org.mjdev.desktop.extensions.Modifier.onLeftClick
 import org.mjdev.desktop.extensions.Modifier.onMouseEnter
 import org.mjdev.desktop.extensions.Modifier.onMousePress
@@ -79,11 +78,9 @@ fun ControlCenter(
         }
     ) { isVisible ->
         if (!isVisible) {
-            Divider(
-                modifier = Modifier.fillMaxHeight()
-                    .width(controlCenterDividerWidth),
-                color = if (isDebug) Color.Red else Color.Transparent,
-                thickness = controlCenterDividerWidth
+            VerticalDivider(
+                thickness = controlCenterDividerWidth,
+                color = if (isDebug) Color.Red else Color.Transparent
             )
         } else {
             BlurPanel(
@@ -159,11 +156,9 @@ fun ControlCenter(
                             Row(
                                 Modifier.fillMaxHeight()
                             ) {
-                                Divider(
-                                    modifier = Modifier.fillMaxHeight()
-                                        .width(2.dp),
-                                    color = borderColor,
-                                    thickness = 2.dp
+                                VerticalDivider(
+                                    thickness = 2.dp,
+                                    color = borderColor
                                 )
                                 Column(
                                     modifier = Modifier.width(controlCenterExpandedWidth),
@@ -173,18 +168,15 @@ fun ControlCenter(
                                         page.Render()
                                     }
                                 }
-                                Divider(
-                                    modifier = Modifier.fillMaxHeight()
-                                        .width(2.dp),
-                                    color = borderColor,
-                                    thickness = 2.dp
+                                VerticalDivider(
+                                    thickness = 2.dp,
+                                    color = borderColor
                                 )
                             }
                         }
-                        Divider(
-                            modifier = Modifier.fillMaxHeight().width(2.dp),
-                            color = Color.White.copy(0.1f),
-                            thickness = 2.dp
+                        VerticalDivider(
+                            thickness = 2.dp,
+                            color = Color.White.copy(0.1f)
                         )
                     }
                 }

+ 16 - 9
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/base/ControlCenterPage.kt

@@ -6,31 +6,32 @@ import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Settings
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.vector.ImageVector
 import org.mjdev.desktop.components.controlcenter.base.ControlCenterPageScope.Companion.withControlCenterPageScope
 import org.mjdev.desktop.interfaces.IControlCenterPageDataSaver
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 import org.mjdev.desktop.interfaces.IPage
 import org.jetbrains.compose.ui.tooling.preview.Preview
 
 @Suppress("MemberVisibilityCanBePrivate")
 class ControlCenterPage(
+    override val context: IDesktopContext,
     override val icon: ImageVector = Icons.Filled.Settings,
     override val name: String = "",
     override val condition: IDesktopContext.() -> Boolean = { true },
-    override val saver: IControlCenterPageDataSaver? = null,
-    val cache: PageCache = PageCache(saver),
-    val showHeader: Boolean = true,
-//    val scrollState: ScrollState = ScrollState(0),
-    val content: @Composable ControlCenterPageScope.() -> Unit = {},
+    override val saver: (context: IDesktopContext) -> IControlCenterPageDataSaver? = { null },
+    override val cache: PageCache = PageCache(saver(context)),
+    override val showHeader: Boolean = true,
+    override val content: @Composable ControlCenterPageScope.() -> Unit = {},
 ) : IPage {
+    //    val scrollState: ScrollState = ScrollState(0),
+
     @Preview
     @Composable
     override fun Render() {
-        withControlCenterPageScope(
-            cache = cache
-        ) {
+        withControlCenterPageScope(cache) {
             Column(
                 modifier = Modifier.fillMaxSize()
             ) {
@@ -47,6 +48,12 @@ class ControlCenterPage(
 //                adapter = rememberScrollbarAdapter(scrollState)
 //            )
             }
+            DisposableEffect(Unit) {
+                cache.load()
+                onDispose {
+                    cache.save()
+                }
+            }
         }
     }
 

+ 14 - 7
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/base/ControlCenterPageScope.kt

@@ -2,29 +2,36 @@ package org.mjdev.desktop.components.controlcenter.base
 
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisallowComposableCalls
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.remember
 import org.mjdev.desktop.context.DesktopContextScope
 import org.mjdev.desktop.context.LocalDesktopContext
-import org.mjdev.desktop.interfaces.IDesktopContext
-import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.mjdev.desktop.context.IDesktopContext
 
+@Suppress("UnusedReceiverParameter", "ComposableNaming")
 class ControlCenterPageScope(
     context: IDesktopContext,
-    val cache: PageCache
+    private val cache: PageCache
 ) : DesktopContextScope(context) {
     companion object {
-        @Suppress("UnusedReceiverParameter")
         @Composable
         fun ControlCenterPage.withControlCenterPageScope(
-            context: IDesktopContext = LocalDesktopContext.current,
             cache: PageCache,
+            context: IDesktopContext = LocalDesktopContext.current,
             block: @Composable ControlCenterPageScope.() -> Unit
-        ) = ControlCenterPageScope(context, cache).apply {
-            block()
+        ) {
+            block(ControlCenterPageScope(context, cache))
         }
 
         @Composable
         fun <T> ControlCenterPageScope.remember(
             calculation: @DisallowComposableCalls () -> T
         ): T = cache.cache(false, calculation)
+
+        @Composable
+        fun <T> ControlCenterPageScope.rememberComputed(
+            vararg key: Any?,
+            calculation: () -> T
+        ) = remember(*key) { derivedStateOf { calculation() } }
     }
 }

+ 5 - 5
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/base/PageCache.kt

@@ -2,16 +2,16 @@ package org.mjdev.desktop.components.controlcenter.base
 
 import androidx.compose.runtime.DisallowComposableCalls
 import org.mjdev.desktop.interfaces.IControlCenterPageDataSaver
-import org.jetbrains.compose.ui.tooling.preview.Preview
 
-@Suppress("unused")
 class PageCache(
     private val saver: IControlCenterPageDataSaver? = null
 ) {
-    private val _data : MutableMap<Int, Any> = mutableMapOf()
+    private val _data: MutableMap<Int, Any> = mutableMapOf()
 
-    init {
-        saver?.load()?.onEach { (t, u) -> _data[t] = u }
+    fun load() {
+        saver?.load()?.onEach { (t, u) ->
+            _data[t] = u
+        }
     }
 
     fun save() {

+ 3 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/base/PageHeader.kt

@@ -30,11 +30,12 @@ import org.mjdev.desktop.extensions.Modifier.circleBorder
 import org.mjdev.desktop.extensions.Modifier.clipCircle
 import org.mjdev.desktop.context.DesktopContextScope.Companion.withDesktopContext
 import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.mjdev.desktop.context.LocalDesktopContext
 
 @Composable
 fun PageHeader(
     modifier: Modifier = Modifier,
-    page: ControlCenterPage = ControlCenterPage(),
+    page: ControlCenterPage = ControlCenterPage(LocalDesktopContext.current),
     contentPadding: PaddingValues = PaddingValues(8.dp),
     iconSize: Dp = 64.dp,
     actions: @Composable RowScope.() -> Unit = {},
@@ -76,6 +77,6 @@ fun PageHeader(
 @Composable
 fun PageHeaderPreview() = preview {
     PageHeader(
-        page = ControlCenterPage()
+        page = ControlCenterPage(context)
     )
 }

+ 30 - 0
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/base/PersistentPageSaver.kt

@@ -0,0 +1,30 @@
+package org.mjdev.desktop.components.controlcenter.base
+
+import org.mjdev.desktop.extensions.CustomExt.asJson
+import org.mjdev.desktop.extensions.CustomExt.to
+import org.mjdev.desktop.interfaces.IControlCenterPageDataSaver
+import org.mjdev.desktop.context.IDesktopContext
+import org.mjdev.desktop.log.Log
+
+class PersistentPageSaver(
+    val context: IDesktopContext,
+    override val key: String,
+) : IControlCenterPageDataSaver {
+    override fun save(data: Map<Int, Any>) {
+        Log.d("Saving data for $key: $data")
+        context.storageProvider.put(key, data.asJson())
+    }
+
+    override fun load(): Map<Int, Any> = runCatching {
+        Log.d("Loading data for $key")
+        context.storageProvider.get(key)?.to<Map<Int, Any>>()
+    }.onFailure { e ->
+        Log.e(e)
+    }.getOrNull() ?: emptyMap<Int, Any>().also { data ->
+        if (data.isEmpty()) {
+            Log.d("No data found for $key, returning empty map.")
+        } else {
+            Log.d("Loaded data for $key: $data.")
+        }
+    }
+}

+ 6 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/about/AboutPage.kt

@@ -8,9 +8,13 @@ import org.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 import org.mjdev.desktop.extensions.Compose.preview
 import org.mjdev.desktop.icons.custom.Mjdev
 import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.mjdev.desktop.context.IDesktopContext
 
 @Suppress("FunctionName")
-fun AboutPage() = ControlCenterPage(
+fun AboutPage(
+    context: IDesktopContext
+) = ControlCenterPage(
+    context = context,
     icon = Mjdev,
     name = "About",
     condition = { true }
@@ -23,5 +27,5 @@ fun AboutPage() = ControlCenterPage(
 @Preview
 @Composable
 fun AboutPagePreview() = preview {
-    AboutPage().Render()
+    AboutPage(context).Render()
 }

+ 19 - 17
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/ai/AIPage.kt

@@ -9,7 +9,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Icon
+import androidx.compose.material3.Icon
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.runtime.mutableStateOf
@@ -42,17 +42,24 @@ import org.mjdev.desktop.helpers.compose.rememberForeverLazyListState
 import org.mjdev.desktop.icons.chat.Chat
 import org.mjdev.desktop.icons.system.ContentCopy
 import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.mjdev.desktop.components.controlcenter.base.PersistentPageSaver
+import org.mjdev.desktop.context.IDesktopContext
 
 @Suppress("FunctionName")
-fun AIPage() = ControlCenterPage(
+fun AIPage(
+    context: IDesktopContext,
+) = ControlCenterPage(
+    context = context,
     icon = Chat,
     name = "Assistant",
     condition = {
-         ai.isAvailable()
+        ai.isAvailable()
+    },
+    saver = { ctx ->
+        PersistentPageSaver(ctx, "Assistant") // todo name
     }
 ) {
     val scrollState = rememberForeverLazyListState("Assistant")
-    val clipboardManager = LocalClipboardManager.current
     val questionsList = remember { mutableStateListOf<Pair<String, String>>() }
     val request = remember { mutableStateOf("") }
     val onDone: () -> Unit = {
@@ -110,14 +117,12 @@ fun AIPage() = ControlCenterPage(
                     ) {
                         TimeLineItem(
                             idx = idx,
-                            item = item,
-                            clipboardManager = clipboardManager
+                            item = item
                         )
                     }
                 }
                 TextArea(
                     modifier = Modifier.padding(top = 8.dp).fillMaxWidth(),
-                    clipboardManager = clipboardManager,
                     textState = request,
                     onDone = onDone
                 )
@@ -129,8 +134,7 @@ fun AIPage() = ControlCenterPage(
 @Composable
 fun TimeLineItem(
     idx: Int,
-    item: Pair<String, String>,
-    clipboardManager: ClipboardManager
+    item: Pair<String, String>
 ) {
     Column(
         modifier = Modifier.padding(4.dp)
@@ -142,12 +146,10 @@ fun TimeLineItem(
     ) {
         TextBlock(
             idx = idx,
-            text = item.first,
-            clipboardManager = clipboardManager
+            text = item.first
         )
         TextBlock(
-            text = item.second,
-            clipboardManager = clipboardManager
+            text = item.second
         )
     }
 }
@@ -155,9 +157,9 @@ fun TimeLineItem(
 @Composable
 fun TextBlock(
     idx: Int = -1,
-    text: String = "",
-    clipboardManager: ClipboardManager = LocalClipboardManager.current
+    text: String = ""
 ) {
+    val clipboard: ClipboardManager = LocalClipboardManager.current
     Box(
         Modifier.padding(4.dp)
             .fillMaxWidth()
@@ -180,7 +182,7 @@ fun TextBlock(
                 .clipCircle()
                 .align(Alignment.TopEnd),
             onClick = {
-                clipboardManager.setText(AnnotatedString(text))
+                clipboard.setText(AnnotatedString(text))
             },
         ) {
             Icon(
@@ -196,5 +198,5 @@ fun TextBlock(
 @Preview
 @Composable
 fun AIPagePreview() = preview {
-    AIPage().Render()
+    AIPage(context).Render()
 }

+ 6 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/bluetooth/BluetoothSettingsPage.kt

@@ -8,9 +8,13 @@ import org.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 import org.mjdev.desktop.extensions.Compose.preview
 import org.mjdev.desktop.icons.network.Bluetooth
 import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.mjdev.desktop.context.IDesktopContext
 
 @Suppress("FunctionName")
-fun BluetoothSettingsPage() = ControlCenterPage(
+fun BluetoothSettingsPage(
+    context: IDesktopContext
+) = ControlCenterPage(
+    context = context,
     icon = Bluetooth,
     name = "Bluetooth",
     condition = {
@@ -26,5 +30,5 @@ fun BluetoothSettingsPage() = ControlCenterPage(
 @Preview
 @Composable
 fun BluetoothSettingsPagePreview() = preview {
-    BluetoothSettingsPage().Render()
+    BluetoothSettingsPage(context).Render()
 }

+ 6 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/devices/Devices.kt

@@ -8,9 +8,13 @@ import org.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 import org.mjdev.desktop.extensions.Compose.preview
 import org.mjdev.desktop.icons.mobile.MobileFriendly
 import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.mjdev.desktop.context.IDesktopContext
 
 @Suppress("FunctionName")
-fun DevicesPage() = ControlCenterPage(
+fun DevicesPage(
+    context: IDesktopContext
+) = ControlCenterPage(
+    context = context,
     icon = MobileFriendly,
     name = "Connected devices",
     condition = {
@@ -28,5 +32,5 @@ fun DevicesPage() = ControlCenterPage(
 @Preview
 @Composable
 fun DevicesPagePreview() = preview {
-    DevicesPage().Render()
+    DevicesPage(context).Render()
 }

+ 6 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/display/DisplaySettingsPage.kt

@@ -8,9 +8,13 @@ import org.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 import org.mjdev.desktop.extensions.Compose.preview
 import org.mjdev.desktop.icons.settings.SettingsMonitor
 import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.mjdev.desktop.context.IDesktopContext
 
 @Suppress("FunctionName")
-fun DisplaySettingsPage() = ControlCenterPage(
+fun DisplaySettingsPage(
+    context: IDesktopContext
+) = ControlCenterPage(
+    context = context,
     icon = SettingsMonitor,
     name = "Display"
 ) {
@@ -22,5 +26,5 @@ fun DisplaySettingsPage() = ControlCenterPage(
 @Preview
 @Composable
 fun DisplaySettingsPagePreview() = preview {
-    DisplaySettingsPage().Render()
+    DisplaySettingsPage(context).Render()
 }

+ 6 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/ethernet/EthSettingsPage.kt

@@ -8,9 +8,13 @@ import org.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 import org.mjdev.desktop.extensions.Compose.preview
 import org.mjdev.desktop.icons.settings.SettingsEthernet
 import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.mjdev.desktop.context.IDesktopContext
 
 @Suppress("FunctionName")
-fun EthSettingsPage() = ControlCenterPage(
+fun EthSettingsPage(
+    context: IDesktopContext
+) = ControlCenterPage(
+    context = context,
     icon = SettingsEthernet,
     name = "Ethernet",
     condition = {
@@ -26,5 +30,5 @@ fun EthSettingsPage() = ControlCenterPage(
 @Preview
 @Composable
 fun EthSettingsPagePreview() = preview {
-    EthSettingsPage().Render()
+    EthSettingsPage(context).Render()
 }

+ 8 - 7
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/main/MainSettingsPage.kt

@@ -2,7 +2,7 @@ package org.mjdev.desktop.components.controlcenter.pages.main
 
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.*
-import androidx.compose.material.Divider
+import androidx.compose.material3.HorizontalDivider
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -18,14 +18,17 @@ import org.mjdev.desktop.components.user.UserAvatar
 import org.mjdev.desktop.extensions.Colors.alpha
 import org.mjdev.desktop.extensions.Colors.darker
 import org.mjdev.desktop.extensions.Compose.preview
-import org.mjdev.desktop.extensions.LaunchedEffect.runAsync
 import org.mjdev.desktop.helpers.compose.Orientation
 import org.mjdev.desktop.helpers.shape.BarShape
 import org.mjdev.desktop.icons.settings.SettingsHome
 import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.mjdev.desktop.context.IDesktopContext
 
 @Suppress("FunctionName")
-fun MainSettingsPage() = ControlCenterPage(
+fun MainSettingsPage(
+    context: IDesktopContext
+) = ControlCenterPage(
+    context = context,
     icon = SettingsHome,
     name = "Home",
     showHeader = false
@@ -80,9 +83,7 @@ fun MainSettingsPage() = ControlCenterPage(
                     orientation = Orientation.Horizontal
                 )
             }
-            Divider(
-                modifier = Modifier.fillMaxWidth()
-                    .height(2.dp),
+            HorizontalDivider(
                 color = borderColor.alpha(0.5f),
                 thickness = 2.dp
             )
@@ -143,5 +144,5 @@ fun MainSettingsPage() = ControlCenterPage(
 @Preview
 @Composable
 fun MainSettingsPagePreview() = preview {
-    MainSettingsPage().Render()
+    MainSettingsPage(context).Render()
 }

+ 6 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/remotes/RemotesSettingsPage.kt

@@ -8,9 +8,13 @@ import org.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 import org.mjdev.desktop.extensions.Compose.preview
 import org.mjdev.desktop.icons.custom.Mjdev
 import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.mjdev.desktop.context.IDesktopContext
 
 @Suppress("FunctionName")
-fun RemotesSettingsPage() = ControlCenterPage(
+fun RemotesSettingsPage(
+    context: IDesktopContext
+) = ControlCenterPage(
+    context = context,
     icon = Mjdev, // todo
     name = "Remote",
     condition = { true }
@@ -23,5 +27,5 @@ fun RemotesSettingsPage() = ControlCenterPage(
 @Preview
 @Composable
 fun RemotesSettingsPagePreview() = preview {
-    RemotesSettingsPage().Render()
+    RemotesSettingsPage(context).Render()
 }

+ 6 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/sound/SoundSettingsPage.kt

@@ -8,9 +8,13 @@ import org.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 import org.mjdev.desktop.extensions.Compose.preview
 import org.mjdev.desktop.icons.settings.SettingsSound
 import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.mjdev.desktop.context.IDesktopContext
 
 @Suppress("FunctionName")
-fun SoundSettingsPage() = ControlCenterPage(
+fun SoundSettingsPage(
+    context: IDesktopContext
+) = ControlCenterPage(
+    context = context,
     icon = SettingsSound,
     name = "Sound",
     condition = { true } // todo sound manager
@@ -23,5 +27,5 @@ fun SoundSettingsPage() = ControlCenterPage(
 @Preview
 @Composable
 fun SoundSettingsPagePreview() = preview {
-    SoundSettingsPage().Render()
+    SoundSettingsPage(context).Render()
 }

+ 6 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/theme/ThemeSettingsPage.kt

@@ -8,9 +8,13 @@ import org.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 import org.mjdev.desktop.extensions.Compose.preview
 import org.mjdev.desktop.icons.custom.Mjdev
 import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.mjdev.desktop.context.IDesktopContext
 
 @Suppress("FunctionName")
-fun ThemeSettingsPage() = ControlCenterPage(
+fun ThemeSettingsPage(
+    context: IDesktopContext
+) = ControlCenterPage(
+    context = context,
     icon = Mjdev, // todo
     name = "Theme",
     condition = { true }
@@ -23,5 +27,5 @@ fun ThemeSettingsPage() = ControlCenterPage(
 @Preview
 @Composable
 fun ThemeSettingsPagePreview() = preview {
-    ThemeSettingsPage().Render()
+    ThemeSettingsPage(context).Render()
 }

+ 25 - 11
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/controlcenter/pages/wifi/WifiSettingsPage.kt

@@ -15,18 +15,25 @@ import org.mjdev.desktop.data.WifiNetwork
 import org.mjdev.desktop.extensions.Compose.preview
 import org.mjdev.desktop.extensions.LaunchedEffect.flowBlock
 
-import org.mjdev.desktop.extensions.MutableStateExt.rememberComputed
-
 import org.mjdev.desktop.icons.settings.SettingsWifi
 import org.mjdev.desktop.log.Log
 import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.mjdev.desktop.components.controlcenter.base.ControlCenterPageScope.Companion.rememberComputed
+import org.mjdev.desktop.components.controlcenter.base.PersistentPageSaver
+import org.mjdev.desktop.context.IDesktopContext
 
 @Suppress("FunctionName")
-fun WifiSettingsPage() = ControlCenterPage(
+fun WifiSettingsPage(
+    context: IDesktopContext
+) = ControlCenterPage(
+    context = context,
     icon = SettingsWifi,
     name = "Wifi",
     condition = {
-        connectionManager?.isWifiAdapterAvailable ?: false
+        connectionManager.isWifiAdapterAvailable
+    },
+    saver = { ctx ->
+        PersistentPageSaver(ctx, "Assistant")
     }
 ) {
     val shape = RoundedCornerShape(8.dp)
@@ -34,19 +41,26 @@ fun WifiSettingsPage() = ControlCenterPage(
     val wifiList: Map<String, WifiNetwork> by flowBlock(
         emptyMap(),
         250L
-    ) { context.connectionManager?.wifiNetworks ?: emptyMap() }
+    ) { context.connectionManager.wifiNetworks }
     val items: List<WifiNetwork> by rememberComputed(isConnecting) {
-        if (wifiList.isEmpty()) emptyList() else wifiList.map { e -> e.value }
+        if (wifiList.isEmpty()) emptyList()
+        else wifiList.map { e -> e.value }
     }
     val connect: (item: WifiNetwork) -> Unit = remember {
         { wn ->
             isConnecting = true
-            context.connectionManager?.connectWifi(wn.ssid)?.onFailure { e ->
-                Log.e(e)
-            }
-            isConnecting = false
+            context.connectionManager.connectWifi(wn.ssid)
+                .onSuccess {
+                    Log.d("Connected to ${wn.ssid}")
+                    isConnecting = false
+                }
+                .onFailure { e ->
+                    Log.e(e)
+                    isConnecting = false
+                }
         }
     }
+
     ExpandableLazyColumn(
         modifier = Modifier.fillMaxWidth(),
         items = items
@@ -79,5 +93,5 @@ fun WifiSettingsPage() = ControlCenterPage(
 @Preview
 @Composable
 fun WifiSettingsPagePreview() = preview {
-    WifiSettingsPage().Render()
+    WifiSettingsPage(context).Render()
 }

+ 7 - 15
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/custom/Clock.kt

@@ -1,6 +1,5 @@
 package org.mjdev.desktop.components.custom
 
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.runtime.Composable
@@ -21,13 +20,11 @@ import org.mjdev.desktop.extensions.CustomExt.dateFlow
 import org.mjdev.desktop.extensions.CustomExt.timeFlow
 import org.mjdev.desktop.extensions.MutableStateExt.rememberState
 import org.mjdev.desktop.context.DesktopContextScope.Companion.withDesktopContext
-import org.mjdev.desktop.extensions.LaunchedEffect.runAsync
 import org.mjdev.desktop.extensions.Modifier.onMousePress
 import org.mjdev.desktop.helpers.parsers.Parsers.ParsedList
 import org.jetbrains.compose.ui.tooling.preview.Preview
 
 // todo remove params
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 fun Clock(
     modifier: Modifier = Modifier,
@@ -45,20 +42,15 @@ fun Clock(
     val time = timeFlow.value
     var lastTalk by rememberState("")
     val talk: () -> Unit = {
-        runAsync {
-            lastTalk = time
-            val parsed = ParsedList(time, ":")
-//            ai.talk(
-//                "It is ${parsed.firstOrNull()} hour, ${
-//                    parsed.getOrNull(1)?.let { "$it minutes" }.orEmpty()
-//                }, ${
-//                    parsed.getOrNull(2)?.let { "$it seconds" }.orEmpty()
-//                }"
-//            )
-        }
+        lastTalk = time
+        ai.ask("Whats current time?")
     }
     Box(
-        modifier = modifier.onMousePress { if (talkOnClick) talk() },
+        modifier = modifier.onMousePress {
+            if (talkOnClick) {
+                talk()
+            }
+        },
         contentAlignment = Alignment.Center
     ) {
         Column(

+ 1 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/custom/VerticalScrollableBox.kt

@@ -15,7 +15,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import org.mjdev.desktop.extensions.Compose.isLandscapeMode
 import org.mjdev.desktop.extensions.Compose.preview
 import org.mjdev.desktop.extensions.Compose.verticalTouchScrollable
 import org.jetbrains.compose.ui.tooling.preview.Preview
@@ -24,7 +23,7 @@ import org.jetbrains.compose.ui.tooling.preview.Preview
 fun VerticalScrollableBox(
     modifier: Modifier = Modifier,
     contentAlignment: Alignment = Alignment.TopStart,
-    propagateMinConstraints: Boolean = isLandscapeMode(),
+    propagateMinConstraints: Boolean = false, // isLandscapeMode(),
     state: LazyListState = rememberLazyListState(),
     content: @Composable BoxScope.() -> Unit = {}
 ) = Box(

+ 2 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/desktoppanel/DesktopPanel.kt

@@ -41,7 +41,6 @@ import androidx.compose.ui.unit.DpSize
 import org.mjdev.desktop.components.sliding.base.VisibilityState
 import org.mjdev.desktop.components.sliding.base.VisibilityState.Companion.rememberVisibilityState
 import org.mjdev.desktop.context.DesktopContextScope
-import org.mjdev.desktop.extensions.LaunchedEffect.runAsync
 import org.mjdev.desktop.extensions.MutableStateExt.rememberComputed
 import org.mjdev.desktop.extensions.PaddingValues.height
 import org.mjdev.desktop.helpers.animation.Animations.DesktopPanelEnterAnimation
@@ -55,6 +54,7 @@ fun DesktopPanel(
     iconSize: DpSize = DpSize(56.dp, 56.dp),
     iconPadding: PaddingValues = PaddingValues(2.dp),
     iconOuterPadding: PaddingValues = PaddingValues(2.dp),
+    iconSpace: Dp = 2.dp,
     showMenuIcon: Boolean = true,
     panelState: VisibilityState = rememberVisibilityState(),
     enterAnimation: EnterTransition = DesktopPanelEnterAnimation,
@@ -184,6 +184,7 @@ fun DesktopPanel(
                                         iconBackgroundColor = iconsTintColor,
                                         iconColorRunning = iconsTintColor.lighter(0.3f),
                                         iconSize = iconSize,
+                                        iconSpace = iconSpace,
                                         iconPadding = iconPadding,
                                         iconOuterPadding = iconOuterPadding,
                                         onTooltip = onTooltip,

+ 12 - 3
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/desktoppanel/applets/DesktopPanelFavoriteApps.kt

@@ -5,12 +5,14 @@ import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.material3.VerticalDivider
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 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.context.DesktopContextScope.Companion.withDesktopContext
@@ -29,9 +31,10 @@ fun DesktopPanelFavoriteApps(
     iconSize: DpSize = DpSize(56.dp, 56.dp),
     iconPadding: PaddingValues = PaddingValues(4.dp),
     iconOuterPadding: PaddingValues = PaddingValues(2.dp),
+    iconSpace: Dp = 2.dp,
     onTooltip: (item: Any?) -> Unit = {},
     onAppClick: (app: IApp) -> Unit = {},
-    onContextMenuClick: (app: IApp) -> Unit = {}
+    onContextMenuClick: (app: IApp) -> Unit = {},
 ) = withDesktopContext {
     val apps: List<IApp> by flowBlock(
         emptyList(),
@@ -45,7 +48,7 @@ fun DesktopPanelFavoriteApps(
             horizontalArrangement = Arrangement.Start,
             verticalAlignment = Alignment.CenterVertically
         ) {
-            items(apps) { app ->
+            itemsIndexed(apps) { idx, app ->
                 DesktopPanelIcon(
                     app = app,
                     iconColor = iconColor,
@@ -61,6 +64,12 @@ fun DesktopPanelFavoriteApps(
                     },
                     onContextMenuClick = { onContextMenuClick(app) }
                 )
+                if (idx < apps.size - 1) {
+                    VerticalDivider(
+                        color = Color.Transparent,
+                        thickness = iconSpace
+                    )
+                }
             }
         }
     }

+ 0 - 5
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/file/FolderView.kt

@@ -2,17 +2,14 @@ package org.mjdev.desktop.components.file
 
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clipToBounds
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
@@ -31,8 +28,6 @@ import org.mjdev.desktop.extensions.PathExt.cwd
 import org.mjdev.desktop.extensions.PathExt.isDirectory
 import org.mjdev.desktop.extensions.PathExt.listFiles
 import org.mjdev.desktop.context.DesktopContextScope.Companion.withDesktopContext
-import org.mjdev.desktop.extensions.LaunchedEffect.runAsync
-import org.mjdev.desktop.extensions.Modifier.clipCircle
 import kotlin.math.max
 import org.jetbrains.compose.ui.tooling.preview.Preview
 

+ 18 - 21
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/image/ImageAny.kt

@@ -16,11 +16,14 @@ import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.layout.ContentScale
 import coil3.ImageLoader
 import coil3.compose.AsyncImage
+import okio.Path
+import org.adman.kmp.player.KmpShaPlayer
 import org.mjdev.desktop.extensions.Compose.preview
 import org.mjdev.desktop.helpers.image.ImageLoader.asyncImageLoader
 import org.mjdev.desktop.icons.image.BrokenImage
 import androidx.compose.foundation.Image as ComposeImage
 import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.mjdev.desktop.extensions.PathExt.absolutePath
 
 @Suppress("FunctionName", "UNUSED_PARAMETER")
 @Composable
@@ -34,12 +37,12 @@ fun ImageAny(
     colorFilter: ColorFilter? = null,
     filterQuality: FilterQuality = DefaultFilterQuality,
     // todo
-    imageLoader:ImageLoader? = asyncImageLoader(),
+    imageLoader: ImageLoader? = asyncImageLoader(),
     placeholder: @Composable () -> Unit = {}, // todo
     onLoading: () -> Unit = {},
     onFail: (error: Throwable) -> Unit = {},
     onAnimationFinish: () -> Unit = {}
-)  {
+) {
     // todo from mime
     val isGif = src.toString().trim().endsWith(".gif")
     // todo from mime
@@ -110,19 +113,17 @@ fun ImageAny(
 //            onAnimationFinish = onAnimationFinish
 //        )
 
-//        isVideo -> VideoView(
-//            modifier = modifier,
-//            src = when {
-//                src is File -> src.absolutePath
-//                else -> src.toString()
-//            },
-//            alignment = alignment,
-//            alpha = alpha,
-//            contentDescription = contentDescription,
-//            colorFilter = colorFilter,
-//            contentScale = contentScale,
-//            onVideoFinish = onAnimationFinish
-//        )
+        isVideo -> KmpShaPlayer(
+            modifier = modifier,
+            urlOrPathToFile = when {
+                src is Path -> src.absolutePath
+                else -> src.toString()
+            },
+            isLandscape = true,
+            stop = false,
+            onLoading = { onLoading() },
+            onError = onFail
+        )
 
         else -> AsyncImage(
             modifier = modifier,
@@ -131,12 +132,8 @@ fun ImageAny(
             contentScale = contentScale,
             filterQuality = filterQuality,
             imageLoader = imageLoader ?: asyncImageLoader(),
-            onLoading = {
-                onLoading()
-            },
-            onError = {
-                onFail(it.result.throwable)
-            },
+            onLoading = { onLoading() },
+            onError = { e -> onFail(e.result.throwable) },
         )
     }
 }

+ 0 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/info/Info.kt

@@ -21,7 +21,6 @@ import org.mjdev.desktop.components.sliding.base.VisibilityState
 import org.mjdev.desktop.components.sliding.base.VisibilityState.Companion.rememberVisibilityState
 import org.mjdev.desktop.components.text.AutoResizeText
 import org.mjdev.desktop.context.DesktopContextScope.Companion.withDesktopContext
-import org.mjdev.desktop.extensions.LaunchedEffect.runAsync
 import org.jetbrains.compose.ui.tooling.preview.Preview
 import org.mjdev.desktop.extensions.Compose.preview
 

+ 0 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/installer/Installer.kt

@@ -14,7 +14,6 @@ import org.mjdev.desktop.components.background.BackgroundImage
 import org.mjdev.desktop.components.sliding.base.VisibilityState
 import org.mjdev.desktop.components.sliding.base.VisibilityState.Companion.rememberVisibilityState
 import org.mjdev.desktop.context.DesktopContextScope.Companion.withDesktopContext
-import org.mjdev.desktop.extensions.LaunchedEffect.runAsync
 import org.jetbrains.compose.ui.tooling.preview.Preview
 import org.mjdev.desktop.extensions.Compose.preview
 

+ 4 - 5
composeApp/src/commonMain/kotlin/org/mjdev/desktop/components/text/TextArea.kt

@@ -4,7 +4,7 @@ import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Icon
+import androidx.compose.material3.Icon
 import androidx.compose.material.TextFieldDefaults
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
@@ -29,9 +29,9 @@ import org.jetbrains.compose.ui.tooling.preview.Preview
 fun TextArea(
     modifier: Modifier = Modifier,
     textState: MutableState<String>,
-    clipboardManager: ClipboardManager,
     onDone: () -> Unit = {}
 ) = withDesktopContext {
+    val clipboard : ClipboardManager = LocalClipboardManager.current
     val iconSend = rememberComputed {
         if (textState.value.isNotEmpty()) Send else null
     }
@@ -63,7 +63,7 @@ fun TextArea(
                         modifier = Modifier.padding(4.dp)
                             .size(24.dp)
                             .onMousePress { // on click
-                                clipboardManager.setText(AnnotatedString(textState.value))
+                                clipboard.setText(AnnotatedString(textState.value))
                             },
                         imageVector = ContentCopy,
                         tint = Color.White.copy(alpha = 0.9f),
@@ -91,7 +91,6 @@ fun TextArea(
 @Composable
 fun TextAreaPreview() {
     TextArea(
-        textState = remember { mutableStateOf("Hello") },
-        clipboardManager = LocalClipboardManager.current
+        textState = remember { mutableStateOf("Hello") }
     )
 }

+ 9 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/context/DesktopContextScope.kt

@@ -7,15 +7,16 @@ import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import coil3.ImageLoader
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
 import org.mjdev.desktop.data.Category
 import org.mjdev.desktop.interfaces.IApp
 import org.mjdev.desktop.managers.apps.IAppsManager
-import org.mjdev.desktop.interfaces.IDesktopContext
 import org.mjdev.desktop.interfaces.ILocale
-import org.mjdev.desktop.interfaces.IPalette
+import org.mjdev.desktop.managers.palette.IPalette
 import org.mjdev.desktop.managers.process.IProcessManager
 import org.mjdev.desktop.interfaces.ITheme
 import org.mjdev.desktop.interfaces.IUser
+import kotlin.coroutines.CoroutineContext
 
 // todo theme user support
 @Suppress("unused", "MemberVisibilityCanBePrivate")
@@ -153,7 +154,13 @@ open class DesktopContextScope(
 
     fun dispose() = context.dispose()
 
+    fun runAsync(
+        coroutineContext: CoroutineContext = Dispatchers.Default,
+        block: suspend () -> Unit
+    ) = context.runAsync(coroutineContext, block)
+
     companion object {
+        @Suppress("ComposableNaming")
         @Composable
         fun withDesktopContext(
             context: IDesktopContext = LocalDesktopContext.current,

+ 0 - 69
composeApp/src/commonMain/kotlin/org/mjdev/desktop/context/EmptyDesktopContext.kt

@@ -1,69 +0,0 @@
-package org.mjdev.desktop.context
-
-import androidx.compose.ui.unit.DpSize
-import coil3.ImageLoader
-import coil3.PlatformContext
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import org.mjdev.desktop.data.Category
-import org.mjdev.desktop.interfaces.IApp
-import org.mjdev.desktop.interfaces.IDesktopContext
-import org.mjdev.desktop.interfaces.ILocale
-import org.mjdev.desktop.interfaces.IPalette
-import org.mjdev.desktop.interfaces.ITheme
-import org.mjdev.desktop.interfaces.IUser
-import org.mjdev.desktop.managers.ai.base.IAiManager
-import org.mjdev.desktop.managers.apps.IAppsManager
-import org.mjdev.desktop.managers.base.IDelegate
-import org.mjdev.desktop.managers.connectivity.IConnectivityManager
-import org.mjdev.desktop.managers.os.IOSManager
-import org.mjdev.desktop.managers.process.IProcessManager
-import org.mjdev.desktop.managers.theme.IThemeManager
-import org.mjdev.desktop.managers.translations.ITranslator
-import kotlin.reflect.KClass
-
-open class EmptyDesktopContext : IDesktopContext() {
-    override val osManager: IOSManager = IOSManager.EMPTY
-    override val ai: IAiManager = IAiManager.EMPTY
-    override val themeManager: IThemeManager = IThemeManager.EMPTY
-    override val translator: ITranslator = ITranslator.EMPTY
-    override val appsManager: IAppsManager = IAppsManager.EMPTY
-    override val processManager: IProcessManager = IProcessManager.EMPTY
-    override val connectionManager: IConnectivityManager = IConnectivityManager.EMPTY
-
-    override val palette: IPalette = IPalette.EMPTY
-    override val theme: ITheme = ITheme.DEFAULT
-
-    override var isFirstStart: Boolean = false
-    override var isInstalled: Boolean = false
-    override val isDebug: Boolean = true
-    override val isLandscapeMode: Boolean = true
-
-    override val machineName: String = ""
-    override val appArgs: List<String> = emptyList()
-    override val containerSize: DpSize = DpSize.Zero
-    override val scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
-    override val currentLocale: ILocale = ILocale.DEFAULT
-    override val appCategories: List<Category> = emptyList()
-    override val allApps: List<IApp> = emptyList()
-    override val favoriteApps: List<IApp> = emptyList()
-
-    override val currentUser: IUser = IUser.DEFAULT // todo non null
-    override val imageLoader: ImageLoader? = null // todo non null
-
-    override val platformContext: PlatformContext? = null
-
-    override suspend fun open(what: Any?) {}
-    override suspend fun logOut() {}
-    override suspend fun restart() {}
-    override suspend fun suspend() {}
-    override suspend fun shutdown() {}
-    override suspend fun login(uName: String, uPasswd: String): Boolean  = false
-    override suspend fun logout(): Boolean = false
-
-    override fun createManager(cls: KClass<*>): IDelegate {
-        throw (RuntimeException("No manager found for: $cls"))
-    }
-
-    override fun dispose() {}
-}

+ 61 - 22
composeApp/src/commonMain/kotlin/org/mjdev/desktop/interfaces/IDesktopContext.kt → composeApp/src/commonMain/kotlin/org/mjdev/desktop/context/IDesktopContext.kt

@@ -1,9 +1,11 @@
-package org.mjdev.desktop.interfaces
+package org.mjdev.desktop.context
 
 import androidx.compose.ui.unit.DpSize
 import coil3.ImageLoader
 import coil3.PlatformContext
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
 import org.mjdev.desktop.components.controlcenter.pages.about.AboutPage
 import org.mjdev.desktop.components.controlcenter.pages.ai.AIPage
 import org.mjdev.desktop.components.controlcenter.pages.bluetooth.BluetoothSettingsPage
@@ -16,7 +18,14 @@ import org.mjdev.desktop.components.controlcenter.pages.sound.SoundSettingsPage
 import org.mjdev.desktop.components.controlcenter.pages.theme.ThemeSettingsPage
 import org.mjdev.desktop.components.controlcenter.pages.wifi.WifiSettingsPage
 import org.mjdev.desktop.data.Category
-import org.mjdev.desktop.managers.ai.base.IAiManager
+import org.mjdev.desktop.helpers.persistence.StorageProvider
+import org.mjdev.desktop.interfaces.IApp
+import org.mjdev.desktop.interfaces.IDisposable
+import org.mjdev.desktop.interfaces.ILocale
+import org.mjdev.desktop.interfaces.IPage
+import org.mjdev.desktop.interfaces.ITheme
+import org.mjdev.desktop.interfaces.IUser
+import org.mjdev.desktop.managers.ai.IAiManager
 import org.mjdev.desktop.managers.apps.IAppsManager
 import org.mjdev.desktop.managers.connectivity.IConnectivityManager
 import org.mjdev.desktop.managers.os.IOSManager
@@ -26,16 +35,30 @@ import org.mjdev.desktop.managers.translations.ITranslator
 import org.mjdev.desktop.managers.base.IDelegate
 import org.mjdev.desktop.managers.base.ManagerCache
 import org.mjdev.desktop.managers.keys.IKeyManager
+import org.mjdev.desktop.managers.palette.IPalette
+import kotlin.coroutines.CoroutineContext
 import kotlin.reflect.KClass
 import kotlin.reflect.KProperty
 
-@Suppress("LeakingThis")
+@Suppress("LeakingThis", "unused")
 abstract class IDesktopContext : IDisposable {
-    open val managersCache: ManagerCache by lazy {
-        ManagerCache(this)
-    }
+    open val managersCache: ManagerCache = ManagerCache(this)
+
+    open val controlCenterPages: MutableList<IPage> = mutableListOf(
+        MainSettingsPage(this),
+        WifiSettingsPage(this),
+        EthSettingsPage(this),
+        SoundSettingsPage(this),
+        BluetoothSettingsPage(this),
+        DisplaySettingsPage(this),
+        DevicesPage(this),
+        RemotesSettingsPage(this),
+        ThemeSettingsPage(this),
+        AIPage(this),
+        AboutPage(this)
+    )
 
-    open val controlCenterPages: List<IPage> = CONTROL_CENTER_PAGES
+    val storageProvider = StorageProvider(this)
 
     open val osManager: IOSManager by this
     open val connectionManager: IConnectivityManager by this
@@ -49,8 +72,8 @@ abstract class IDesktopContext : IDisposable {
 
     abstract var isFirstStart: Boolean
     abstract var isInstalled: Boolean
+
     abstract val machineName: String
-    abstract val isLandscapeMode: Boolean
     abstract val appArgs: List<String>
     abstract val containerSize: DpSize
     abstract val scope: CoroutineScope
@@ -77,21 +100,38 @@ abstract class IDesktopContext : IDisposable {
 
     abstract fun createManager(cls: KClass<*>): IDelegate?
 
+    fun runAsync(
+        context: CoroutineContext = Dispatchers.Default,
+        block: suspend () -> Unit
+    ) = scope.launch(context) { block() }
+
+    fun notify(
+        message: String = "This is a test message",
+        id: String = "1",
+        title: String = "This is a test",
+    ) {
+//        val messageData = KNotifMessageData(
+//            id = id,
+//            title = title,
+//            appName = stringResource(Res.string.app_name),
+//            message = message,
+//            poster = imageResource(Res.drawable.default_poster),
+//            appIcon = imageResource(Res.drawable.default_app_icon),
+//        )
+//        // Show the notification
+//        Knotif.show(messageData)
+//        // Dismiss the notification
+//        Knotif.dismiss(messageData.id)
+//        // Dismiss all notifications
+//        Knotif.dismissAll()
+//        // Set a listener to be called when a notification is clicked
+//        Knotif.setOnBuildMessageKnotifListener {
+//            println("notification clicked ${it}")
+//        }
+    }
+
     companion object {
         const val APP_PARAM_DEBUG = "--debug"
-        val CONTROL_CENTER_PAGES = listOf<IPage>(
-            MainSettingsPage(),
-            WifiSettingsPage(),
-            EthSettingsPage(),
-            SoundSettingsPage(),
-            BluetoothSettingsPage(),
-            DisplaySettingsPage(),
-            DevicesPage(),
-            RemotesSettingsPage(),
-            ThemeSettingsPage(),
-            AIPage(),
-            AboutPage()
-        )
     }
 
     inline operator fun <reified T : IDelegate> IDesktopContext.getValue(
@@ -99,4 +139,3 @@ abstract class IDesktopContext : IDisposable {
         property: KProperty<*>
     ): T = managersCache.get()
 }
-

+ 0 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/context/LocalDesktopContext.kt

@@ -1,6 +1,5 @@
 package org.mjdev.desktop.context
 
 import androidx.compose.runtime.ProvidableCompositionLocal
-import org.mjdev.desktop.interfaces.IDesktopContext
 
 expect val LocalDesktopContext: ProvidableCompositionLocal<IDesktopContext>

+ 24 - 28
composeApp/src/commonMain/kotlin/org/mjdev/desktop/extensions/Compose.kt

@@ -24,7 +24,6 @@ import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.snapshots.SnapshotStateList
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.DrawModifier
 import androidx.compose.ui.draw.drawWithContent
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
@@ -33,7 +32,6 @@ import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.graphics.ColorMatrix
 import androidx.compose.ui.graphics.ImageBitmap
 import androidx.compose.ui.graphics.Paint
-import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.platform.LocalInspectionMode
 import androidx.compose.ui.unit.Dp
@@ -43,18 +41,17 @@ import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import org.mjdev.desktop.components.guide.GuideLines
-import org.mjdev.desktop.context.LocalDesktopContext
 import org.mjdev.desktop.extensions.Colors.SuperDarkGray
 import org.mjdev.desktop.extensions.Colors.alpha
 import org.mjdev.desktop.extensions.Modifier.size
 import org.mjdev.desktop.helpers.fuzzywuzzy.FuzzySearch
-import org.mjdev.desktop.interfaces.IDesktopContext
 import kotlin.coroutines.CoroutineContext
 import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
-import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.unit.IntSize
-import kotlin.text.toInt
+import org.mjdev.desktop.context.DesktopContextScope
+import org.mjdev.desktop.context.DesktopContextScope.Companion.withDesktopContext
 
+@Suppress("unused", "MemberVisibilityCanBePrivate")
 object Compose {
 
     inline fun <reified T> Any?.orElse(
@@ -126,12 +123,6 @@ object Compose {
         }
     }
 
-    @Composable
-    fun isLandscapeMode(
-        context: IDesktopContext = LocalDesktopContext.current
-    ) = context.isLandscapeMode
-
-
     @Composable
     fun runAsync(
         context: CoroutineContext = Dispatchers.Main,
@@ -194,6 +185,7 @@ object Compose {
 //        }
 //    }
 
+    @Suppress("ComposableNaming")
     @Composable
     fun preview(
         isDark: Boolean = true,
@@ -205,25 +197,28 @@ object Compose {
         guideLinesColor: Color = if (isDark) Color.White else Color.SuperDarkGray,
         gravity: Alignment = Alignment.Center,
         padding: PaddingValues = PaddingValues(16.dp),
-        content: @Composable BoxScope.() -> Unit = {}
-    ) = BoxWithConstraints(
-        modifier = Modifier.fillMaxSize()
-            .background(backgroundColor),
-        contentAlignment = gravity,
-    ) {
-        GuideLines(
-            modifier = Modifier.fillMaxSize(),
-            color = guideLinesColor.alpha(guideAlpha),
-            cellSize = DpSize(guideStepX, guideStepY),
-            visible = showGuide
-        )
-        Box(
-            modifier = Modifier.padding(padding)
+        content: @Composable DesktopContextScope.() -> Unit = {}
+    ) = withDesktopContext {
+        BoxWithConstraints(
+            modifier = Modifier.fillMaxSize()
+                .background(backgroundColor),
+            contentAlignment = gravity,
         ) {
-            content()
+            GuideLines(
+                modifier = Modifier.fillMaxSize(),
+                color = guideLinesColor.alpha(guideAlpha),
+                cellSize = DpSize(guideStepX, guideStepY),
+                visible = showGuide
+            )
+            Box(
+                modifier = Modifier.padding(padding)
+            ) {
+                content()
+            }
         }
     }
 
+    @Suppress("ComposableNaming")
     @Composable
     fun preview(
         width: Int,
@@ -255,6 +250,7 @@ object Compose {
         )
     }
 
+    @Suppress("ComposableNaming")
     @Composable
     fun preview(
         size: Int,
@@ -325,7 +321,7 @@ object Compose {
 
     // todo
     fun Modifier.applyTransform(
-        transform : (bitmap:ImageBitmap) -> ImageBitmap = { bitmap -> bitmap }
+        transform: (bitmap: ImageBitmap) -> ImageBitmap = { bitmap -> bitmap }
     ): Modifier = this then drawWithContent {
         val paint = Paint()
         val size = IntSize(size.width.toInt(), size.height.toInt())

+ 87 - 4
composeApp/src/commonMain/kotlin/org/mjdev/desktop/extensions/CustomExt.kt

@@ -8,13 +8,17 @@ import kotlinx.coroutines.launch
 import kotlinx.datetime.Clock
 import kotlinx.datetime.Instant
 import kotlinx.datetime.toLocalDateTime
-import kotlinx.datetime.TimeZone
 import kotlinx.datetime.TimeZone.Companion.currentSystemDefault
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.Json
+import org.mjdev.desktop.log.Log
+import kotlin.coroutines.suspendCoroutine
 
+@Suppress("unused")
 object CustomExt {
 
     // todo
-    fun Instant.formatDate(): String {
+    private fun Instant.formatDate(): String {
         val localDateTime = this.toLocalDateTime(currentSystemDefault())
         val day = localDateTime.date.dayOfMonth.toString().padStart(2, '0')
         val month = localDateTime.date.monthNumber.toString().padStart(2, '0')
@@ -23,8 +27,8 @@ object CustomExt {
     }
 
     // todo
-    fun Instant.formatTime(): String {
-        val localDateTime = this.toLocalDateTime(TimeZone.currentSystemDefault())
+    private fun Instant.formatTime(): String {
+        val localDateTime = this.toLocalDateTime(currentSystemDefault())
         val hours = localDateTime.hour.toString().padStart(2, '0')
         val minutes = localDateTime.minute.toString().padStart(2, '0')
         val seconds = localDateTime.second.toString().padStart(2, '0')
@@ -97,4 +101,83 @@ object CustomExt {
 //        ignoreCase: Boolean = false
 //    ) = !trim().startsWith(prefix, ignoreCase)
 
+    suspend fun <T : Any> getAsync(
+        block: () -> T
+    ): T = suspendCoroutine { continuation ->
+        runCatching {
+            block()
+        }.onSuccess { result ->
+            continuation.resumeWith(Result.success(result))
+        }.onFailure { e ->
+            continuation.resumeWith(Result.failure(e))
+        }
+    }
+
+    inline fun <reified T> String.jsonToList(): List<T> = runCatching {
+        jsonSerializer.decodeFromString<List<T>>(this)
+    }.onFailure { err ->
+        Log.e(err)
+    }.getOrNull() ?: emptyList()
+
+    inline fun <reified T> String.to(): T? = runCatching {
+        jsonSerializer.decodeFromString<T>(this)
+    }.onFailure { err ->
+        Log.e(err)
+    }.getOrNull()
+
+    inline fun <reified T> T.asJson(): String = runCatching {
+        jsonSerializer.encodeToString(this)
+    }.onFailure { err ->
+        Log.e(err)
+    }.getOrDefault("")
+
+//    @ExperimentalSerializationApi
+//    class DynamicLookupSerializer : KSerializer<Any> {
+//        override val descriptor: SerialDescriptor = ContextualSerializer(
+//            Any::class,
+//            null,
+//            emptyArray()
+//        ).descriptor
+//
+//        @Suppress("UNCHECKED_CAST")
+//        @OptIn(InternalSerializationApi::class)
+//        override fun serialize(
+//            encoder: Encoder,
+//            value: Any
+//        ) {
+//            if (value is List<*>) {
+//                encoder.encodeSerializableValue(
+//                    ListSerializer(
+//                        DynamicLookupSerializer()
+//                    ),
+//                    value as List<Any>
+//                )
+//                return
+//            }
+//            encoder.encodeSerializableValue(
+//                encoder.serializersModule.getContextual(
+//                    value::class
+//                ) ?: value::class.serializer(),
+//                value
+//            )
+//        }
+//
+//        override fun deserialize(decoder: Decoder): Any {
+//            error("Unsupported")
+//        }
+//    }
+
+    @OptIn(ExperimentalSerializationApi::class)
+    val jsonSerializer: Json = Json {
+        allowStructuredMapKeys = true
+        coerceInputValues = true
+        useArrayPolymorphism = true
+        allowSpecialFloatingPointValues = true
+        ignoreUnknownKeys = true
+        isLenient = true
+        allowComments = true
+        allowTrailingComma = true
+        prettyPrint = true
+    }
+
 }

+ 1 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/extensions/ImageBitmapExt.kt

@@ -1,7 +1,7 @@
 package org.mjdev.desktop.extensions
 
 import androidx.compose.ui.graphics.ImageBitmap
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 
 @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
 expect object ImageBitmapExt {

+ 2 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/extensions/LaunchedEffect.kt

@@ -1,6 +1,5 @@
 package org.mjdev.desktop.extensions
 
-import androidx.annotation.RestrictTo
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.MutableState
@@ -16,11 +15,12 @@ import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.launch
-import org.mjdev.desktop.context.DesktopContextScope
 import org.mjdev.desktop.extensions.MutableStateExt.rememberState
 import kotlin.coroutines.CoroutineContext
 
+@Suppress("ComposableNaming")
 object LaunchedEffect {
+
     fun launch(
         context: CoroutineContext = Dispatchers.Default,
         scope: CoroutineScope = CoroutineScope(context),

+ 2 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/helpers/generic/Queue.kt

@@ -40,13 +40,13 @@ class Queue<E>(
         when {
             hasNext() -> {
                 source[currentIndex].apply {
-                    Log.d("New background request: $this")
+//                    Log.d("New background request: $this")
                     currentIndex += 1
                 }
             }
 
             source.isEmpty() -> {
-                Log.e("No background, empty list.")
+//                Log.e("No background, empty list.")
                 null
             }
 

+ 0 - 8
composeApp/src/commonMain/kotlin/org/mjdev/desktop/helpers/persistence/KeyValueStorage.kt

@@ -1,8 +0,0 @@
-package org.mjdev.desktop.helpers.persistence
-
-interface KeyValueStorage {
-    fun put(key: String, value: String)
-    fun get(key: String, default: String? = null): String?
-    fun remove(key: String)
-    fun clear()
-}

+ 10 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/helpers/persistence/StorageProvider.kt

@@ -1,6 +1,14 @@
 package org.mjdev.desktop.helpers.persistence
 
+import org.mjdev.desktop.context.IDesktopContext
+
 @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
-expect object StorageProvider {
-    val instance: KeyValueStorage
+expect open class StorageProvider(
+    context: IDesktopContext
+) {
+    open fun put(key: String, value: String)
+    open fun get(key: String, default: String? = null): String?
+    open fun getAll() : Map<String, String>
+    open fun remove(key: String)
+    open fun clear()
 }

+ 1 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/helpers/system/meminfo/MemInfo.kt

@@ -1,6 +1,6 @@
 package org.mjdev.desktop.helpers.system.meminfo
 
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 
 expect fun MemInfo(
     context: IDesktopContext

+ 1 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/helpers/system/meminfo/MemInfoStub.kt

@@ -1,6 +1,6 @@
 package org.mjdev.desktop.helpers.system.meminfo
 
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 
 open class MemInfoStub(
     val context: IDesktopContext

+ 2 - 0
composeApp/src/commonMain/kotlin/org/mjdev/desktop/interfaces/IControlCenterPageDataSaver.kt

@@ -1,6 +1,8 @@
 package org.mjdev.desktop.interfaces
 
 interface IControlCenterPageDataSaver {
+    val key:String
+
     fun save(data: Map<Int, Any>)
     fun load(): Map<Int, Any>
 }

+ 9 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/interfaces/IPage.kt

@@ -2,12 +2,19 @@ package org.mjdev.desktop.interfaces
 
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.graphics.vector.ImageVector
+import org.mjdev.desktop.components.controlcenter.base.ControlCenterPageScope
+import org.mjdev.desktop.components.controlcenter.base.PageCache
+import org.mjdev.desktop.context.IDesktopContext
 
-interface IPage : IDisposable {
+interface IPage: IDisposable {
+    val context: IDesktopContext
     val name: String
     val icon: ImageVector
     val condition: IDesktopContext.() -> Boolean
-    val saver: IControlCenterPageDataSaver?
+    val saver: (context: IDesktopContext) -> IControlCenterPageDataSaver?
+    val cache: PageCache
+    val showHeader: Boolean
+    val content: @Composable ControlCenterPageScope.() -> Unit
 
     @Composable
     fun Render()

+ 1 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/AiManager.kt

@@ -14,13 +14,12 @@ import org.mjdev.desktop.managers.ai.stt.base.STTPlugin
 import org.mjdev.desktop.managers.ai.tts.base.TTSPlugin
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 import org.mjdev.desktop.managers.ai.plugins.AiPluginGemini
 import org.mjdev.desktop.managers.ai.actions.ActionCalculator
 import org.mjdev.desktop.managers.ai.actions.base.ActionsProvider
 import org.mjdev.desktop.managers.ai.actions.ActionCurrentTime
 import org.mjdev.desktop.managers.ai.actions.base.ActionException.ActionNone
-import org.mjdev.desktop.managers.ai.base.IAiManager
 import org.mjdev.desktop.managers.ai.plugins.base.AIPlugin
 import org.mjdev.desktop.managers.ai.tts.TTSPluginEmpty
 

+ 1 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/base/IAiManager.kt → composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/IAiManager.kt

@@ -1,4 +1,4 @@
-package org.mjdev.desktop.managers.ai.base
+package org.mjdev.desktop.managers.ai
 
 import org.mjdev.desktop.managers.base.IDelegate
 

+ 2 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/actions/base/ActionProviderScope.kt

@@ -10,8 +10,8 @@ package org.mjdev.desktop.managers.ai.actions.base
 
 import org.mjdev.desktop.managers.ai.actions.base.ActionException.ActionFail
 import org.mjdev.desktop.managers.ai.actions.base.ActionException.ActionSuccess
-import org.mjdev.desktop.interfaces.IDesktopContext
-import org.mjdev.desktop.managers.ai.base.IAiManager
+import org.mjdev.desktop.context.IDesktopContext
+import org.mjdev.desktop.managers.ai.IAiManager
 
 @Suppress("RedundantSuspendModifier", "unused")
 class ActionProviderScope(

+ 1 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/actions/base/ActionsProvider.kt

@@ -16,7 +16,7 @@ import kotlinx.coroutines.Dispatchers
 import org.mjdev.desktop.extensions.Compose.addIfNotExists
 import org.mjdev.desktop.extensions.System.currentTimeMillis
 import org.mjdev.desktop.helpers.fuzzywuzzy.FuzzySearch
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 
 @Suppress("unused")
 class ActionsProvider(

+ 1 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/plugins/AiPluginEmpty.kt

@@ -8,7 +8,7 @@
 
 package org.mjdev.desktop.managers.ai.plugins
 
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 import org.mjdev.desktop.managers.ai.plugins.base.AIPlugin
 
 @Suppress("UNUSED_PARAMETER")

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

@@ -12,7 +12,7 @@ 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.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 import org.mjdev.desktop.managers.ai.plugins.base.AIPlugin
 
 /*

+ 1 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/plugins/AiPluginOllama.kt

@@ -1,7 +1,7 @@
 package org.mjdev.desktop.managers.ai.plugins
 
 import kotlinx.coroutines.CoroutineScope
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 import org.mjdev.desktop.managers.ai.plugins.base.AIPlugin
 
 class AiPluginOllama(

+ 1 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/stt/STTPluginEmpty.kt

@@ -10,7 +10,7 @@ package org.mjdev.desktop.managers.ai.stt
 
 import org.mjdev.desktop.managers.ai.stt.base.STTPlugin
 import org.mjdev.desktop.managers.ai.stt.base.STTListener
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 
 @Suppress("UNUSED_PARAMETER")
 class STTPluginEmpty(

+ 1 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/tts/TTSPluginEmpty.kt

@@ -1,6 +1,6 @@
 package org.mjdev.desktop.managers.ai.tts
 
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 import org.mjdev.desktop.managers.ai.tts.base.TTSPlugin
 
 @Suppress("UNUSED_PARAMETER")

+ 1 - 3
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/ai/tts/TTSPluginMain.kt

@@ -14,10 +14,8 @@ import org.mjdev.desktop.managers.ai.tts.base.TTSPlugin
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.async
 import kotlinx.coroutines.launch
-import nl.marc_apps.tts.TextToSpeechEngine
-import nl.marc_apps.tts.TextToSpeechFactory
 import nl.marc_apps.tts.TextToSpeechInstance
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 
 open class TTSPluginMain(
     val context: IDesktopContext,

+ 5 - 0
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/apps/IAppsManager.kt

@@ -3,6 +3,7 @@ package org.mjdev.desktop.managers.apps
 import org.mjdev.desktop.data.Category
 import org.mjdev.desktop.interfaces.IApp
 import org.mjdev.desktop.interfaces.ILocale
+import org.mjdev.desktop.log.Log
 import org.mjdev.desktop.managers.base.IDelegate
 
 interface IAppsManager : IDelegate {
@@ -11,6 +12,10 @@ interface IAppsManager : IDelegate {
     val favoriteApps : List<IApp>
     val currentLocale : ILocale
 
+    suspend fun startApp(app: IApp) {
+        Log.d("Start app called for app: ${app.name}, but IAppsManager is EMPTY")
+    }
+
     companion object {
         val EMPTY = object : IAppsManager {
             override val categories: List<Category> = emptyList()

+ 5 - 4
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/base/ManagerCache.kt

@@ -1,17 +1,18 @@
 package org.mjdev.desktop.managers.base
 
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 import kotlin.reflect.KClass
 
 class ManagerCache(
     val context: IDesktopContext
 ) {
-    val managersCache = mutableMapOf<KClass<*>, Any>()
+    val managersCache = mutableMapOf<KClass<*>, Any?>()
 
     inline fun <reified T : IDelegate> get(): T {
         val type = T::class
-        if (!managersCache.containsKey(type)) {
-            val manager = context.createManager(type)
+        var manager = if (managersCache.containsKey(type)) managersCache[type] else null
+        if (manager == null) {
+            manager = context.createManager(type)
             if (manager == null) {
                 throw (RuntimeException("No manager for ${T::class.simpleName} found."))
             } else {

+ 0 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/connectivity/IConnectivityManager.kt

@@ -42,7 +42,6 @@ interface IConnectivityManager : IDelegate {
         store: Boolean = true
     ): Result<Boolean> = Result.success(false)
 
-
     companion object {
         val EMPTY = object : IConnectivityManager {
             override val allDevices: MutableMap<String, NetDevice> = mutableMapOf()
@@ -57,7 +56,6 @@ interface IConnectivityManager : IDelegate {
             override val isEthAdapterAvailable: Boolean = false
             override val isBthAdapterAvailable: Boolean = false
             override val hasConnectedDevices: Boolean = false
-
         }
     }
 }

+ 1 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/language/Translator.kt

@@ -8,7 +8,7 @@
 
 package org.mjdev.desktop.managers.language
 
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 import org.mjdev.desktop.managers.base.IDelegate
 
 //import  org.mini2Dx.gettext.GetText

+ 1 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/os/base/OSManagerStub.kt

@@ -8,7 +8,7 @@
 
 package org.mjdev.desktop.managers.os.base
 
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 import org.mjdev.desktop.managers.os.IOSManager
 
 open class OSManagerStub(

+ 2 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/interfaces/IPalette.kt → composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/palette/IPalette.kt

@@ -1,7 +1,8 @@
-package org.mjdev.desktop.interfaces
+package org.mjdev.desktop.managers.palette
 
 import androidx.compose.ui.graphics.Color
 import org.mjdev.desktop.extensions.Colors.SuperDarkGray
+import org.mjdev.desktop.interfaces.IDisposable
 import org.mjdev.desktop.managers.base.IDelegate
 
 interface IPalette : IDisposable, IDelegate {

+ 1 - 2
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/palette/Palette.kt

@@ -22,8 +22,7 @@ import org.mjdev.desktop.extensions.Colors.nonAlphaValue
 import org.mjdev.desktop.extensions.ImageBitmapExt.cut
 import org.mjdev.desktop.extensions.ImageBitmapExt.loadPicture
 import org.mjdev.desktop.helpers.image.ImageUtils.topMostColor
-import org.mjdev.desktop.interfaces.IDesktopContext
-import org.mjdev.desktop.interfaces.IPalette
+import org.mjdev.desktop.context.IDesktopContext
 
 @Suppress("CanBeParameter", "MemberVisibilityCanBePrivate", "unused")
 class Palette(

+ 3 - 10
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/process/IProcessManager.kt

@@ -17,17 +17,10 @@ interface IProcessManager : IDisposable, IDelegate {
     companion object {
         val EMPTY = object : IProcessManager {
             override val size: Int = 0
-
             override fun hasAppProcess(app: IApp?): Boolean = false
-
-            override fun addListener(listener: IProcessListener) {
-            }
-
-            override fun removeListener(listener: IProcessListener) {
-            }
-
-            override fun dispose() {
-            }
+            override fun addListener(listener: IProcessListener) {}
+            override fun removeListener(listener: IProcessListener) {}
+            override fun dispose() {}
         }
     }
 }

+ 0 - 1
composeApp/src/commonMain/kotlin/org/mjdev/desktop/managers/translations/ITranslator.kt

@@ -3,7 +3,6 @@ package org.mjdev.desktop.managers.translations
 import org.mjdev.desktop.managers.base.IDelegate
 
 interface ITranslator : IDelegate {
-
     companion object {
         val EMPTY = object : ITranslator {}
     }

+ 0 - 1
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/components/controlcenter/ControlCenterWindow.kt

@@ -20,7 +20,6 @@ import org.mjdev.desktop.helpers.animation.Animations.ControlCenterExitAnimation
 import org.mjdev.desktop.helpers.mouseevents.MouseRange
 import org.mjdev.desktop.context.DesktopContextScope.Companion.withDesktopContext
 import org.mjdev.desktop.extensions.Compose.orElse
-import org.mjdev.desktop.extensions.LaunchedEffect.runAsync
 import org.mjdev.desktop.windows.ChromeWindow
 import org.mjdev.desktop.windows.ChromeWindowState
 import org.mjdev.desktop.windows.ChromeWindowState.Companion.rememberChromeWindowState

+ 2 - 1
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/components/desktoppanel/DesktopPanelWindow.kt

@@ -25,7 +25,7 @@ import org.mjdev.desktop.windows.ChromeWindowState.Companion.rememberChromeWindo
 @Suppress("FunctionName")
 @Composable
 fun DesktopPanelWindow(
-    iconSize: DpSize = DpSize(56.dp, 56.dp),
+    iconSize: DpSize = DpSize(48.dp, 48.dp),
     iconPadding: PaddingValues = PaddingValues(4.dp),
     iconOuterPadding: PaddingValues = PaddingValues(2.dp),
     showMenuIcon: Boolean = true,
@@ -134,6 +134,7 @@ fun DesktopPanelWindow(
         DesktopPanel(
             iconSize = iconSize,
             iconPadding = iconPadding,
+            iconOuterPadding = iconOuterPadding,
             showMenuIcon = showMenuIcon,
             panelState = panelState,
             onMenuIconClicked = {

+ 0 - 1
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/components/greeter/GreeterWindow.kt

@@ -7,7 +7,6 @@ import androidx.compose.ui.unit.DpOffset
 import org.mjdev.desktop.extensions.Compose.preview
 import org.mjdev.desktop.extensions.MutableStateExt.rememberState
 import org.mjdev.desktop.context.DesktopContextScope.Companion.withDesktopContext
-import org.mjdev.desktop.extensions.LaunchedEffect.runAsync
 import org.mjdev.desktop.interfaces.IUser
 import org.mjdev.desktop.windows.ChromeWindow
 

+ 0 - 1
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/components/main/MainWindow.kt

@@ -26,7 +26,6 @@ import org.mjdev.desktop.context.DesktopContextScope.Companion.withDesktopContex
 import org.mjdev.desktop.extensions.Compose.isDesign
 import org.mjdev.desktop.extensions.Compose.preview
 import org.mjdev.desktop.extensions.LaunchedEffect.launchedEffect
-import org.mjdev.desktop.extensions.LaunchedEffect.runAsync
 import org.mjdev.desktop.extensions.MutableStateExt.rememberCalculated
 import org.mjdev.desktop.helpers.system.shell.Shell
 import org.mjdev.desktop.log.Log

+ 30 - 21
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/context/DeskptopContext.kt

@@ -13,28 +13,26 @@ import coil3.request.ImageRequest
 import coil3.toBitmap
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
 import okio.Path
 import org.mjdev.desktop.data.Category
 import org.mjdev.desktop.data.User
 import org.mjdev.desktop.extensions.Compose.isDesign
 import org.mjdev.desktop.helpers.application.ApplicationScope
 import org.mjdev.desktop.helpers.image.ImageLoader.asyncImageLoader
+import org.mjdev.desktop.helpers.persistence.StorageProvider
 import org.mjdev.desktop.managers.palette.Palette
 import org.mjdev.desktop.helpers.system.shell.Shell
 import org.mjdev.desktop.interfaces.IApp
 import org.mjdev.desktop.managers.connectivity.IConnectivityManager
-import org.mjdev.desktop.interfaces.IDesktopContext
-import org.mjdev.desktop.interfaces.IPalette
+import org.mjdev.desktop.managers.palette.IPalette
 import org.mjdev.desktop.interfaces.ITheme
 import org.mjdev.desktop.interfaces.IUser
 import org.mjdev.desktop.managers.language.Translator
 import org.mjdev.desktop.log.Log
 import org.mjdev.desktop.managers.ai.AiManager
-import org.mjdev.desktop.managers.ai.base.IAiManager
+import org.mjdev.desktop.managers.ai.IAiManager
 import org.mjdev.desktop.managers.ai.plugins.AiPluginGemini
 import org.mjdev.desktop.managers.ai.stt.STTPluginEmpty
-import org.mjdev.desktop.managers.ai.tts.TTSPluginDesktop
 import org.mjdev.desktop.managers.ai.tts.TTSPluginSwift
 import org.mjdev.desktop.managers.apps.IAppsManager
 import org.mjdev.desktop.managers.os.IOSManager
@@ -52,11 +50,11 @@ import java.awt.Desktop
 import java.awt.Toolkit
 import java.io.File
 import java.net.URI
-import kotlin.coroutines.CoroutineContext
 import kotlin.reflect.KClass
 import kotlin.reflect.full.companionObject
 import org.mjdev.desktop.managers.keys.KeysManager
 
+@Suppress("RedundantSuspendModifier", "unused", "MemberVisibilityCanBePrivate")
 class DesktopContext(
     val application: ApplicationScope? = null,
     override val scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
@@ -103,10 +101,25 @@ class DesktopContext(
         get() = runCatching {
             toolkit.screenSize.let { screen -> DpSize(screen.width.dp, screen.height.dp) }
         }.getOrNull() ?: DpSize.Zero
-    override val isLandscapeMode: Boolean
-        get() = containerSize.let { it.width > it.height }
 
-//    init {
+    init {
+        // sudo apt-get update
+        // sudo apt-get install \
+        //    libgl1-mesa-dev \
+        //    xorg-dev \
+        //    libgtk-3-dev \
+        //    libglib2.0-dev \
+        //    libwebkit2gtk-4.0-dev \
+        //    libjavafx-web-dev \
+        //    openjfx \
+        //    libwebkit2gtk-4.0-37 \
+        //    libwebkitgtk-6.0-4
+//        val javaLibPath = System.getProperty("java.library.path")
+//        System.setProperty("prism.order", "sw")
+//        System.setProperty("glass.platform", "gtk")
+//        System.setProperty("javafx.platform", "gtk")
+//        System.setProperty("quantum.multithreaded", "false")
+//        System.setProperty("java.library.path","$javaLibPath:/usr/lib/x86_64-linux-gnu/")
 //        Log.i("Got app args: $args")
 //        runCatching {
 //            windowsManager.init()
@@ -117,7 +130,7 @@ class DesktopContext(
 //        runCatching {
 //            dbus.updateEnvironment()
 //        }
-//    }
+    }
 
     override fun dispose() {
         Log.i("Cleaning up resources.")
@@ -135,13 +148,6 @@ class DesktopContext(
 //        }
     }
 
-    fun runAsync(
-        context: CoroutineContext = Dispatchers.Default,
-        block: suspend () -> Unit
-    ) {
-        scope.launch(context) { block() }
-    }
-
     suspend fun openMail(emailAddress: String) = runCatching {
         desktopUtils.mail(URI.create("mailto:$emailAddress"))
     }
@@ -211,8 +217,10 @@ class DesktopContext(
         Shell.executeAndRead("/usr/bin/loginctl", "lock-sessions")
     }
 
-    override suspend fun logOut(): Unit = runAsync {
-        application?.exitApplication()
+    override suspend fun logOut() {
+        runAsync {
+            application?.exitApplication()
+        }
     }
 
     override suspend fun suspend() {
@@ -231,7 +239,7 @@ class DesktopContext(
         return false // todo
     }
 
-    override fun createManager(cls: KClass<*>): IDelegate? = when (cls) {
+    override fun createManager(cls: KClass<*>): IDelegate = when (cls) {
         IOSManager::class -> OsManager(this)
         IPalette::class -> Palette(this)
         ITranslator::class -> Translator(this)
@@ -243,6 +251,7 @@ class DesktopContext(
             pluginTTS = TTSPluginSwift(this@DesktopContext),
             pluginSTT = STTPluginEmpty(this@DesktopContext)
         )
+
         IAppsManager::class -> AppsManager(this)
         IThemeManager::class -> ThemeManager(this)
         IProcessManager::class -> ProcessManager(this)
@@ -254,7 +263,7 @@ class DesktopContext(
         Shell.executeAndRead("/usr/sbin/halt", "--reboot")
     }
 
-//    suspend fun Desktop.startApp(app: App) {
+//    suspend fun startApp(app: App) {
 //        startApp(app.desktopFile)
 //    }
 

+ 0 - 1
composeApp/src/desktopMain/kotlin/org/mjdev/desktop/context/LocalDesktopContext.desktop.kt

@@ -2,7 +2,6 @@ package org.mjdev.desktop.context
 
 import androidx.compose.runtime.ProvidableCompositionLocal
 import androidx.compose.runtime.compositionLocalOf
-import org.mjdev.desktop.interfaces.IDesktopContext
 
 actual val LocalDesktopContext: ProvidableCompositionLocal<IDesktopContext> = compositionLocalOf {
     DesktopContext()

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

@@ -5,7 +5,7 @@ import okio.Path
 import okio.Path.Companion.toPath
 import org.mjdev.desktop.extensions.PathExt.lines
 import org.mjdev.desktop.icons.user.AccountCircle
-import org.mjdev.desktop.interfaces.IDesktopContext
+import org.mjdev.desktop.context.IDesktopContext
 import org.mjdev.desktop.interfaces.IUser
 import org.mjdev.desktop.interfaces.IUserDirs
 import java.io.File

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است