Milan Jurkulak il y a 2 ans
Parent
commit
0b827d55ec
26 fichiers modifiés avec 490 ajouts et 198 suppressions
  1. 73 0
      src/main/kotlin/eu/mjdev/desktop/components/appbar/AppBar.kt
  2. 73 0
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppActions.kt
  3. 6 44
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsBottomBar.kt
  4. 2 2
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenu.kt
  5. 1 1
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenuApp.kt
  6. 1 1
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenuCategory.kt
  7. 3 3
      src/main/kotlin/eu/mjdev/desktop/components/button/ButtonPrimary.kt
  8. 31 0
      src/main/kotlin/eu/mjdev/desktop/components/button/ButtonSecondary.kt
  9. 33 0
      src/main/kotlin/eu/mjdev/desktop/components/button/TransparentButton.kt
  10. 7 2
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/ControlCenter.kt
  11. 2 3
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/base/ControlCenterPage.kt
  12. 70 0
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/base/PageHeader.kt
  13. 1 1
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/AIPage.kt
  14. 3 2
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/MainSettingsPage.kt
  15. 23 23
      src/main/kotlin/eu/mjdev/desktop/components/custom/MeasurableComponent.kt
  16. 36 36
      src/main/kotlin/eu/mjdev/desktop/components/custom/MeasureUnconstrainedView.kt
  17. 67 46
      src/main/kotlin/eu/mjdev/desktop/components/custom/UserAvatar.kt
  18. 0 5
      src/main/kotlin/eu/mjdev/desktop/components/file/FolderView.kt
  19. 1 1
      src/main/kotlin/eu/mjdev/desktop/components/greeter/Greeter.kt
  20. 1 0
      src/main/kotlin/eu/mjdev/desktop/data/App.kt
  21. 20 12
      src/main/kotlin/eu/mjdev/desktop/data/DesktopFile.kt
  22. 8 8
      src/main/kotlin/eu/mjdev/desktop/extensions/Custom.kt
  23. 14 0
      src/main/kotlin/eu/mjdev/desktop/helpers/compose/Orientation.kt
  24. 1 1
      src/main/kotlin/eu/mjdev/desktop/helpers/shape/DottedShape.kt
  25. 12 6
      src/main/kotlin/eu/mjdev/desktop/provider/DesktopProvider.kt
  26. 1 1
      src/main/kotlin/eu/mjdev/desktop/theme/gtk/GtkThemeHelper.kt

+ 73 - 0
src/main/kotlin/eu/mjdev/desktop/components/appbar/AppBar.kt

@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package eu.mjdev.desktop.components.appbar
+
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun AppBar(
+    modifier: Modifier = Modifier,
+    contentPadding :PaddingValues = PaddingValues(4.dp),
+    icon: @Composable RowScope.() -> Unit = {},
+    title: @Composable RowScope.() -> Unit = {},
+    actions: @Composable RowScope.() -> Unit = {},
+) {
+    Row(
+        modifier = modifier
+            .height(IntrinsicSize.Min)
+            .padding(contentPadding),
+        verticalAlignment = Alignment.CenterVertically,
+    ) {
+        Row(
+            verticalAlignment = Alignment.CenterVertically,
+            content = icon
+        )
+        Row(
+            modifier = Modifier.weight(1f),
+            verticalAlignment = Alignment.CenterVertically,
+            content = title
+        )
+        Row(
+            verticalAlignment = Alignment.CenterVertically,
+            content = actions
+        )
+    }
+}
+
+@Preview
+@Composable
+fun AppBarPreview() = AppBar(
+    modifier = Modifier.fillMaxWidth().height(48.dp).background(Color.Green),
+    icon = {
+        Box(
+            modifier = Modifier.size(24.dp)
+                .background(Color.Red)
+        )
+    },
+    title = {
+        Box(
+            modifier = Modifier.height(24.dp)
+                .fillMaxWidth()
+                .background(Color.Blue)
+        )
+    },
+    actions = {
+        Box(
+            modifier = Modifier.size(24.dp, 24.dp)
+                .background(Color.Yellow)
+        )
+    }
+)

+ 73 - 0
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppActions.kt

