Jelajahi Sumber

icon improvements

icon improvements

app started pre-implementation

app started pre-implementation
Milan Jurkulak 2 tahun lalu
induk
melakukan
a16531a9b1

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/Main.kt

@@ -39,7 +39,7 @@ fun main() = application(
 
     MaterialTheme {
         CompositionLocalProvider(
-            LocalDesktop provides DesktopProvider(
+            LocalDesktop providesDefault DesktopProvider(
                 scope = scope,
                 imageLoader = asyncImageLoader()
             ),

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenu.kt

@@ -120,7 +120,7 @@ fun AppsMenu(
                                     backgroundColor = appMenuBackgroundColor,
                                     iconTint = Color.White
                                 ) { app ->
-                                    app?.start()
+                                    app?.let { api.appsProvider.startApp(it) }
                                     menuState.hide()
                                 }
                             }

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenuApp.kt

@@ -30,7 +30,7 @@ fun AppsMenuApp(
     textColor: Color = Color.White,
     backgroundColor: Color = Color.White,
     api: DesktopProvider = LocalDesktop.current,
-    onClick: (app: App?) -> Unit = { app?.start() }
+    onClick: (app: App?) -> Unit = { app?.let { api.appsProvider.startApp(it) } }
 ) = Row(
     modifier = modifier.clickable { onClick(app) },
     verticalAlignment = Alignment.CenterVertically,

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanel.kt

@@ -57,7 +57,7 @@ fun DesktopPanel(
     tooltipData: MutableState<Any?> = mutableStateOf(null),
     onTooltip: (item: Any?) -> Unit = { item -> tooltipData.value = item },
     panelHeight: (visible: Boolean) -> Dp = { visible ->
-        if (visible) iconSize.height + iconOuterPadding.height + tooltipHeight + panelContentPadding.height
+        if (visible) iconSize.height + iconOuterPadding.height + tooltipHeight + panelContentPadding.height + 8.dp
         else dividerWidth
     },
     onMenuIconClicked: () -> Unit = {},

+ 4 - 2
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanelIcon.kt

@@ -32,6 +32,7 @@ import eu.mjdev.desktop.provider.data.App
 fun DesktopPanelIcon(
     api: DesktopProvider = LocalDesktop.current,
     app: App? = null,
+    isRunning: Boolean = false,
     icon: String? = null,
     iconColor: Color = Color.Black,
     iconBackgroundColor: Color = Color.White,
@@ -44,8 +45,9 @@ fun DesktopPanelIcon(
     onToolTip: (item: Any?) -> Unit = {},
     onClick: () -> Unit,
 ) {
-    val materialIcon = api.currentUser.theme.iconSet.iconForName(app?.name ?: icon) ?: "?".toInt()
-    val background = remember(iconState.value) { if (iconState.value) iconBackgroundHover else Color.Transparent }
+    val iconName = app?.name ?: icon
+    val materialIcon = api.currentUser.theme.iconSet.iconForName(iconName) ?: "?".toInt()
+    val background = if (iconState.value || isRunning) iconBackgroundHover else Color.Transparent
     Box(
         modifier = Modifier
             .size(iconSize)

+ 3 - 5
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/applets/DesktopPanelFavoriteApps.kt

@@ -7,8 +7,6 @@ import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.foundation.lazy.items
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.collectAsState
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -23,14 +21,13 @@ import eu.mjdev.desktop.provider.data.App
 fun DesktopPanelFavoriteApps(
     modifier: Modifier = Modifier,
     api: DesktopProvider = LocalDesktop.current,
-    favoriteApps: State<List<App>> = api.appsProvider.favoriteApps.collectAsState(emptyList()),
     iconColor: Color = Color.Black,
     iconBackgroundColor: Color = Color.White,
     iconSize: DpSize = DpSize(56.dp, 56.dp),
     iconPadding: PaddingValues = PaddingValues(4.dp),
     iconOuterPadding: PaddingValues = PaddingValues(2.dp),
     onTooltip: (item: Any?) -> Unit = {},
-    onClick: (app: App) -> Unit = { app -> app.start() },
+    onClick: (app: App) -> Unit = { app -> api.appsProvider.startApp(app) },
 ) = Box(
     modifier = modifier
 ) {
@@ -39,9 +36,10 @@ fun DesktopPanelFavoriteApps(
         horizontalArrangement = Arrangement.Start,
         verticalAlignment = Alignment.CenterVertically
     ) {
-        items(favoriteApps.value) { app ->
+        items(api.appsProvider.favoriteApps) { app ->
             DesktopPanelIcon(
                 app = app,
+                isRunning = app.isRunning,
                 iconColor = iconColor,
                 iconBackgroundColor = iconBackgroundColor,
                 iconBackgroundHover = Color.White.copy(alpha = 0.4f),

+ 21 - 10
src/main/kotlin/eu/mjdev/desktop/components/fonticon/MaterialIconFont.kt

@@ -14,31 +14,42 @@ class MaterialIconFont(
     val fontFamily: FontFamily
         get() = fontFile.fontFamily
 
+    private val cache: MutableMap<String, Int> = mutableMapOf()
+
     fun iconForName(
         name: String?,
     ): Int? = runCatching {
         if (name != null) {
-            codePointsFile.icons.map { icon ->
-                Pair(FuzzySearch.ratio(name, icon.key), icon)
-            }.maxByOrNull { it.first }?.second?.value
+            if (cache.containsKey(name)) {
+                cache[name]
+            } else {
+                codePointsFile.icons.map { icon ->
+                    Pair(FuzzySearch.ratio(name, icon.key), icon)
+                }.maxByOrNull { it.first }?.second?.value?.also {
+                    cache[name] = it
+                }
+            }
         } else null
     }.getOrNull()
 }
 
-val MaterialSymbolsOutlined
-    get() = MaterialIconFont(
+val MaterialSymbolsOutlined by lazy {
+    MaterialIconFont(
         "icons/MaterialSymbolsOutlined.ttf",
         "icons/MaterialSymbolsOutlined.codepoints"
     )
+}
 
-val MaterialSymbolsRounded
-    get() = MaterialIconFont(
+val MaterialSymbolsRounded by lazy {
+    MaterialIconFont(
         "icons/MaterialSymbolsRounded.ttf",
         "icons/MaterialSymbolsRounded.codepoints"
     )
+}
 
-val MaterialSymbolsSharp
-    get() = MaterialIconFont(
+val MaterialSymbolsSharp by lazy {
+    MaterialIconFont(
         "icons/MaterialSymbolsSharp.ttf",
         "icons/MaterialSymbolsSharp.codepoints"
-    )
+    )
+}

+ 7 - 0
src/main/kotlin/eu/mjdev/desktop/extensions/Custom.kt

@@ -2,6 +2,7 @@ package eu.mjdev.desktop.extensions
 
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.snapshots.SnapshotStateList
 import androidx.compose.ui.graphics.NativePaint
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.channelFlow
@@ -52,4 +53,10 @@ object Custom {
             }
         }.collectAsState(initial = "00:00:00")
 
+    // todo better solution
+    fun <T> SnapshotStateList<T>.invalidate() = toList().also {
+        clear()
+        addAll(it)
+    }
+
 }

+ 7 - 0
src/main/kotlin/eu/mjdev/desktop/helpers/EmptyException.kt

@@ -0,0 +1,7 @@
+package eu.mjdev.desktop.helpers
+
+class EmptyException : Exception() {
+    companion object {
+        val EmptyException = EmptyException()
+    }
+}

+ 0 - 20
src/main/kotlin/eu/mjdev/desktop/provider/DesktopProvider.kt

@@ -105,26 +105,6 @@ class DesktopProvider(
         }.start()
     }
 
-//    fun runCommandForOutput(vararg cmd: String): String? = runCatching {
-//        val sj = StringJoiner(System.lineSeparator())
-//        var result = ""
-//        try {
-//            val p = ProcessBuilder(*cmd).start()
-//            BufferedReader(InputStreamReader(p.inputStream))
-//                .lines()
-//                .iterator()
-//                .forEachRemaining { newElement: String? ->
-//                    sj.add(newElement)
-//                }
-//            result = sj.toString()
-//            p.waitFor()
-//            p.destroy()
-//        } catch (e: Exception) {
-//            e.printStackTrace()
-//        }
-//        result
-//    }.getOrNull()
-
     companion object {
         private val CONTROL_CENTER_PAGES = listOf(
             MainSettingsPage(),

+ 57 - 16
src/main/kotlin/eu/mjdev/desktop/provider/data/App.kt

@@ -1,49 +1,62 @@
 package eu.mjdev.desktop.provider.data
 
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.graphics.Color
+import eu.mjdev.desktop.helpers.EmptyException.Companion.EmptyException
 import me.xdrop.fuzzywuzzy.FuzzySearch
 import java.io.File
 import kotlin.reflect.full.companionObjectInstance
 
 @Suppress("unused", "MemberVisibilityCanBePrivate")
 class App(
-    val file: File? = null,
-    val desktopFile: DesktopFile? = file?.let { DesktopFile(file) }
+    val file: File = File("nonexistent_file_path"),
+    val desktopFile: DesktopFile = DesktopFile(file),
 ) {
     val type
-        get() = desktopFile?.type ?: "Application"
+        get() = desktopFile.type
     val version
-        get() = desktopFile?.version ?: ""
+        get() = desktopFile.version
     val name
-        get() = desktopFile?.name ?: ""
+        get() = desktopFile.name
     val comment
-        get() = desktopFile?.comment ?: ""
+        get() = desktopFile.comment
     val path
-        get() = desktopFile?.path ?: ""
+        get() = desktopFile.path
     val exec
-        get() = desktopFile?.exec ?: ""
+        get() = desktopFile.exec
     val iconName
-        get() = desktopFile?.icon ?: ""
+        get() = desktopFile.icon
     val categories
-        get() = desktopFile?.categories.ifEmptyCategories { listOf(Category.UNCATEGORIZED) }
+        get() = desktopFile.categories.ifEmptyCategories { listOf(Category.UNCATEGORIZED) }
     val notifyOnStart
-        get() = desktopFile?.notifyOnStart ?: false
+        get() = desktopFile.notifyOnStart
     val runInTerminal
-        get() = desktopFile?.runInTerminal ?: false
+        get() = desktopFile.runInTerminal
 
-    var enabled: Boolean = true
     var iconTint: Color? = null
-
     val iconBackground: Color
         get() : Color = runCatching { colorFromName() }.getOrNull() ?: Color.White
 
+    val onStopHandler: MutableState<(Throwable) -> Unit> = mutableStateOf({})
+    val onStartHandler: MutableState<() -> Unit> = mutableStateOf({})
+
+    var process: Process? = null
+    val isRunning: Boolean get() = process?.isAlive ?: false
+
     // todo :  replace all params with what needed
     fun start() = runCatching {
         val cleanExec = exec.replace("%u", "").replace("%U", "")
         println("Starting app: $name [$cleanExec]")
-        ProcessBuilder("/bin/bash", "-c", cleanExec).start()
+        ProcessBuilder("/bin/bash", "-c", cleanExec).start().also {
+            process = it
+        }.onExit().thenRun {
+            onStopHandler.value.invoke(EmptyException)
+        }
     }.onFailure { error ->
-        error.printStackTrace()
+        onStopHandler.value.invoke(error)
+    }.onSuccess {
+        onStartHandler.value.invoke()
     }
 
     private fun colorFromName(): Color {
@@ -54,6 +67,34 @@ class App(
         }.maxByOrNull { it.first }?.second?.call(null) as? Color ?: Color.White
     }
 
+    fun onStop(function: (Throwable) -> Unit): App {
+        onStopHandler.value = function
+        return this
+    }
+
+    fun onStart(function: () -> Unit): App {
+        onStartHandler.value = function
+        return this
+    }
+
+    override fun equals(other: Any?): Boolean {
+        return when {
+            other is App -> {
+                @Suppress("PlatformExtensionReceiverOfInline")
+                other.file.name.contentEquals(file.name)
+            }
+
+            else -> false
+        }
+    }
+
+    override fun hashCode(): Int {
+        var result = file.hashCode()
+        result = 31 * result + desktopFile.hashCode()
+        result = 31 * result + isRunning.hashCode()
+        return result
+    }
+
     companion object {
         val Empty: App = App()
 

+ 2 - 2
src/main/kotlin/eu/mjdev/desktop/provider/data/DesktopFile.kt

@@ -8,10 +8,10 @@ class DesktopFile(
     val file: File
 ) {
     private val content
-        get() = Ini(file)
+        get() = runCatching { if (file.exists()) Ini(file) else null }.getOrNull()
 
     private val props
-        get() = content.get("Desktop Entry")
+        get() = content?.get("Desktop Entry")
 
     val type
         get() = props?.get(Prop_Type) ?: ""

+ 25 - 23
src/main/kotlin/eu/mjdev/desktop/provider/data/Theme.kt

@@ -86,28 +86,30 @@ class Theme(
     val appMenuOuterPadding get() = appMenuOuterPaddingState.value
 
     companion object {
-        val Default = Theme(
-            iconSet = MaterialSymbolsSharp,
-
-            backgroundColor = Color.SuperDarkGray,
-
-            panelLocation = PanelLocation.Bottom,
-            panelHideDelay = 5000L,
-
-            controlCenterLocation = ControlCenterLocation.Right,
-            controlPanelHideDelay = 5000L,
-            controlCenterExpandedWidth = 480.dp,
-            controlCenterDividerColor = Color.SuperDarkGray,
-            controlCenterDividerWidth = 2.dp,
-            controlCenterIconColor = Color.White,
-            controlCenterIconSize = DpSize(32.dp, 32.dp),
-            controlCenterBackgroundAlpha = 0.8f,
-
-            backgroundRotationDelay = 60000,
-
-            appMenuMinWidth = 480.dp,
-            appMenuMinHeight = 640.dp,
-            appMenuOuterPadding = 2.dp
-        )
+        val Default by lazy {
+            Theme(
+                iconSet = MaterialSymbolsSharp,
+
+                backgroundColor = Color.SuperDarkGray,
+
+                panelLocation = PanelLocation.Bottom,
+                panelHideDelay = 5000L,
+
+                controlCenterLocation = ControlCenterLocation.Right,
+                controlPanelHideDelay = 5000L,
+                controlCenterExpandedWidth = 480.dp,
+                controlCenterDividerColor = Color.SuperDarkGray,
+                controlCenterDividerWidth = 2.dp,
+                controlCenterIconColor = Color.White,
+                controlCenterIconSize = DpSize(32.dp, 32.dp),
+                controlCenterBackgroundAlpha = 0.8f,
+
+                backgroundRotationDelay = 60000,
+
+                appMenuMinWidth = 480.dp,
+                appMenuMinHeight = 640.dp,
+                appMenuOuterPadding = 2.dp
+            )
+        }
     }
 }

+ 53 - 14
src/main/kotlin/eu/mjdev/desktop/provider/providers/AppsProvider.kt

@@ -1,14 +1,15 @@
 package eu.mjdev.desktop.provider.providers
 
+import androidx.compose.runtime.mutableStateListOf
+import eu.mjdev.desktop.extensions.Custom.invalidate
 import eu.mjdev.desktop.extensions.Locale.toLocale
 import eu.mjdev.desktop.helpers.Command
 import eu.mjdev.desktop.helpers.Command.Companion.toList
+import eu.mjdev.desktop.helpers.EmptyException.Companion.EmptyException
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.data.App
 import eu.mjdev.desktop.provider.data.Category
 import eu.mjdev.desktop.provider.data.DesktopFile
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flow
 import java.io.File
 import java.util.*
 
@@ -128,23 +129,61 @@ class AppsProvider(
             map
         }
 
-    val favoriteApps: Flow<List<App>>
-        get() = flow {
-            Command(
-                "gsettings",
-                "get",
-                "org.gnome.shell",
-                "favorite-apps"
-            ).execute()?.toList<String>()?.map { deskFileName ->
+//    val runningApps = mutableStateListOf<App>()
+
+//    val runningAppsSize: Int
+//        get() = runningApps.size
+
+    val favoriteApps = mutableStateListOf<App>()
+
+//    private fun clearNonRunning() {
+//        if (runningApps.size > 0) {
+//            with(runningApps.iterator()) {
+//                val app = next()
+//                if (!isRunning(app)) {
+//                    remove()
+//                }
+//            }
+//        }
+//    }
+
+//    private fun isRunning(app: App) =
+//        ProcessHandle.allProcesses().filter {
+//            it.isAlive && it.pid() == app.processId
+//        }.count() > 0
+
+    fun startApp(app: App) {
+        app.onStart {
+            favoriteApps.invalidate()
+        }.onStop { result ->
+            favoriteApps.invalidate()
+            if (result != EmptyException) {
+                result.printStackTrace()
+            }
+        }.start()
+    }
+
+    init {
+        Command(
+            "gsettings",
+            "get",
+            "org.gnome.shell",
+            "favorite-apps"
+        ).execute()
+            ?.toList<String>()
+            ?.mapNotNull { deskFileName ->
                 allAppsDesktopFiles.filter { deskFile ->
                     @Suppress("PlatformExtensionReceiverOfInline")
                     deskFile.file.name.contentEquals(deskFileName)
                 }.map { deskFile ->
-                    App(desktopFile = deskFile, file = deskFile.file)
+                    App(
+                        desktopFile = deskFile,
+                        file = deskFile.file
+                    )
                 }.firstOrNull()
-            }.also { list ->
-                emit(list?.filterNotNull() ?: emptyList())
+            }?.also { favorite ->
+                favoriteApps.addAll(favorite)
             }
-        }
+    }
 
 }