@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package eu.mjdev.desktop.components.appsmenu
+
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import eu.mjdev.desktop.components.icon.ShapedIcon
+import eu.mjdev.desktop.icons.Icons
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
+
+@Composable
+fun AppActions(
+    // todo theme
+    iconRestart: ImageVector = Icons.RestartComputer,
+    // todo theme
+    iconPowerOff: ImageVector = Icons.PowerOffComputer,
+    // todo theme
+    iconLogout: ImageVector = Icons.LogOutUser,
+    modifier: Modifier = Modifier,
+    onActionClick: () -> Unit = {},
+) = withDesktopScope {
+    Row(
+        modifier = modifier.wrapContentWidth()
+    ) {
+        ShapedIcon(
+            imageVector = iconRestart,
+            iconColor = borderColor,
+            iconBackgroundColor = iconsTintColor,
+            onRightClick = {},
+            onClick = {
+                onActionClick()
+                // todo : dialog
+                api.restart()
+            }
+        )
+        ShapedIcon(
+            imageVector = iconPowerOff,
+            iconColor = borderColor,
+            iconBackgroundColor = iconsTintColor,
+            onRightClick = {},
+            onClick = {
+                onActionClick()
+                // todo : dialog
+                api.suspend()
+            }
+        )
+        ShapedIcon(
+            imageVector = iconLogout,
+            iconColor = borderColor,
+            iconBackgroundColor = iconsTintColor,
+            onRightClick = {},
+            onClick = {
+                onActionClick()
+                // todo : dialog
+                api.logOut()
+            }
+        )
+    }
+}
+
+@Preview
+@Composable
+fun AppActionsPreview() = AppActions()

+ 6 - 44
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsBottomBar.kt

@@ -3,16 +3,13 @@ package eu.mjdev.desktop.components.appsmenu
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.*
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.TopAppBar
-import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.sp
+import eu.mjdev.desktop.components.appbar.AppBar
 import eu.mjdev.desktop.components.icon.ShapedIcon
 import eu.mjdev.desktop.components.input.SearchFieldPassive
 import eu.mjdev.desktop.extensions.Compose.clear
@@ -20,7 +17,6 @@ import eu.mjdev.desktop.extensions.Compose.rememberState
 import eu.mjdev.desktop.icons.Icons
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Suppress("FunctionName")
 @Composable
 fun AppsBottomBar(
@@ -28,12 +24,11 @@ fun AppsBottomBar(
     backButtonVisible: Boolean = true,
     searchTextState: MutableState<String> = rememberState(""),
     onContextMenuClick: () -> Unit = {},
-    onHideMenu: () -> Unit = {},
+    onActionClick: () -> Unit = {},
     onBackClick: () -> Unit = {}
 ) = withDesktopScope {
-    TopAppBar(
+    AppBar(
         modifier = modifier.focusable(false),
-        colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
         title = {
             SearchFieldPassive(
                 modifier = Modifier.fillMaxWidth().focusable(true),
@@ -46,7 +41,7 @@ fun AppsBottomBar(
                 onClearClick = { searchTextState.clear() }
             )
         },
-        navigationIcon = {
+        icon = {
             ShapedIcon(
                 modifier = Modifier.focusable(false),
                 visible = backButtonVisible && searchTextState.value.isEmpty(),
@@ -58,41 +53,8 @@ fun AppsBottomBar(
             )
         },
         actions = {
-            ShapedIcon(
-                modifier = Modifier,
-                imageVector = Icons.RestartComputer,
-                iconColor = borderColor,
-                iconBackgroundColor = iconsTintColor,
-                onRightClick = onContextMenuClick,
-                onClick = {
-                    onHideMenu()
-                    // todo : dialog
-                    api.restart()
-                }
-            )
-            ShapedIcon(
-                modifier = Modifier,
-                imageVector = Icons.PowerOffComputer,
-                iconColor = borderColor,
-                iconBackgroundColor = iconsTintColor,
-                onRightClick = onContextMenuClick,
-                onClick = {
-                    onHideMenu()
-                    // todo : dialog
-                    api.suspend()
-                }
-            )
-            ShapedIcon(
-                modifier = Modifier,
-                imageVector = Icons.LogOutUser,
-                iconColor = borderColor,
-                iconBackgroundColor = iconsTintColor,
-                onRightClick = onContextMenuClick,
-                onClick = {
-                    onHideMenu()
-                    // todo : dialog
-                    api.logOut()
-                }
+            AppActions(
+                onActionClick = onActionClick
             )
         }
     )

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

@@ -5,7 +5,6 @@ import androidx.compose.animation.ExitTransition
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
-import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.shape.RoundedCornerShape
@@ -35,6 +34,7 @@ import eu.mjdev.desktop.extensions.Custom.trimIsNotEmpty
 import eu.mjdev.desktop.extensions.Modifier.dropShadow
 import eu.mjdev.desktop.helpers.animation.Animations.AppsMenuEnterAnimation
 import eu.mjdev.desktop.helpers.animation.Animations.AppsMenuExitAnimation
+import eu.mjdev.desktop.helpers.compose.Orientation
 import eu.mjdev.desktop.helpers.compose.rememberForeverLazyListState
 import eu.mjdev.desktop.helpers.internal.KeyEventHandler.Companion.globalKeyEventHandler
 import eu.mjdev.desktop.provider.DesktopScope
@@ -202,7 +202,7 @@ fun AppsMenu(
                             .background(backgroundColor),
                         backButtonVisible = items.firstOrNull() is App,
                         searchTextState = searchTextState,
-                        onHideMenu = {
+                        onActionClick = {
                             menuState.hide()
                         },
                         onBackClick = {

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

@@ -18,7 +18,7 @@ import eu.mjdev.desktop.extensions.Compose.onLeftClick
 import eu.mjdev.desktop.extensions.Compose.onMousePress
 import eu.mjdev.desktop.extensions.Compose.onRightClick
 import eu.mjdev.desktop.extensions.Modifier.circleBorder
-import eu.mjdev.desktop.helpers.DottedShape
+import eu.mjdev.desktop.helpers.shape.DottedShape
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 
 @Suppress("FunctionName")

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

@@ -18,7 +18,7 @@ import eu.mjdev.desktop.extensions.Compose.onLeftClick
 import eu.mjdev.desktop.extensions.Compose.onMousePress
 import eu.mjdev.desktop.extensions.Compose.onRightClick
 import eu.mjdev.desktop.extensions.Modifier.circleBorder
-import eu.mjdev.desktop.helpers.DottedShape
+import eu.mjdev.desktop.helpers.shape.DottedShape
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 
 @Composable

+ 3 - 3
src/main/kotlin/eu/mjdev/desktop/components/button/ButtonPrimary.kt

@@ -1,10 +1,10 @@
 package eu.mjdev.desktop.components.button
 
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
 import androidx.compose.runtime.Composable
 import eu.mjdev.desktop.components.text.TextAny
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
-import androidx.compose.material.Button
-import androidx.compose.material.ButtonDefaults
 
 @Composable
 fun ButtonPrimary(
@@ -18,7 +18,7 @@ fun ButtonPrimary(
             onClick = onClick,
             enabled = enabled,
             colors = ButtonDefaults.buttonColors(
-                backgroundColor = backgroundColor,
+                backgroundColor = iconsTintColor,
                 disabledBackgroundColor = disabledColor
             )
         ) {

+ 31 - 0
src/main/kotlin/eu/mjdev/desktop/components/button/ButtonSecondary.kt

@@ -0,0 +1,31 @@
+package eu.mjdev.desktop.components.button
+
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.runtime.Composable
+import eu.mjdev.desktop.components.text.TextAny
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
+
+@Composable
+fun ButtonSecondary(
+    text: String = "Button",
+    enabled: Boolean = true,
+    visible: Boolean = true,
+    onClick: () -> Unit = {}
+) = withDesktopScope {
+    if (visible) {
+        Button(
+            onClick = onClick,
+            enabled = enabled,
+            colors = ButtonDefaults.buttonColors(
+                backgroundColor = backgroundColor,
+                disabledBackgroundColor = disabledColor
+            )
+        ) {
+            TextAny(
+                text = text,
+                color = textColor
+            )
+        }
+    }
+}

+ 33 - 0
src/main/kotlin/eu/mjdev/desktop/components/button/TransparentButton.kt

@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package eu.mjdev.desktop.components.button
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import eu.mjdev.desktop.extensions.Compose.noElevation
+import eu.mjdev.desktop.extensions.Compose.transparent
+
+@Composable
+fun TransparentButton(
+    modifier: Modifier = Modifier,
+    onClick: () -> Unit = {},
+    content: @Composable RowScope.() -> Unit = {}
+) = Button(
+    modifier = modifier,
+    contentPadding = PaddingValues(0.dp),
+    onClick = onClick,
+    colors = ButtonDefaults.transparent(),
+    elevation = ButtonDefaults.noElevation(),
+    content = content
+)

+ 7 - 2
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/ControlCenter.kt

@@ -21,6 +21,7 @@ import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.WindowPosition
+import eu.mjdev.desktop.components.controlcenter.base.PageHeader
 import eu.mjdev.desktop.components.sliding.SlidingMenu
 import eu.mjdev.desktop.components.sliding.base.VisibilityState
 import eu.mjdev.desktop.components.sliding.base.VisibilityState.Companion.rememberVisibilityState
@@ -174,10 +175,14 @@ fun ControlCenter(
                                         color = borderColor,
                                         thickness = 2.dp
                                     )
-                                    Box(
+                                    Column(
                                         modifier = Modifier.width(controlCenterExpandedWidth),
                                     ) {
-                                        pagesFiltered.value[pagerState.value].render()
+                                        val page = pagesFiltered.value[pagerState.value]
+                                        PageHeader(page = page)
+                                        Box(modifier = Modifier.weight(1f)) {
+                                            page.render()
+                                        }
                                     }
                                     Divider(
                                         modifier = Modifier.fillMaxHeight()

+ 2 - 3
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/base/ControlCenterPage.kt

@@ -14,10 +14,9 @@ class ControlCenterPage(
     val condition: DesktopProvider.() -> Boolean = { true },
     val saver: IControlCenterPageDataSaver? = null,
     val cache: PageCache = PageCache(saver),
+    val showHeader: Boolean = true,
     val content: @Composable ControlCenterPageScope.() -> Unit = {},
 ) {
     @Composable
-    fun render() = withControlCenterPageScope(cache = cache) {
-        content()
-    }
+    fun render() = withControlCenterPageScope(cache = cache) { content() }
 }

+ 70 - 0
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/base/PageHeader.kt

@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package eu.mjdev.desktop.components.controlcenter.base
+
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import eu.mjdev.desktop.components.appbar.AppBar
+import eu.mjdev.desktop.components.controlcenter.pages.MainSettingsPage
+import eu.mjdev.desktop.components.image.ImageAny
+import eu.mjdev.desktop.components.text.TextAny
+import eu.mjdev.desktop.extensions.Modifier.circleBorder
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
+
+@Composable
+fun PageHeader(
+    modifier: Modifier = Modifier,
+    page: ControlCenterPage = MainSettingsPage(),
+    contentPadding: PaddingValues = PaddingValues(8.dp),
+    iconSize: Dp = 64.dp,
+    actions: @Composable RowScope.() -> Unit = {},
+) = withDesktopScope {
+    if (page.showHeader) {
+        AppBar(
+            modifier = modifier.fillMaxWidth()
+                .wrapContentHeight()
+                .background(backgroundColor),
+            contentPadding = contentPadding,
+            icon = {
+                Box(
+                    modifier = Modifier.size(iconSize + 4.dp)
+                        .circleBorder(2.dp, iconsTintColor)
+                        .padding(4.dp)
+                ) {
+                    ImageAny(
+                        modifier = Modifier.size(iconSize),
+                        src = page.icon,
+                        colorFilter = ColorFilter.tint(iconsTintColor),
+                        contentDescription = ""
+                    )
+                }
+            },
+            title = {
+                TextAny(
+                    modifier = Modifier.fillMaxWidth().padding(start = 16.dp),
+                    text = page.name,
+                    color = iconsTintColor,
+                    fontSize = 20.sp
+                )
+            },
+            actions = actions
+        )
+    }
+}
+
+@Preview
+@Composable
+fun PageHeaderPreview() = PageHeader()

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/AIPage.kt

@@ -31,7 +31,7 @@ import eu.mjdev.desktop.icons.Icons
 @Suppress("FunctionName")
 fun AIPage() = ControlCenterPage(
     icon = Icons.Chat,
-    name = "AI",
+    name = "Assistant",
     condition = { ai.isAvailable() }
 ) {
     val clipboardManager = LocalClipboardManager.current

+ 3 - 2
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/MainSettingsPage.kt

@@ -2,7 +2,6 @@ package eu.mjdev.desktop.components.controlcenter.pages
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.background
-import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.*
 import androidx.compose.material.Divider
 import androidx.compose.material.icons.Icons
@@ -17,11 +16,13 @@ import eu.mjdev.desktop.components.custom.PowerBlock
 import eu.mjdev.desktop.components.custom.UserAvatar
 import eu.mjdev.desktop.extensions.ColorUtils.alpha
 import eu.mjdev.desktop.extensions.Modifier.rectShadow
+import eu.mjdev.desktop.helpers.compose.Orientation
 
 @Suppress("FunctionName")
 fun MainSettingsPage() = ControlCenterPage(
     icon = Icons.Filled.Home,
-    name = "Home"
+    name = "Home",
+    showHeader = false
 ) {
     Box(
         modifier = Modifier.fillMaxSize()

+ 23 - 23
src/main/kotlin/eu/mjdev/desktop/components/custom/MeasurableComponent.kt

@@ -1,26 +1,26 @@
 package eu.mjdev.desktop.components.custom
 
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.layout.SubcomposeLayout
-import androidx.compose.ui.unit.DpSize
-import androidx.compose.ui.unit.dp
+//import androidx.compose.runtime.Composable
+//import androidx.compose.ui.layout.SubcomposeLayout
+//import androidx.compose.ui.unit.DpSize
+//import androidx.compose.ui.unit.dp
 
-@Suppress("FunctionName", "unused")
-@Composable
-fun MeasurableComponent(
-    key: Any,
-    onMeasure: (size: DpSize) -> Unit = {},
-    content: @Composable () -> Unit = {},
-) = SubcomposeLayout { constraints ->
-    subcompose(
-        key,
-        content
-    ).map { measurable ->
-        measurable.measure(constraints.copy(minWidth = 0, minHeight = 0))
-    }.first().let { placeable ->
-        onMeasure(DpSize(placeable.width.dp, placeable.height.dp))
-        layout(placeable.width, placeable.height) {
-            placeable.placeRelative(0, 0)
-        }
-    }
-}
+//@Suppress("FunctionName", "unused")
+//@Composable
+//fun MeasurableComponent(
+//    key: Any? = null,
+//    onMeasure: (size: DpSize) -> Unit = {},
+//    content: @Composable () -> Unit = {},
+//) = SubcomposeLayout { constraints ->
+//    subcompose(
+//        key,
+//        content
+//    ).map { measurable ->
+//        measurable.measure(constraints.copy(minWidth = 0, minHeight = 0))
+//    }.first().let { placeable ->
+//        onMeasure(DpSize(placeable.width.dp, placeable.height.dp))
+//        layout(placeable.width, placeable.height) {
+//            placeable.placeRelative(0, 0)
+//        }
+//    }
+//}

+ 36 - 36
src/main/kotlin/eu/mjdev/desktop/components/custom/MeasureUnconstrainedView.kt

@@ -1,40 +1,40 @@
 package eu.mjdev.desktop.components.custom
 
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.layout.SubcomposeLayout
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.Dp
+//import androidx.compose.material.Text
+//import androidx.compose.runtime.Composable
+//import androidx.compose.ui.layout.SubcomposeLayout
+//import androidx.compose.ui.unit.Constraints
+//import androidx.compose.ui.unit.Dp
 
-@Suppress("FunctionName", "unused")
-@Composable
-fun MeasureUnconstrainedView(
-    viewToMeasure: @Composable () -> Unit,
-    content: @Composable (measuredWidth: Dp, measuredHeight: Dp) -> Unit,
-) {
-    SubcomposeLayout { constraints ->
-        val measured = subcompose("viewToMeasure", viewToMeasure)[0]
-            .measure(Constraints())
-        val measuredWidth = measured.width.toDp()
-        val measuredHeight = measured.height.toDp()
-        val contentPlaceable = subcompose("content") {
-            content(measuredWidth, measuredHeight)
-        }[0].measure(constraints)
-        layout(contentPlaceable.width, contentPlaceable.height) {
-            contentPlaceable.place(0, 0)
-        }
-    }
-}
+//@Suppress("FunctionName", "unused")
+//@Composable
+//fun MeasureUnconstrainedView(
+//    viewToMeasure: @Composable () -> Unit,
+//    content: @Composable (measuredWidth: Dp, measuredHeight: Dp) -> Unit,
+//) {
+//    SubcomposeLayout { constraints ->
+//        val measured = subcompose("viewToMeasure", viewToMeasure)[0]
+//            .measure(Constraints())
+//        val measuredWidth = measured.width.toDp()
+//        val measuredHeight = measured.height.toDp()
+//        val contentPlaceable = subcompose("content") {
+//            content(measuredWidth, measuredHeight)
+//        }[0].measure(constraints)
+//        layout(contentPlaceable.width, contentPlaceable.height) {
+//            contentPlaceable.place(0, 0)
+//        }
+//    }
+//}
 
-@Suppress("FunctionName", "unused")
-@Composable
-fun TestMeasurable() {
-    MeasureUnconstrainedView(
-        viewToMeasure = {
-            Text("your sample text")
-        }
-    ) { mw, mh ->
-        println("size: $mw,$mh")
-        // measure view by another view
-    }
-}
+//@Suppress("FunctionName", "unused")
+//@Composable
+//fun TestMeasurable() {
+//    MeasureUnconstrainedView(
+//        viewToMeasure = {
+//            Text("your sample text")
+//        }
+//    ) { mw, mh ->
+//        println("size: $mw,$mh")
+//        // measure view by another view
+//    }
+//}

+ 67 - 46
src/main/kotlin/eu/mjdev/desktop/components/custom/UserAvatar.kt

@@ -1,74 +1,82 @@
 package eu.mjdev.desktop.components.custom
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
-import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.*
-import androidx.compose.material.Button
-import androidx.compose.material.ButtonDefaults
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import eu.mjdev.desktop.components.appbar.AppBar
+import eu.mjdev.desktop.components.button.TransparentButton
 import eu.mjdev.desktop.components.image.ImageAny
 import eu.mjdev.desktop.components.text.TextAny
-import eu.mjdev.desktop.extensions.Compose.noElevation
-import eu.mjdev.desktop.extensions.Compose.transparent
+import eu.mjdev.desktop.extensions.Compose.SuperDarkGray
 import eu.mjdev.desktop.extensions.Modifier.circleBorder
 import eu.mjdev.desktop.extensions.Modifier.clipCircle
+import eu.mjdev.desktop.helpers.compose.Orientation
 import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 
 @Suppress("FunctionName")
 @Composable
 fun UserAvatar(
     modifier: Modifier = Modifier,
-    avatarSize: Dp = 128.dp,
+    avatarSize: Dp = 64.dp,
     orientation: Orientation = Orientation.Vertical,
-    onUserAvatarClick: () -> Unit = {}
+    onUserAvatarClick: () -> Unit = {},
+    actions: @Composable RowScope.() -> Unit = {},
 ) = withDesktopScope {
     val content: @Composable (orientation: Orientation) -> Unit = {
-        Button(
-            modifier = Modifier
-                .size(avatarSize)
-                .clipCircle(),
-            contentPadding = PaddingValues(0.dp),
-            onClick = onUserAvatarClick,
-            colors = ButtonDefaults.transparent(),
-            elevation = ButtonDefaults.noElevation()
-        ) {
-            ImageAny(
-                modifier = Modifier
-                    .size(avatarSize)
-                    .circleBorder(2.dp, iconsTintColor),
-                src = api.currentUser.picture,
-                colorFilter = if (api.currentUser.picture is ImageVector) ColorFilter.tint(iconsTintColor)
-                else null,
-                contentDescription = ""
-            )
-        }
-        Column(
-            modifier = Modifier.padding(start = 8.dp).fillMaxWidth().padding(top = 8.dp),
-            verticalArrangement = Arrangement.Center,
-            horizontalAlignment = if (orientation == Orientation.Vertical) Alignment.CenterHorizontally
-            else Alignment.Start
-        ) {
-            TextAny(
-                text = api.currentUser.userName,
-                color = textColor,
-                textAlign = TextAlign.Start,
-                fontWeight = FontWeight.Bold
-            )
-            TextAny(
-                text = api.machineName,
-                color = textColor,
-                textAlign = TextAlign.Start,
-                fontWeight = FontWeight.Medium
-            )
-        }
+        AppBar(
+            icon = {
+                TransparentButton(
+                    modifier = Modifier
+                        .size(avatarSize)
+                        .clipCircle(),
+                    onClick = onUserAvatarClick,
+                ) {
+                    ImageAny(
+                        modifier = Modifier
+                            .size(avatarSize)
+                            .circleBorder(2.dp, iconsTintColor),
+                        src = api.currentUser.picture,
+                        colorFilter = if (api.currentUser.picture is ImageVector) ColorFilter.tint(iconsTintColor)
+                        else null,
+                        contentDescription = ""
+                    )
+                }
+            },
+            title = {
+                Column(
+                    modifier = Modifier.padding(start = 8.dp)
+                        .fillMaxWidth()
+                        .padding(top = 8.dp),
+                    verticalArrangement = Arrangement.Center,
+                    horizontalAlignment = if (orientation == Orientation.Vertical) Alignment.CenterHorizontally
+                    else Alignment.Start
+                ) {
+                    TextAny(
+                        text = api.currentUser.userName,
+                        color = textColor,
+                        textAlign = TextAlign.Start,
+                        fontWeight = FontWeight.Bold
+                    )
+                    TextAny(
+                        text = api.machineName,
+                        color = textColor,
+                        textAlign = TextAlign.Start,
+                        fontWeight = FontWeight.Medium
+                    )
+                }
+            },
+            actions = actions
+        )
     }
     Box(
         modifier = modifier
@@ -97,4 +105,17 @@ fun UserAvatar(
 
 @Preview
 @Composable
-fun UserAvatarPreview() = UserAvatar()
+fun UserAvatarPreview() {
+    Column {
+        UserAvatar(
+            modifier = Modifier.fillMaxWidth()
+                .background(Color.SuperDarkGray),
+            orientation = Orientation.Vertical
+        )
+        UserAvatar(
+            modifier = Modifier.fillMaxWidth()
+                .background(Color.SuperDarkGray),
+            orientation = Orientation.Horizontal
+        )
+    }
+}

+ 0 - 5
src/main/kotlin/eu/mjdev/desktop/components/file/FolderView.kt

@@ -1,10 +1,5 @@
 package eu.mjdev.desktop.components.file
 
-//import androidx.compose.ui.Alignment
-//import androidx.compose.ui.graphics.Color
-//import androidx.compose.ui.unit.dp
-//import com.composables.core.*
-//import eu.mjdev.desktop.extensions.Compose.SuperDarkGray
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.ScrollState
 import androidx.compose.foundation.gestures.Orientation

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/components/greeter/Greeter.kt

@@ -2,7 +2,6 @@ package eu.mjdev.desktop.components.greeter
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.background
-import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
@@ -22,6 +21,7 @@ import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 import eu.mjdev.desktop.windows.ChromeWindow
 import eu.mjdev.desktop.extensions.ColorUtils.alpha
 import eu.mjdev.desktop.extensions.Compose.rememberState
+import eu.mjdev.desktop.helpers.compose.Orientation
 
 // todo : all users
 @Suppress("FunctionName", "unused")

+ 1 - 0
src/main/kotlin/eu/mjdev/desktop/data/App.kt

@@ -85,6 +85,7 @@ class App(
                     app = this@App,
                     onStarted = {
                         println("App started app: $name [$windowClass].")
+                        println("Desktop File:")
                         triggerStarted()
                     },
                     onStopped = { e ->

+ 20 - 12
src/main/kotlin/eu/mjdev/desktop/data/DesktopFile.kt

@@ -16,7 +16,11 @@ class DesktopFile(
     val correctDir: String = "/tmp/.corrected-desktop-files/",
     val correctedFile: File = File(File(correctDir), file.name),
     var fileData: String = (if (correctedFile.exists()) correctedFile else file).text,
-    var content: Ini = (runCatching { LinuxIni(fileData) }.getOrNull() ?: LinuxIni())
+    private var content: Ini = (runCatching {
+        LinuxIni(fileData)
+    }.onFailure { e ->
+        Exception("Error in file: ${file.absolutePath}", e).printStackTrace()
+    }.getOrNull() ?: LinuxIni())
 ) {
     var fileName: String = if (correctedFile.exists()) correctedFile.name else file.name
     val absolutePath: String = if (correctedFile.exists()) correctedFile.absolutePath else file.absolutePath
@@ -31,14 +35,14 @@ class DesktopFile(
             it.value.toString()
         }
 
-    val desktopSection
-        get() = content[DesktopFileType.DesktopEntry.text]?.let { DesktopSectionScope(it) }
+    val desktopSection : DesktopSectionScope?
+        get() = content[DesktopEntryType.DesktopEntry]?.let { DesktopSectionScope(it) }
 
     val themeSection
-        get() = content[DesktopFileType.Theme.text]?.let { ThemeSectionScope(it) }
+        get() = content[DesktopEntryType.Theme]?.let { ThemeSectionScope(it) }
 
     val isApp
-        get() = desktopSection?.Type == DesktopFileType.Application
+        get() = desktopSection?.Type == DesktopEntryType.Application
 
     val isCorrect
         get() = correctedFile.exists()
@@ -63,19 +67,19 @@ class DesktopFile(
     }
 
     fun section(
-        type: DesktopFileType,
+        type: DesktopEntryType,
         block: Section.() -> Unit
     ) = (content[type.text] ?: content.add(type.text))?.apply(block)
 
     fun desktopSection(
         block: DesktopSectionScope.() -> Unit
-    ) = section(DesktopFile.DesktopFileType.DesktopEntry) {
+    ) = section(DesktopFile.DesktopEntryType.DesktopEntry) {
         DesktopSectionScope(this).apply(block)
     }
 
     fun themeSection(
         block: ThemeSectionScope.() -> Unit
-    ) = section(DesktopFile.DesktopFileType.Theme) {
+    ) = section(DesktopFile.DesktopEntryType.Theme) {
         ThemeSectionScope(this).apply(block)
     }
 
@@ -122,8 +126,8 @@ class DesktopFile(
     class DesktopSectionScope(
         override val section: Section
     ) : ISectionScope {
-        var Type: DesktopFileType
-            get() = DesktopFileType(section[Prop_Type])
+        var Type: DesktopEntryType
+            get() = DesktopEntryType(section[Prop_Type])
             set(value) {
                 section[Prop_Type] = value.text
             }
@@ -241,7 +245,7 @@ class DesktopFile(
             }
     }
 
-    enum class DesktopFileType(val text: String) {
+    enum class DesktopEntryType(val text: String) {
         Unknown(""),
         DesktopEntry("Desktop Entry"),
         Application("Application"),
@@ -250,7 +254,7 @@ class DesktopFile(
         companion object {
             operator fun invoke(
                 value: String?
-            ): DesktopFileType = value.orEmpty().trim().let { v ->
+            ): DesktopEntryType = value.orEmpty().trim().let { v ->
                 entries.firstOrNull { e -> e.text.contentEquals(v, true) } ?: Unknown
             }
         }
@@ -297,6 +301,10 @@ class DesktopFile(
 
         val Empty = DesktopFile()
 
+        operator fun Ini.get(
+            type: DesktopEntryType
+        ) : Section? = get(type.text)
+
         fun desktopFile(
             file: File,
             block: DesktopFile.() -> Unit

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

@@ -135,8 +135,8 @@ object Custom {
         ignoreCase: Boolean = false
     ) = !startsWith(prefix, ignoreCase)
 
-    fun String.trimIsEmpty() =
-        trim().isEmpty()
+//    fun String.trimIsEmpty() =
+//        trim().isEmpty()
 
 //    fun String.trimIsNotEmpty() =
 //        trim().isNotEmpty()
@@ -147,10 +147,10 @@ object Custom {
     fun State<String>.trimIsNotEmpty() =
         value.trim().isNotEmpty()
 
-    fun String.trimStartsWith(
-        prefix: String,
-        ignoreCase: Boolean = false
-    ) = trim().startsWith(prefix, ignoreCase)
+//    fun String.trimStartsWith(
+//        prefix: String,
+//        ignoreCase: Boolean = false
+//    ) = trim().startsWith(prefix, ignoreCase)
 
 //    fun String.trimNotStartsWith(
 //        prefix: String,
@@ -174,9 +174,9 @@ object Custom {
     }
 
     fun ParsedString(
-        value: String?,
+        value: Any?,
         defaultValue: String = ""
-    ) = value ?: defaultValue
+    ) = value?.toString() ?: defaultValue
 
     fun <T> flowBlock(
         block: suspend () -> T

+ 14 - 0
src/main/kotlin/eu/mjdev/desktop/helpers/compose/Orientation.kt

@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) Milan Jurkulák 2024.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package eu.mjdev.desktop.helpers.compose;
+
+enum class Orientation {
+    Vertical,
+    Horizontal
+}

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/helpers/DottedShape.kt → src/main/kotlin/eu/mjdev/desktop/helpers/shape/DottedShape.kt

@@ -6,7 +6,7 @@
  *  w: https://mjdev.org
  */
 
-package eu.mjdev.desktop.helpers
+package eu.mjdev.desktop.helpers.shape
 
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect

+ 12 - 6
src/main/kotlin/eu/mjdev/desktop/provider/DesktopProvider.kt

@@ -1,7 +1,7 @@
 package eu.mjdev.desktop.provider
 
 import androidx.compose.runtime.*
-import androidx.compose.ui.platform.*
+import androidx.compose.ui.platform.LocalInspectionMode
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import coil3.ImageLoader
@@ -11,14 +11,20 @@ import eu.mjdev.desktop.data.DesktopFile
 import eu.mjdev.desktop.data.User
 import eu.mjdev.desktop.extensions.Compose.asyncImageLoader
 import eu.mjdev.desktop.helpers.internal.Palette
-import eu.mjdev.desktop.managers.*
 import eu.mjdev.desktop.helpers.system.OsRelease
 import eu.mjdev.desktop.helpers.system.Shell
+import eu.mjdev.desktop.managers.ConnectivityManager
+import eu.mjdev.desktop.managers.GnomeManager
+import eu.mjdev.desktop.managers.KCEFHelper
+import eu.mjdev.desktop.managers.ProcessManager
 import eu.mjdev.desktop.provider.AIProvider.AiPluginGemini
 import eu.mjdev.desktop.theme.gtk.GtkThemeHelper
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
-import java.awt.*
+import java.awt.Desktop
+import java.awt.GraphicsDevice
+import java.awt.GraphicsEnvironment
+import java.awt.Toolkit
 import java.io.File
 import java.net.URI
 import kotlin.system.exitProcess
@@ -231,13 +237,13 @@ class DesktopProvider(
     companion object {
         private val CONTROL_CENTER_PAGES = listOf(
             MainSettingsPage(),
-            EthSettingsPage(),
             WifiSettingsPage(),
+            EthSettingsPage(),
+            SoundSettingsPage(),
             BluetoothSettingsPage(),
             DisplaySettingsPage(),
-            SoundSettingsPage(),
+            DevicesPage(),
             AIPage(),
-            DevicesPage()
         )
 
         val LocalDesktop = staticCompositionLocalOf {

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/theme/gtk/GtkThemeHelper.kt

@@ -41,7 +41,7 @@ class GtkThemeHelper(
             mkDirs()
             deleteFile()
             desktopSection {
-                Type = DesktopFile.DesktopFileType.Theme
+                Type = DesktopFile.DesktopEntryType.Theme
                 Name = THEME_MJDEV
                 Comment = "dynamic system theme"
                 Encoding = Charsets.UTF_8.name()