diff --git a/FloconAndroid/sample-android-only/src/main/AndroidManifest.xml b/FloconAndroid/sample-android-only/src/main/AndroidManifest.xml
index 8131109c0..9bec47652 100644
--- a/FloconAndroid/sample-android-only/src/main/AndroidManifest.xml
+++ b/FloconAndroid/sample-android-only/src/main/AndroidManifest.xml
@@ -3,6 +3,9 @@
xmlns:tools="http://schemas.android.com/tools">
+
+
+
{
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/AppAction.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/AppAction.kt
index 697f4d4ba..334445a24 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/AppAction.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/AppAction.kt
@@ -21,4 +21,7 @@ internal sealed interface AppAction {
data object Restart : AppAction
data object Screenshoot : AppAction
+
+ data class DeviceDetail(val item: DeviceItemUiModel) : AppAction
+
}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/AppScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/AppScreen.kt
index 413b9a1a6..53ac0f57c 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/AppScreen.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/AppScreen.kt
@@ -11,6 +11,7 @@ import androidx.navigation3.scene.SinglePaneSceneStrategy
import io.github.openflocon.flocondesktop.app.ui.settings.settingsRoutes
import io.github.openflocon.flocondesktop.app.ui.view.leftpannel.LeftPanelView
import io.github.openflocon.flocondesktop.app.ui.view.topbar.MainScreenTopBar
+import io.github.openflocon.flocondesktop.device.deviceRoutes
import io.github.openflocon.flocondesktop.app.version.VersionCheckerView
import io.github.openflocon.flocondesktop.common.ui.feedback.FeedbackDisplayerView
import io.github.openflocon.flocondesktop.features.analytics.analyticsRoutes
@@ -79,7 +80,8 @@ private fun Content(
onAppSelected = { onAction(AppAction.SelectApp(it)) },
onRecordClicked = { onAction(AppAction.Record) },
onRestartClicked = { onAction(AppAction.Restart) },
- onTakeScreenshotClicked = { onAction(AppAction.Screenshoot) }
+ onTakeScreenshotClicked = { onAction(AppAction.Screenshoot) },
+ onClickDetail = { onAction(AppAction.DeviceDetail(it)) }
)
}
)
@@ -100,5 +102,6 @@ private fun Content(
tableRoutes()
settingsRoutes()
crashReporterRoutes()
+ deviceRoutes()
}
}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/AppViewModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/AppViewModel.kt
index b48672cf8..5b1f5581a 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/AppViewModel.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/AppViewModel.kt
@@ -16,6 +16,7 @@ import io.github.openflocon.flocondesktop.app.ui.model.SubScreen
import io.github.openflocon.flocondesktop.app.ui.model.leftpanel.buildMenu
import io.github.openflocon.flocondesktop.app.ui.settings.SettingsRoutes
import io.github.openflocon.flocondesktop.common.utils.stateInWhileSubscribed
+import io.github.openflocon.flocondesktop.device.DeviceRoutes
import io.github.openflocon.flocondesktop.features.analytics.AnalyticsRoutes
import io.github.openflocon.flocondesktop.features.crashreporter.CrashReporterRoutes
import io.github.openflocon.flocondesktop.features.dashboard.DashboardRoutes
@@ -112,9 +113,14 @@ internal class AppViewModel(
AppAction.Screenshoot -> onTakeScreenshot()
is AppAction.SelectApp -> onAppSelected(action)
is AppAction.SelectDevice -> onDeviceSelected(action)
+ is AppAction.DeviceDetail -> onDeviceDetail(action)
}
}
+ private fun onDeviceDetail(action: AppAction.DeviceDetail) {
+ navigationState.navigate(DeviceRoutes.Detail(action.item.id))
+ }
+
private fun onSelectMenu(action: AppAction.SelectMenu) {
contentState.update { it.copy(current = action.menu) }
navigationState.menu(
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/SettingsScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/SettingsScreen.kt
index 20cf83dca..0984f07f5 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/SettingsScreen.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/SettingsScreen.kt
@@ -133,7 +133,10 @@ private fun SettingsScreen(
}
}
FloconSection(
- title = stringResource(Res.string.settings_font_size_multiplier, uiState.fontSizeMultiplier),
+ title = stringResource(
+ Res.string.settings_font_size_multiplier,
+ uiState.fontSizeMultiplier
+ ),
initialValue = true
) {
Column(
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/leftpannel/PannelView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/leftpannel/PannelView.kt
index 5130743f0..af3ad206c 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/leftpannel/PannelView.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/leftpannel/PannelView.kt
@@ -29,9 +29,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.github.openflocon.library.designsystem.FloconTheme
-import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun PanelView(
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/MainScreenTopBar.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/MainScreenTopBar.kt
index 4a5d43c46..ba6b05a74 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/MainScreenTopBar.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/MainScreenTopBar.kt
@@ -28,6 +28,8 @@ import io.github.openflocon.flocondesktop.app.ui.view.topbar.actions.TopBarActio
import io.github.openflocon.library.designsystem.FloconTheme
import org.jetbrains.compose.resources.painterResource
+val TopBarHeight = 48.dp
+
@Composable
fun MainScreenTopBar(
modifier: Modifier = Modifier,
@@ -41,6 +43,7 @@ fun MainScreenTopBar(
recordState: RecordVideoStateUiModel,
onRecordClicked: () -> Unit,
onRestartClicked: () -> Unit,
+ onClickDetail: (DeviceItemUiModel) -> Unit,
) {
Row(
modifier = modifier
@@ -57,6 +60,7 @@ fun MainScreenTopBar(
onAppSelected = onAppSelected,
deleteDevice = deleteDevice,
deleteApp = deleteApp,
+ onClickDetail = onClickDetail
)
Spacer(modifier = Modifier.weight(1f))
TopBarActions(
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/TopBarDeviceAndAppView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/TopBarDeviceAndAppView.kt
index 5bcaa0bf0..2fe99161d 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/TopBarDeviceAndAppView.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/TopBarDeviceAndAppView.kt
@@ -6,6 +6,7 @@ package io.github.openflocon.flocondesktop.app.ui.view.topbar
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.height
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -26,6 +27,7 @@ internal fun TopBarDeviceAndAppView(
deleteDevice: (DeviceItemUiModel) -> Unit,
onAppSelected: (DeviceAppUiModel) -> Unit,
deleteApp: (DeviceAppUiModel) -> Unit,
+ onClickDetail: (DeviceItemUiModel) -> Unit,
modifier: Modifier = Modifier,
) {
Row(
@@ -37,6 +39,8 @@ internal fun TopBarDeviceAndAppView(
state = devicesState,
onDeviceSelected = onDeviceSelected,
deleteDevice = deleteDevice,
+ onClickDetail = onClickDetail,
+ modifier = Modifier.height(TopBarHeight)
)
AnimatedVisibility(devicesState is DevicesStateUiModel.WithDevices) {
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/device/TopBarDeviceDropdown.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/device/TopBarDeviceDropdown.kt
index 240c0c19d..211b11f51 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/device/TopBarDeviceDropdown.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/device/TopBarDeviceDropdown.kt
@@ -5,8 +5,8 @@ package io.github.openflocon.flocondesktop.app.ui.view.topbar.device
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExposedDropdownMenuAnchorType
import androidx.compose.material3.ExposedDropdownMenuBox
-import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -29,6 +29,7 @@ internal fun TopBarDeviceDropdown(
state: DevicesStateUiModel,
onDeviceSelected: (DeviceItemUiModel) -> Unit,
deleteDevice: (DeviceItemUiModel) -> Unit,
+ onClickDetail: (DeviceItemUiModel) -> Unit,
modifier: Modifier = Modifier,
) {
var expanded by remember { mutableStateOf(false) }
@@ -51,7 +52,9 @@ internal fun TopBarDeviceDropdown(
) {
TopBarDeviceView(
device = state.deviceSelected,
- modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable),
+ onClick = { expanded = true },
+ onClickDetail = { onClickDetail(state.deviceSelected) },
+ modifier = Modifier.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable),
)
}
}
@@ -72,6 +75,7 @@ internal fun TopBarDeviceDropdown(
onDeviceSelected(device)
expanded = false
},
+ onClickDetail = { onClickDetail(device) },
onDelete = {
deleteDevice(device)
expanded = false
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/device/TopBarDeviceView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/device/TopBarDeviceView.kt
index 652af6ee0..be0bfead0 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/device/TopBarDeviceView.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/device/TopBarDeviceView.kt
@@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.DesktopAccessDisabled
import androidx.compose.material.icons.filled.DesktopWindows
@@ -20,6 +19,8 @@ import androidx.compose.material.icons.filled.MobileOff
import androidx.compose.material.icons.filled.PhoneIphone
import androidx.compose.material.icons.filled.Smartphone
import androidx.compose.material.icons.outlined.Close
+import androidx.compose.material.icons.outlined.Details
+import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.MobileOff
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -38,7 +39,9 @@ import flocondesktop.composeapp.generated.resources.devices_disconnected
import io.github.openflocon.flocondesktop.app.ui.model.DeviceItemUiModel
import io.github.openflocon.library.designsystem.FloconTheme
import io.github.openflocon.library.designsystem.components.FloconIcon
+import io.github.openflocon.library.designsystem.components.FloconIconButton
import io.github.openflocon.library.designsystem.components.FloconSurface
+import io.github.openflocon.library.designsystem.components.FloconVerticalDivider
import org.jetbrains.compose.resources.stringResource
@Composable
@@ -46,6 +49,7 @@ internal fun TopBarDeviceView(
device: DeviceItemUiModel,
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
+ onClickDetail: (() -> Unit)? = null,
selected: Boolean = false,
onDelete: (() -> Unit)? = null,
) {
@@ -57,7 +61,7 @@ internal fun TopBarDeviceView(
else
Modifier
)
- .padding(horizontal = 8.dp, 4.dp),
+ .padding(horizontal = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
@@ -101,6 +105,7 @@ internal fun TopBarDeviceView(
}
Row(
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Column(
@@ -125,15 +130,23 @@ internal fun TopBarDeviceView(
),
)
}
+ FloconVerticalDivider(color = FloconTheme.colorPalette.secondary)
+ FloconIconButton(
+ onClick = { onClickDetail?.invoke() }
+ ) {
+ FloconIcon(
+ imageVector = Icons.Outlined.Info
+ )
+ }
if (!selected && onDelete != null) {
+ FloconVerticalDivider(color = FloconTheme.colorPalette.secondary)
Spacer(modifier = Modifier.weight(1f))
Box(
- Modifier.clip(RoundedCornerShape(4.dp))
- .background(
- Color.White.copy(alpha = 0.8f)
- ).padding(2.dp).clickable {
- onDelete()
- },
+ Modifier
+ .clip(FloconTheme.shapes.small)
+ .background(Color.White.copy(alpha = 0.8f))
+ .padding(2.dp)
+ .clickable(onClick = onDelete),
contentAlignment = Alignment.Center,
) {
FloconIcon(
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.kt
index 9165f9953..2cfcb828f 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.kt
@@ -3,6 +3,7 @@ package io.github.openflocon.flocondesktop.common.ui.window
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.FrameWindowScope
interface FloconWindowState
@@ -18,5 +19,5 @@ expect fun FloconWindow(
state: FloconWindowState,
onCloseRequest: () -> Unit,
alwaysOnTop: Boolean = false,
- content: @Composable () -> Unit
+ content: @Composable FrameWindowScope.() -> Unit,
)
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DI.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DI.kt
new file mode 100644
index 000000000..72e61d77f
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DI.kt
@@ -0,0 +1,18 @@
+package io.github.openflocon.flocondesktop.device
+
+import io.github.openflocon.flocondesktop.device.pages.battery.BatteryViewModel
+import io.github.openflocon.flocondesktop.device.pages.cpu.CpuViewModel
+import io.github.openflocon.flocondesktop.device.pages.info.InfoViewModel
+import io.github.openflocon.flocondesktop.device.pages.memory.MemoryViewModel
+import io.github.openflocon.flocondesktop.device.pages.permission.PermissionViewModel
+import org.koin.core.module.dsl.viewModelOf
+import org.koin.dsl.module
+
+internal val deviceModule = module {
+ viewModelOf(::DeviceViewModel)
+ viewModelOf(::PermissionViewModel)
+ viewModelOf(::CpuViewModel)
+ viewModelOf(::BatteryViewModel)
+ viewModelOf(::InfoViewModel)
+ viewModelOf(::MemoryViewModel)
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceAction.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceAction.kt
new file mode 100644
index 000000000..e42ee305e
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceAction.kt
@@ -0,0 +1,7 @@
+package io.github.openflocon.flocondesktop.device
+
+internal sealed interface DeviceAction {
+
+ data class SelectTab(val selected: DeviceTab) : DeviceAction
+
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceScreen.kt
new file mode 100644
index 000000000..f7da8ae15
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceScreen.kt
@@ -0,0 +1,132 @@
+package io.github.openflocon.flocondesktop.device
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import io.github.openflocon.domain.device.models.DeviceId
+import io.github.openflocon.flocondesktop.device.models.DeviceUiState
+import io.github.openflocon.flocondesktop.device.models.previewDeviceUiState
+import io.github.openflocon.flocondesktop.device.pages.battery.BatteryPage
+import io.github.openflocon.flocondesktop.device.pages.cpu.CpuPage
+import io.github.openflocon.flocondesktop.device.pages.info.InfoPage
+import io.github.openflocon.flocondesktop.device.pages.memory.MemoryPage
+import io.github.openflocon.flocondesktop.device.pages.permission.PermissionPage
+import io.github.openflocon.library.designsystem.FloconTheme
+import io.github.openflocon.library.designsystem.components.FloconCircularProgressIndicator
+import io.github.openflocon.library.designsystem.components.FloconHorizontalDivider
+import io.github.openflocon.library.designsystem.components.FloconScaffold
+import io.github.openflocon.library.designsystem.components.FloconScrollableTabRow
+import io.github.openflocon.library.designsystem.components.FloconSurface
+import io.github.openflocon.library.designsystem.components.FloconTab
+import org.koin.compose.viewmodel.koinViewModel
+import org.koin.core.parameter.parametersOf
+
+@Composable
+internal fun DeviceScreen(
+ deviceId: DeviceId
+) {
+ val viewModel = koinViewModel {
+ parametersOf(deviceId)
+ }
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
+ Content(
+ uiState = uiState,
+ onAction = viewModel::onAction
+ )
+}
+
+@Composable
+private fun Content(
+ uiState: DeviceUiState,
+ onAction: (DeviceAction) -> Unit
+) {
+ val tabs = remember { DeviceTab.entries }
+ val pagerState = rememberPagerState { tabs.size }
+
+ LaunchedEffect(uiState.contentState.selectedTab) {
+ pagerState.animateScrollToPage(uiState.contentState.selectedTab.ordinal)
+ }
+
+ FloconSurface(
+ modifier = Modifier.fillMaxSize()
+ ) {
+ FloconScaffold(
+ topBar = {
+ Column(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ FloconScrollableTabRow(
+ selectedTabIndex = uiState.contentState.selectedTab.ordinal,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ tabs.forEach { tab ->
+ FloconTab(
+ text = tab.title,
+ selected = uiState.contentState.selectedTab == tab,
+ onClick = { onAction(DeviceAction.SelectTab(tab)) },
+ selectedContentColor = FloconTheme.colorPalette.onSurface
+ )
+ }
+ }
+ FloconHorizontalDivider(
+ modifier = Modifier.fillMaxWidth(),
+ color = FloconTheme.colorPalette.primary
+ )
+ }
+ }
+ ) {
+ if (uiState.deviceSerial.isEmpty()) {
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = Modifier.fillMaxSize()
+ ) {
+ FloconCircularProgressIndicator()
+ }
+ } else {
+ HorizontalPager(
+ state = pagerState,
+ userScrollEnabled = false,
+ contentPadding = PaddingValues(8.dp),
+ pageSpacing = 8.dp,
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(it)
+ ) { index ->
+ when (tabs[index]) {
+ DeviceTab.INFORMATION -> InfoPage(uiState.deviceSerial)
+ DeviceTab.BATTERY -> BatteryPage(uiState.deviceSerial)
+ DeviceTab.CPU -> CpuPage(uiState.deviceSerial)
+ DeviceTab.MEMORY -> MemoryPage(uiState.deviceSerial)
+ DeviceTab.PERMISSION -> PermissionPage(uiState.deviceSerial)
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+@Preview
+private fun Preview() {
+ FloconTheme {
+ Content(
+ uiState = previewDeviceUiState(),
+ onAction = {}
+ )
+ }
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceTab.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceTab.kt
new file mode 100644
index 000000000..c79817e8f
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceTab.kt
@@ -0,0 +1,24 @@
+package io.github.openflocon.flocondesktop.device
+
+import io.github.openflocon.flocondesktop.device.DeviceTab.BATTERY
+import io.github.openflocon.flocondesktop.device.DeviceTab.CPU
+import io.github.openflocon.flocondesktop.device.DeviceTab.INFORMATION
+import io.github.openflocon.flocondesktop.device.DeviceTab.MEMORY
+import io.github.openflocon.flocondesktop.device.DeviceTab.PERMISSION
+
+enum class DeviceTab {
+ CPU,
+ MEMORY,
+ PERMISSION,
+ INFORMATION,
+ BATTERY
+}
+
+val DeviceTab.title: String
+ get() = when (this) {
+ INFORMATION -> "Info"
+ BATTERY -> "Battery"
+ CPU -> "CPU"
+ MEMORY -> "Memory"
+ PERMISSION -> "Permission"
+ }
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceViewModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceViewModel.kt
new file mode 100644
index 000000000..ea4a126b9
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceViewModel.kt
@@ -0,0 +1,70 @@
+package io.github.openflocon.flocondesktop.device
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import io.github.openflocon.domain.adb.usecase.GetDeviceSerialUseCase
+import io.github.openflocon.domain.adb.usecase.SendCommandUseCase
+import io.github.openflocon.domain.common.getOrNull
+import io.github.openflocon.flocondesktop.device.models.ContentUiState
+import io.github.openflocon.flocondesktop.device.models.DeviceUiState
+import io.github.openflocon.flocondesktop.device.pages.memory.MemoryItem
+import io.github.openflocon.flocondesktop.device.pages.memory.MemoryUiState
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+
+internal class DeviceViewModel(
+ val deviceId: String,
+ val sendCommandUseCase: SendCommandUseCase,
+ val deviceSerialUseCase: GetDeviceSerialUseCase
+) : ViewModel() {
+
+ private val contentState = MutableStateFlow(ContentUiState(selectedTab = DeviceTab.entries.first()))
+ private val deviceSerial = MutableStateFlow("")
+
+ val uiState = combine(
+ contentState,
+ deviceSerial
+ ) { states ->
+ DeviceUiState(
+ contentState = states[0] as ContentUiState,
+ deviceSerial = states[1] as String
+ )
+ }
+ .stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(5_000),
+ initialValue = DeviceUiState(
+ contentState = contentState.value,
+ deviceSerial = deviceSerial.value
+ )
+ )
+
+ init {
+ viewModelScope.launch(Dispatchers.IO) {
+ deviceSerial.value = deviceSerialUseCase(deviceId)
+ }
+ }
+
+ fun onAction(action: DeviceAction) {
+ when (action) {
+ is DeviceAction.SelectTab -> onSelect(action)
+ }
+ }
+
+ private fun onSelect(action: DeviceAction.SelectTab) {
+ contentState.update { it.copy(selectedTab = action.selected) }
+ }
+
+ private suspend fun sendCommand(vararg args: String): String {
+ return sendCommandUseCase(deviceSerial.value, *args)
+ .getOrNull()
+ .orEmpty()
+ .removeSuffix("\n")
+ }
+
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/Navigation.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/Navigation.kt
new file mode 100644
index 000000000..7994a149c
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/Navigation.kt
@@ -0,0 +1,24 @@
+package io.github.openflocon.flocondesktop.device
+
+import androidx.navigation3.runtime.EntryProviderScope
+import io.github.openflocon.domain.device.models.DeviceId
+import io.github.openflocon.navigation.FloconRoute
+import io.github.openflocon.navigation.scene.WindowSceneStrategy
+import kotlinx.serialization.Serializable
+
+sealed interface DeviceRoutes : FloconRoute {
+
+ @Serializable
+ data class Detail(val id: DeviceId) : DeviceRoutes
+
+}
+
+fun EntryProviderScope.deviceRoutes() {
+ entry(
+ metadata = WindowSceneStrategy.window()
+ ) {
+ DeviceScreen(
+ deviceId = it.id
+ )
+ }
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/PageViewModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/PageViewModel.kt
new file mode 100644
index 000000000..3d0a1a65c
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/PageViewModel.kt
@@ -0,0 +1,19 @@
+package io.github.openflocon.flocondesktop.device
+
+import androidx.lifecycle.ViewModel
+import io.github.openflocon.domain.adb.usecase.SendCommandUseCase
+import io.github.openflocon.domain.common.getOrNull
+
+abstract class PageViewModel(
+ protected val deviceSerial: String,
+ private val sendCommandUseCase: SendCommandUseCase
+) : ViewModel() {
+
+ protected suspend fun sendCommand(vararg args: String): String {
+ return sendCommandUseCase(deviceSerial, *args)
+ .getOrNull()
+ .orEmpty()
+ .removeSuffix("\n")
+ }
+
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/ContentUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/ContentUiState.kt
new file mode 100644
index 000000000..32b26ab94
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/ContentUiState.kt
@@ -0,0 +1,13 @@
+package io.github.openflocon.flocondesktop.device.models
+
+import androidx.compose.runtime.Immutable
+import io.github.openflocon.flocondesktop.device.DeviceTab
+
+@Immutable
+data class ContentUiState(
+ val selectedTab: DeviceTab
+)
+
+fun previewContentUiState() = ContentUiState(
+ selectedTab = DeviceTab.INFORMATION
+)
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/DeviceUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/DeviceUiState.kt
new file mode 100644
index 000000000..af343f144
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/DeviceUiState.kt
@@ -0,0 +1,14 @@
+package io.github.openflocon.flocondesktop.device.models
+
+import androidx.compose.runtime.Immutable
+
+@Immutable
+data class DeviceUiState(
+ val deviceSerial: String,
+ val contentState: ContentUiState
+)
+
+internal fun previewDeviceUiState() = DeviceUiState(
+ deviceSerial = "",
+ contentState = previewContentUiState()
+)
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryAction.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryAction.kt
new file mode 100644
index 000000000..c2b41fa4c
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryAction.kt
@@ -0,0 +1,3 @@
+package io.github.openflocon.flocondesktop.device.pages.battery
+
+sealed interface BatteryAction
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryPage.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryPage.kt
new file mode 100644
index 000000000..ba34f6463
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryPage.kt
@@ -0,0 +1,110 @@
+package io.github.openflocon.flocondesktop.device.pages.battery
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import io.github.openflocon.library.designsystem.FloconTheme
+import io.github.openflocon.library.designsystem.components.FloconTextValue
+import org.koin.compose.viewmodel.koinViewModel
+import org.koin.core.parameter.parametersOf
+
+@Composable
+internal fun BatteryPage(
+ deviceSerial: String
+) {
+ val viewModel = koinViewModel { parametersOf(deviceSerial) }
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
+ Content(
+ uiState = uiState,
+ onAction = viewModel::onAction
+ )
+}
+
+@Composable
+private fun Content(
+ uiState: BatteryUiState,
+ onAction: (BatteryAction) -> Unit
+) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .clip(FloconTheme.shapes.medium)
+ .background(FloconTheme.colorPalette.primary)
+ .border(
+ width = 1.dp,
+ color = FloconTheme.colorPalette.secondary,
+ shape = FloconTheme.shapes.medium
+ )
+ .verticalScroll(rememberScrollState())
+ .padding(8.dp)
+ ) {
+ FloconTextValue(label = "Technology", value = uiState.technology.orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary)
+ FloconTextValue(label = "Health", value = uiState.health?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary)
+ FloconTextValue(
+ label = "Capacity",
+ value = uiState.capacityLevel?.toString().orEmpty(),
+ valueContainerColor = FloconTheme.colorPalette.secondary
+ )
+ FloconTextValue(label = "AC Powered", value = uiState.acPowered?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary)
+ FloconTextValue(
+ label = "Charge counter",
+ value = uiState.chargeCounter?.toString().orEmpty(),
+ valueContainerColor = FloconTheme.colorPalette.secondary
+ )
+ FloconTextValue(
+ label = "Charging policy",
+ value = uiState.chargingPolicy?.toString().orEmpty(),
+ valueContainerColor = FloconTheme.colorPalette.secondary
+ )
+ FloconTextValue(
+ label = "Charging state",
+ value = uiState.chargingState?.toString().orEmpty(),
+ valueContainerColor = FloconTheme.colorPalette.secondary
+ )
+ FloconTextValue(
+ label = "Max Charging current",
+ value = uiState.maxChargingCurrent?.toString().orEmpty(),
+ valueContainerColor = FloconTheme.colorPalette.secondary
+ )
+ FloconTextValue(
+ label = "Max Charging voltage",
+ value = uiState.maxChargingVoltage?.toString().orEmpty(),
+ valueContainerColor = FloconTheme.colorPalette.secondary
+ )
+ FloconTextValue(
+ label = "Dock powered",
+ value = uiState.dockPowered?.toString().orEmpty(),
+ valueContainerColor = FloconTheme.colorPalette.secondary
+ )
+ FloconTextValue(label = "Level", value = uiState.level?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary)
+ FloconTextValue(label = "Voltage", value = uiState.voltage?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary)
+ FloconTextValue(
+ label = "Temperature",
+ value = uiState.temperature?.toString().orEmpty(),
+ valueContainerColor = FloconTheme.colorPalette.secondary
+ )
+ FloconTextValue(
+ label = "Wireless powered",
+ value = uiState.wirelessPowered?.toString().orEmpty(),
+ valueContainerColor = FloconTheme.colorPalette.secondary
+ )
+ FloconTextValue(label = "Scale", value = uiState.scale?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary)
+ FloconTextValue(
+ label = "USB Powered",
+ value = uiState.usbPowered?.toString().orEmpty(),
+ valueContainerColor = FloconTheme.colorPalette.secondary
+ )
+ FloconTextValue(label = "Present", value = uiState.present?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary)
+ }
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryUiState.kt
new file mode 100644
index 000000000..febb56a00
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryUiState.kt
@@ -0,0 +1,46 @@
+package io.github.openflocon.flocondesktop.device.pages.battery
+
+import androidx.compose.runtime.Immutable
+
+@Immutable
+data class BatteryUiState(
+ val acPowered: Boolean?,
+ val usbPowered: Boolean?,
+ val wirelessPowered: Boolean?,
+ val dockPowered: Boolean?,
+ val maxChargingCurrent: Int?,
+ val maxChargingVoltage: Int?,
+ val chargeCounter: Int?,
+ val status: Int?,
+ val health: Int?,
+ val present: Boolean?,
+ val level: Int?,
+ val scale: Int?,
+ val voltage: Int?,
+ val temperature: Int?,
+ val technology: String?,
+ val chargingState: Int?,
+ val chargingPolicy: Int?,
+ val capacityLevel: Int?
+)
+
+fun previewBatteryUiState() = BatteryUiState(
+ acPowered = true,
+ usbPowered = false,
+ wirelessPowered = true,
+ dockPowered = false,
+ maxChargingCurrent = 100,
+ maxChargingVoltage = 100,
+ chargeCounter = 100,
+ status = 100,
+ health = 100,
+ present = true,
+ level = 100,
+ scale = 100,
+ voltage = 100,
+ temperature = 100,
+ technology = "Technology",
+ chargingState = 100,
+ chargingPolicy = 100,
+ capacityLevel = 100
+)
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryViewModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryViewModel.kt
new file mode 100644
index 000000000..2e33b9943
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryViewModel.kt
@@ -0,0 +1,101 @@
+package io.github.openflocon.flocondesktop.device.pages.battery
+
+import androidx.lifecycle.viewModelScope
+import io.github.openflocon.domain.adb.usecase.SendCommandUseCase
+import io.github.openflocon.flocondesktop.device.PageViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+
+class BatteryViewModel(
+ deviceSerial: String,
+ sendCommandUseCase: SendCommandUseCase
+) : PageViewModel(deviceSerial, sendCommandUseCase) {
+
+ private val _uiState = MutableStateFlow(
+ BatteryUiState(
+ acPowered = null,
+ usbPowered = null,
+ wirelessPowered = null,
+ dockPowered = null,
+ maxChargingCurrent = null,
+ maxChargingVoltage = null,
+ chargeCounter = null,
+ status = null,
+ health = null,
+ present = null,
+ level = null,
+ scale = null,
+ voltage = null,
+ temperature = null,
+ technology = null,
+ chargingState = null,
+ chargingPolicy = null,
+ capacityLevel = null
+ )
+ )
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ init {
+ refreshBattery()
+ }
+
+ fun onAction(action: BatteryAction) {
+
+ }
+
+ private fun refreshBattery() {
+ viewModelScope.launch(Dispatchers.IO) {
+ val batteryInfo = sendCommand("shell", "dumpsys", "battery")
+
+ _uiState.update {
+ it.copy(
+ acPowered = AC_POWERED_REGEX.toRegex().find(batteryInfo)?.groupValues?.get(1)?.toBoolean() ?: false,
+ usbPowered = USB_POWERED_REGEX.toRegex().find(batteryInfo)?.groupValues?.get(1)?.toBoolean() ?: false,
+ wirelessPowered = WIRELESS_POWERED_REGEX.toRegex().find(batteryInfo)?.groupValues?.get(1)?.toBoolean()
+ ?: false,
+ dockPowered = DOCK_POWERED_REGEX.toRegex().find(batteryInfo)?.groupValues?.get(1)?.toBoolean() ?: false,
+ maxChargingCurrent = MAX_CHARGING_CURRENT_REGEX.toRegex().find(batteryInfo)?.groupValues?.get(1)?.toIntOrNull(),
+ maxChargingVoltage = MAX_CHARGING_VOLTAGE_REGEX.toRegex().find(batteryInfo)?.groupValues?.get(1)?.toIntOrNull(),
+ chargeCounter = CHARGE_COUNTER_REGEX.toRegex().find(batteryInfo)?.groupValues?.get(1)?.toIntOrNull(),
+ status = STATUS_REGEX.toRegex().find(batteryInfo)?.groupValues?.get(1)?.toIntOrNull(),
+ health = HEALTH_REGEX.toRegex().find(batteryInfo)?.groupValues?.get(1)?.toIntOrNull(),
+ present = PRESENT_REGEX.toRegex().find(batteryInfo)?.groupValues?.get(1)?.toBoolean() ?: false,
+ level = LEVEL_REGEX.toRegex().find(batteryInfo)?.groupValues?.get(1)?.toIntOrNull(),
+ scale = SCALE_REGEX.toRegex().find(batteryInfo)?.groupValues?.get(1)?.toIntOrNull(),
+ voltage = VOLTAGE_REGEX.toRegex().find(batteryInfo)?.groupValues?.get(1)?.toIntOrNull(),
+ temperature = TEMPERATURE_REGEX.toRegex().find(batteryInfo)?.groupValues?.get(1)?.toIntOrNull(),
+ technology = TECHNOLOGY_REGEX.toRegex().find(batteryInfo)?.groupValues?.get(1),
+ chargingState = CHARGING_STATE_REGEX.toRegex().find(batteryInfo)?.groupValues?.get(1)?.toIntOrNull(),
+ chargingPolicy = CHARGING_POLICY_REGEX.toRegex().find(batteryInfo)?.groupValues?.get(1)?.toIntOrNull(),
+ capacityLevel = CAPACITY_LEVEL_REGEX.toRegex().find(batteryInfo)?.groupValues?.get(1)?.toIntOrNull()
+ )
+ }
+ }
+ }
+
+ companion object {
+ private const val AC_POWERED_REGEX = """AC powered:\s+(true|false)"""
+ private const val USB_POWERED_REGEX = """USB powered:\s+(true|false)"""
+ private const val WIRELESS_POWERED_REGEX = """Wireless powered:\s+(true|false)"""
+ private const val DOCK_POWERED_REGEX = """Dock powered:\s+(true|false)"""
+ private const val MAX_CHARGING_CURRENT_REGEX = """Max charging current:\s+(\d+)"""
+ private const val MAX_CHARGING_VOLTAGE_REGEX = """Max charging voltage:\s+(\d+)"""
+ private const val CHARGE_COUNTER_REGEX = """Charge counter:\s+(\d+)"""
+ private const val STATUS_REGEX = """status:\s+(\d+)"""
+ private const val HEALTH_REGEX = """health:\s+(\d+)"""
+ private const val PRESENT_REGEX = """present:\s+(true|false)"""
+ private const val LEVEL_REGEX = """level:\s+(\d+)"""
+ private const val SCALE_REGEX = """scale:\s+(\d+)"""
+ private const val VOLTAGE_REGEX = """voltage:\s+(\d+)"""
+ private const val TEMPERATURE_REGEX = """temperature:\s+(\d+)"""
+ private const val TECHNOLOGY_REGEX = """technology:\s+([a-zA-Z-]+)"""
+ private const val CHARGING_STATE_REGEX = """Charging state:\s+(\d+)"""
+ private const val CHARGING_POLICY_REGEX = """Charging policy:\s+(\d+)"""
+ private const val CAPACITY_LEVEL_REGEX = """Capacity level:\s+(\d+)"""
+ }
+
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuAction.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuAction.kt
new file mode 100644
index 000000000..8b2ca2081
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuAction.kt
@@ -0,0 +1,3 @@
+package io.github.openflocon.flocondesktop.device.pages.cpu
+
+sealed interface CpuAction
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuPage.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuPage.kt
new file mode 100644
index 000000000..175baa0a8
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuPage.kt
@@ -0,0 +1,156 @@
+package io.github.openflocon.flocondesktop.device.pages.cpu
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import io.github.openflocon.library.designsystem.FloconTheme
+import io.github.openflocon.library.designsystem.components.FloconHorizontalDivider
+import org.koin.compose.viewmodel.koinViewModel
+import org.koin.core.parameter.parametersOf
+
+@Composable
+internal fun CpuPage(
+ deviceSerial: String
+) {
+ val viewModel = koinViewModel { parametersOf(deviceSerial) }
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
+ Content(
+ uiState = uiState,
+ onAction = viewModel::onAction
+ )
+}
+
+@Composable
+private fun Content(
+ uiState: CpuUiState,
+ onAction: (CpuAction) -> Unit
+) {
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ .clip(FloconTheme.shapes.medium)
+ .background(FloconTheme.colorPalette.primary)
+ .border(
+ width = 1.dp,
+ color = FloconTheme.colorPalette.secondary,
+ shape = FloconTheme.shapes.medium
+ )
+ ) {
+ stickyHeader {
+ BasicItem(
+ cpuUsage = "Usage (%)",
+ userPercentage = "User (%)",
+ kernelPercentage = "Kernel (%)",
+ packageName = "Package Name",
+ pId = "pId",
+ majorFaults = "Major Faults",
+ minorFaults = "Minor Faults",
+ style = FloconTheme.typography.labelSmall
+ .copy(fontWeight = FontWeight.Bold)
+ )
+ FloconHorizontalDivider(
+ color = FloconTheme.colorPalette.secondary
+ )
+ }
+ itemsIndexed(
+ items = uiState.list,
+ key = { _, item -> item.pId }
+ ) { index, item ->
+ BasicItem(
+ cpuUsage = item.cpuUsage.toString(),
+ userPercentage = item.userPercentage.toString(),
+ kernelPercentage = item.kernelPercentage.toString(),
+ pId = item.pId.toString(),
+ packageName = item.packageName,
+ majorFaults = item.majorFaults?.toString().orEmpty(),
+ minorFaults = item.minorFaults?.toString().orEmpty(),
+ style = FloconTheme.typography.labelSmall
+ )
+ if (index != uiState.list.lastIndex) {
+ FloconHorizontalDivider(
+ color = FloconTheme.colorPalette.secondary
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun BasicItem(
+ cpuUsage: String,
+ userPercentage: String,
+ kernelPercentage: String,
+ pId: String,
+ packageName: String,
+ majorFaults: String,
+ minorFaults: String,
+ style: TextStyle
+) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(FloconTheme.colorPalette.primary)
+ .padding(4.dp)
+ ) {
+ Text(
+ text = cpuUsage,
+ style = style,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.weight(.2f)
+ )
+ Text(
+ text = userPercentage,
+ style = style,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.weight(.2f)
+ )
+ Text(
+ text = kernelPercentage,
+ style = style,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.weight(.2f)
+ )
+ Text(
+ text = pId,
+ style = style,
+ modifier = Modifier.weight(.2f)
+ )
+ Text(
+ text = packageName,
+ style = style,
+ modifier = Modifier.weight(1f)
+ )
+ Text(
+ text = majorFaults,
+ style = style,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.weight(.2f)
+ )
+ Text(
+ text = minorFaults,
+ style = style,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.weight(.2f)
+ )
+ }
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuUiState.kt
new file mode 100644
index 000000000..27ee7f18e
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuUiState.kt
@@ -0,0 +1,23 @@
+package io.github.openflocon.flocondesktop.device.pages.cpu
+
+import androidx.compose.runtime.Immutable
+
+@Immutable
+data class CpuUiState(
+ val list: List
+)
+
+@Immutable
+data class CpuItem(
+ val cpuUsage: Double,
+ val pId: Int,
+ val packageName: String,
+ val userPercentage: Double,
+ val kernelPercentage: Double,
+ val minorFaults: Int?,
+ val majorFaults: Int?
+)
+
+fun previewCpuUiState() = CpuUiState(
+ list = emptyList()
+)
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuViewModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuViewModel.kt
new file mode 100644
index 000000000..43c8c9f5a
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuViewModel.kt
@@ -0,0 +1,66 @@
+package io.github.openflocon.flocondesktop.device.pages.cpu
+
+import androidx.lifecycle.viewModelScope
+import io.github.openflocon.domain.adb.usecase.SendCommandUseCase
+import io.github.openflocon.flocondesktop.device.PageViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+
+class CpuViewModel(
+ deviceSerial: String,
+ sendCommandUseCase: SendCommandUseCase
+) : PageViewModel(deviceSerial, sendCommandUseCase) {
+
+ private val _uiState = MutableStateFlow(CpuUiState(emptyList()))
+ val uiState = _uiState.asStateFlow()
+
+ init {
+ refreshCpu()
+ }
+
+ fun onAction(action: CpuAction) {
+
+ }
+
+ private fun refreshCpu() {
+ viewModelScope.launch(Dispatchers.IO) {
+ val output = sendCommand("shell", "dumpsys", "cpuinfo")
+ val regex = CPU_REGEX.toRegex()
+ val items = output.lineSequence()
+ .mapNotNull { regex.find(it) }
+ .mapNotNull {
+ try {
+ val packageName = it.groupValues[2].split("/")
+
+ CpuItem(
+ cpuUsage = it.groupValues[1].toDoubleOrNull() ?: return@mapNotNull null,
+ packageName = packageName[1],
+ pId = packageName[0].toIntOrNull() ?: return@mapNotNull null,
+ userPercentage = it.groupValues[3].toDoubleOrNull() ?: return@mapNotNull null,
+ kernelPercentage = it.groupValues[4].toDoubleOrNull() ?: return@mapNotNull null,
+ minorFaults = it.groupValues[5].toIntOrNull(),
+ majorFaults = it.groupValues[6].toIntOrNull()
+ )
+ } catch (e: NumberFormatException) {
+ // Handle parsing errors gracefully (e.g., log the error)
+ null
+ }
+ }
+ .sortedByDescending(CpuItem::cpuUsage)
+ .distinctBy(CpuItem::packageName)
+ .toList()
+
+ _uiState.update { it.copy(list = items) }
+ }
+ }
+
+ companion object {
+ // CPU
+ private const val CPU_REGEX =
+ """(\d+(?:\.\d+)?)%\s+([^:]+):\s+(\d+(?:\.\d+)?)%\s+user\s+\+\s+(\d+(?:\.\d+)?)%\s+kernel\s+/ faults:\s+(\d+)\s+minor\s+(\d+)\s+major"""
+ }
+
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoAction.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoAction.kt
new file mode 100644
index 000000000..8b3f63c96
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoAction.kt
@@ -0,0 +1,4 @@
+package io.github.openflocon.flocondesktop.device.pages.info
+
+interface InfoAction {
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoPage.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoPage.kt
new file mode 100644
index 000000000..a35ce7243
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoPage.kt
@@ -0,0 +1,117 @@
+package io.github.openflocon.flocondesktop.device.pages.info
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import io.github.openflocon.library.designsystem.FloconTheme
+import io.github.openflocon.library.designsystem.components.FloconSection
+import io.github.openflocon.library.designsystem.components.FloconTextValue
+import org.koin.compose.viewmodel.koinViewModel
+import org.koin.core.parameter.parametersOf
+
+@Composable
+internal fun InfoPage(
+ deviceSerial: String
+) {
+ val viewModel = koinViewModel { parametersOf(deviceSerial) }
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
+ Content(
+ uiState = uiState,
+ onAction = viewModel::onAction
+ )
+}
+
+@Composable
+private fun Content(
+ uiState: InfoUiState,
+ onAction: (InfoAction) -> Unit
+) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ ) {
+ General(uiState)
+ }
+}
+
+@Composable
+private fun General(
+ state: InfoUiState
+) {
+ Section(
+ title = "General"
+ ) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ modifier = Modifier.padding(8.dp)
+ ) {
+ FloconTextValue(
+ label = "Brand",
+ value = state.brand,
+ valueContainerColor = FloconTheme.colorPalette.secondary
+ )
+ FloconTextValue(
+ label = "Battery",
+ value = state.battery,
+ valueContainerColor = FloconTheme.colorPalette.secondary
+ )
+ FloconTextValue(
+ label = "Serial number",
+ value = state.serialNumber,
+ valueContainerColor = FloconTheme.colorPalette.secondary
+ )
+ FloconTextValue(
+ label = "Version - Release",
+ value = state.versionRelease,
+ valueContainerColor = FloconTheme.colorPalette.secondary
+ )
+ FloconTextValue(
+ label = "Version - Sdk",
+ value = state.versionSdk,
+ valueContainerColor = FloconTheme.colorPalette.secondary
+ )
+ }
+ }
+}
+
+@Composable
+private fun Section(
+ title: String,
+ content: @Composable ColumnScope.() -> Unit
+) {
+ FloconSection(
+ title = title,
+ initialValue = true,
+ modifier = Modifier
+ .fillMaxWidth()
+ .clip(FloconTheme.shapes.medium)
+ .background(FloconTheme.colorPalette.primary)
+ .border(
+ width = 1.dp,
+ color = FloconTheme.colorPalette.secondary,
+ shape = FloconTheme.shapes.medium
+ )
+ ) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ modifier = Modifier.padding(8.dp),
+ content = content
+ )
+ }
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoUiState.kt
new file mode 100644
index 000000000..d6ca00dc0
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoUiState.kt
@@ -0,0 +1,22 @@
+package io.github.openflocon.flocondesktop.device.pages.info
+
+import androidx.compose.runtime.Immutable
+
+@Immutable
+data class InfoUiState(
+ val model: String,
+ val brand: String,
+ val versionRelease: String,
+ val versionSdk: String,
+ val serialNumber: String,
+ val battery: String
+)
+
+fun previewInfoUiState() = InfoUiState(
+ model = "",
+ brand = "",
+ versionRelease = "",
+ versionSdk = "",
+ serialNumber = "",
+ battery = ""
+)
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoViewModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoViewModel.kt
new file mode 100644
index 000000000..01d834a05
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoViewModel.kt
@@ -0,0 +1,44 @@
+package io.github.openflocon.flocondesktop.device.pages.info
+
+import androidx.lifecycle.viewModelScope
+import io.github.openflocon.domain.adb.usecase.SendCommandUseCase
+import io.github.openflocon.flocondesktop.device.PageViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+
+class InfoViewModel(
+ deviceSerial: String,
+ sendCommandUseCase: SendCommandUseCase
+) : PageViewModel(deviceSerial, sendCommandUseCase) {
+
+ private val _uiState = MutableStateFlow(
+ InfoUiState(model = "", brand = "", versionRelease = "", versionSdk = "", serialNumber = "", battery = "")
+ )
+ val uiState = _uiState.asStateFlow()
+
+ init {
+ deviceInfo()
+ }
+
+ fun onAction(action: InfoAction) {
+
+ }
+
+ private fun deviceInfo() {
+ viewModelScope.launch(Dispatchers.IO) {
+ _uiState.update { state ->
+ state.copy(
+ model = sendCommand("shell", "getprop", "ro.product.model"),
+ brand = sendCommand("shell", "getprop", "ro.product.brand"),
+ versionRelease = sendCommand("shell", "getprop", "ro.build.version.release"),
+ versionSdk = sendCommand("shell", "getprop", "ro.build.version.sdk"),
+ serialNumber = sendCommand("shell", "getprop", "ro.serialno")
+ )
+ }
+ }
+ }
+
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryAction.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryAction.kt
new file mode 100644
index 000000000..c2ead131a
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryAction.kt
@@ -0,0 +1,3 @@
+package io.github.openflocon.flocondesktop.device.pages.memory
+
+sealed interface MemoryAction
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryPage.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryPage.kt
new file mode 100644
index 000000000..8dadde400
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryPage.kt
@@ -0,0 +1,117 @@
+package io.github.openflocon.flocondesktop.device.pages.memory
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import io.github.openflocon.library.designsystem.FloconTheme
+import io.github.openflocon.library.designsystem.components.FloconHorizontalDivider
+import org.koin.compose.viewmodel.koinViewModel
+import org.koin.core.parameter.parametersOf
+
+@Composable
+internal fun MemoryPage(
+ deviceSerial: String
+) {
+ val viewModel = koinViewModel { parametersOf(deviceSerial) }
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
+ Content(
+ uiState = uiState,
+ onAction = viewModel::onAction
+ )
+}
+
+@Composable
+private fun Content(
+ uiState: MemoryUiState,
+ onAction: (MemoryAction) -> Unit
+) {
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ .clip(FloconTheme.shapes.medium)
+ .background(FloconTheme.colorPalette.primary)
+ .border(
+ width = 1.dp,
+ color = FloconTheme.colorPalette.secondary,
+ shape = FloconTheme.shapes.medium
+ )
+ ) {
+ stickyHeader {
+ BasicItem(
+ memoryUsage = "Usage",
+ processName = "Package name",
+ pId = "pId",
+ style = FloconTheme.typography.labelSmall.copy(fontWeight = FontWeight.Bold)
+ )
+ }
+ itemsIndexed(
+ items = uiState.list,
+ key = { _, item -> item.pid }
+ ) { index, item ->
+ BasicItem(
+ memoryUsage = item.memoryUsage,
+ pId = item.pid.toString(),
+ processName = item.processName,
+ style = FloconTheme.typography.labelSmall
+ )
+ if (index != uiState.list.lastIndex) {
+ FloconHorizontalDivider(
+ color = FloconTheme.colorPalette.secondary
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun BasicItem(
+ memoryUsage: String,
+ processName: String,
+ pId: String,
+ style: TextStyle
+) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(FloconTheme.colorPalette.primary)
+ .padding(4.dp)
+ ) {
+ Text(
+ text = memoryUsage,
+ style = style,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.weight(.2f)
+ )
+ Text(
+ text = pId,
+ style = style,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.weight(.2f)
+ )
+ Text(
+ text = processName,
+ style = style,
+ modifier = Modifier.weight(1f)
+ )
+ }
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryUiState.kt
new file mode 100644
index 000000000..997d7d928
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryUiState.kt
@@ -0,0 +1,19 @@
+package io.github.openflocon.flocondesktop.device.pages.memory
+
+import androidx.compose.runtime.Immutable
+
+@Immutable
+data class MemoryUiState(
+ val list: List
+)
+
+@Immutable
+data class MemoryItem(
+ val memoryUsage: String,
+ val processName: String,
+ val pid: Int
+)
+
+fun previewMemoryUiState() = MemoryUiState(
+ list = emptyList()
+)
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryViewModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryViewModel.kt
new file mode 100644
index 000000000..538037cc9
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryViewModel.kt
@@ -0,0 +1,79 @@
+package io.github.openflocon.flocondesktop.device.pages.memory
+
+import androidx.lifecycle.viewModelScope
+import io.github.openflocon.domain.adb.usecase.SendCommandUseCase
+import io.github.openflocon.flocondesktop.device.PageViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import kotlin.time.Duration.Companion.seconds
+
+class MemoryViewModel(
+ deviceSerial: String,
+ sendCommandUseCase: SendCommandUseCase
+) : PageViewModel(deviceSerial, sendCommandUseCase) {
+
+ private val _uiState = MutableStateFlow(MemoryUiState(emptyList()))
+ val uiState = _uiState.asStateFlow()
+
+ init {
+ viewModelScope.launch(Dispatchers.IO) {
+ while (true) {
+ refreshMemory()
+ delay(10.seconds)
+ }
+ }
+ }
+
+ fun onAction(action: MemoryAction) {
+
+ }
+
+ private suspend fun refreshMemory() {
+ val output = sendCommand("shell", "dumpsys", "meminfo")
+ val regex = MEM_REGEX.toRegex()
+ val items = output.lineSequence()
+ .map { it.trim() }
+ .mapNotNull { regex.find(it) }
+ .mapNotNull {
+ try {
+ MemoryItem(
+ memoryUsage = formatMemoryUsage(
+ memoryUsageKB = it.groupValues[1].replace(",", "").toDoubleOrNull()
+ ),
+ processName = it.groupValues[2],
+ pid = it.groupValues[3].toIntOrNull() ?: return@mapNotNull null
+ )
+ } catch (e: NumberFormatException) {
+ // Handle parsing errors gracefully (e.g., log the error)
+ null
+ }
+ }
+ .toList()
+
+ _uiState.update { it.copy(list = items) }
+ }
+
+ private fun formatMemoryUsage(memoryUsageKB: Double?): String {
+ if (memoryUsageKB == null) {
+ return "N/A" // Or handle null case as appropriate
+ }
+
+ val memoryUsage = memoryUsageKB * 1024 // Convert KB to bytes
+
+ return when {
+ memoryUsage < 1024 -> String.format("%.2f B", memoryUsage)
+ memoryUsage < 1024 * 1024 -> String.format("%.2f KB", memoryUsage / 1024)
+ memoryUsage < 1024 * 1024 * 1024 -> String.format("%.2f MB", memoryUsage / (1024 * 1024))
+ else -> String.format("%.2f GB", memoryUsage / (1024 * 1024 * 1024))
+ }
+ }
+
+ companion object {
+ private const val MEM_REGEX = """([\d,]+)K:\s+([a-zA-Z0-9._:-]+)\s+$${"pid"}\s+(\d+)(?:\s+/\s+([a-zA-Z\s]+))?$"""
+ }
+
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionAction.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionAction.kt
new file mode 100644
index 000000000..f2698765b
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionAction.kt
@@ -0,0 +1,8 @@
+package io.github.openflocon.flocondesktop.device.pages.permission
+
+sealed interface PermissionAction {
+
+ data class ChangePermission(val permission: String, val granted: Boolean) : PermissionAction
+
+
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionPage.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionPage.kt
new file mode 100644
index 000000000..3d21689d9
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionPage.kt
@@ -0,0 +1,94 @@
+package io.github.openflocon.flocondesktop.device.pages.permission
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import io.github.openflocon.library.designsystem.FloconTheme
+import io.github.openflocon.library.designsystem.components.FloconCheckbox
+import io.github.openflocon.library.designsystem.components.FloconHorizontalDivider
+import org.koin.compose.viewmodel.koinViewModel
+import org.koin.core.parameter.parametersOf
+
+@Composable
+internal fun PermissionPage(
+ deviceSerial: String
+) {
+ val viewModel = koinViewModel { parametersOf(deviceSerial) }
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
+ Content(
+ uiState = uiState,
+ onAction = viewModel::onAction
+ )
+}
+
+@Composable
+private fun Content(
+ uiState: PermissionUiState,
+ onAction: (PermissionAction) -> Unit
+) {
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ .clip(FloconTheme.shapes.medium)
+ .background(FloconTheme.colorPalette.primary)
+ .border(
+ width = 1.dp,
+ color = FloconTheme.colorPalette.secondary,
+ shape = FloconTheme.shapes.medium
+ )
+ ) {
+ itemsIndexed(
+ items = uiState.list,
+ key = { _, item -> item.name }
+ ) { index, item ->
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .fillMaxWidth()
+ .clip(FloconTheme.shapes.medium)
+ .clickable(onClick = {
+ onAction(
+ PermissionAction.ChangePermission(
+ permission = item.name,
+ granted = item.granted
+ )
+ )
+ })
+ .padding(vertical = 4.dp, horizontal = 8.dp)
+ ) {
+ Text(
+ text = item.name,
+ style = FloconTheme.typography.labelSmall,
+ modifier = Modifier.weight(1f)
+ )
+ FloconCheckbox(
+ checked = item.granted,
+ onCheckedChange = null,
+ uncheckedColor = FloconTheme.colorPalette.secondary
+ )
+ }
+ if (index != uiState.list.lastIndex) {
+ FloconHorizontalDivider(
+ color = FloconTheme.colorPalette.secondary
+ )
+ }
+ }
+ }
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionUiState.kt
new file mode 100644
index 000000000..a66d5024b
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionUiState.kt
@@ -0,0 +1,18 @@
+package io.github.openflocon.flocondesktop.device.pages.permission
+
+import androidx.compose.runtime.Immutable
+
+@Immutable
+data class PermissionUiState(
+ val list: List
+)
+
+@Immutable
+data class PermissionItem(
+ val name: String,
+ val granted: Boolean
+)
+
+fun previewPermissionUiState() = PermissionUiState(
+ emptyList()
+)
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionViewModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionViewModel.kt
new file mode 100644
index 000000000..8b887e260
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionViewModel.kt
@@ -0,0 +1,83 @@
+package io.github.openflocon.flocondesktop.device.pages.permission
+
+import androidx.lifecycle.viewModelScope
+import io.github.openflocon.domain.adb.usecase.SendCommandUseCase
+import io.github.openflocon.domain.device.usecase.GetCurrentDeviceIdAndPackageNameUseCase
+import io.github.openflocon.flocondesktop.device.PageViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+
+class PermissionViewModel(
+ deviceSerial: String,
+ sendCommandUseCase: SendCommandUseCase,
+ val currentDeviceAppsUseCase: GetCurrentDeviceIdAndPackageNameUseCase
+) : PageViewModel(deviceSerial = deviceSerial, sendCommandUseCase = sendCommandUseCase) {
+
+ private val _uiState = MutableStateFlow(PermissionUiState(emptyList()))
+ val uiState = _uiState.asStateFlow()
+
+ init {
+ fetchPermission()
+ }
+
+ fun onAction(action: PermissionAction) {
+ when (action) {
+ is PermissionAction.ChangePermission -> onChangePermission(action)
+ }
+ }
+
+ private fun onChangePermission(action: PermissionAction.ChangePermission) {
+ viewModelScope.launch(Dispatchers.IO) {
+ if (action.granted) {
+ revokePermission(action.permission)
+ } else {
+ grantPermission(action.permission)
+ }
+ fetchPermission()
+ }
+ }
+
+ private suspend fun grantPermission(permission: String) {
+ val packageName = currentDeviceAppsUseCase() ?: return
+
+ sendCommand("shell", "pm", "grant", packageName.packageName, "${PERMISSION_PREFIX}$permission")
+ }
+
+ private suspend fun revokePermission(permission: String) {
+ val packageName = currentDeviceAppsUseCase() ?: return
+
+ sendCommand("shell", "pm", "revoke", packageName.packageName, "$PERMISSION_PREFIX$permission")
+ }
+
+ private fun fetchPermission() {
+ viewModelScope.launch {
+ val packageName = currentDeviceAppsUseCase()?.packageName ?: return@launch
+ val command = sendCommand("shell", "dumpsys", "package", packageName)
+ val permissions = command.lines()
+ .dropWhile { !it.contains("runtime permissions:") }
+ .drop(1)
+ .takeWhile { it.contains("granted=") }
+ .map { it.trim() }
+ .filter { it.startsWith(PERMISSION_PREFIX) }
+ .mapNotNull { line ->
+ val list = line.split(":")
+
+ PermissionItem(
+ name = list.getOrNull(0)?.removePrefix(PERMISSION_PREFIX) ?: return@mapNotNull null,
+ granted = list.getOrNull(1)?.contains("granted=true") ?: return@mapNotNull null,
+ )
+ }
+ .sortedBy(PermissionItem::name)
+
+ _uiState.update { it.copy(list = permissions) }
+ }
+ }
+
+ companion object {
+ private const val PERMISSION_PREFIX = "android.permission."
+ }
+
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsRowView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsRowView.kt
index 0ee34768a..8fe7f87d3 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsRowView.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsRowView.kt
@@ -73,7 +73,10 @@ fun AnalyticsRowView(
color = FloconTheme.colorPalette.onPrimary,
)
- Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(4.dp)
+ ) {
model.properties.fastForEach {
Row(
Modifier
@@ -99,8 +102,8 @@ fun AnalyticsRowView(
Text(
text = "...",
modifier =
- Modifier
- .padding(horizontal = 4.dp),
+ Modifier
+ .padding(horizontal = 4.dp),
style = FloconTheme.typography.bodySmall,
color = FloconTheme.colorPalette.onPrimary,
)
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardView.kt
index c22718f77..9fdcc425f 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardView.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardView.kt
@@ -7,12 +7,12 @@ import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.github.openflocon.flocondesktop.features.dashboard.model.DashboardArrangement
import io.github.openflocon.flocondesktop.features.dashboard.model.DashboardViewState
import io.github.openflocon.flocondesktop.features.dashboard.model.previewDashboardViewState
import io.github.openflocon.library.designsystem.FloconTheme
-import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun DashboardView(
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardButtonView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardButtonView.kt
index 4aaab9d72..628cbde30 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardButtonView.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardButtonView.kt
@@ -12,10 +12,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.github.openflocon.flocondesktop.features.dashboard.model.DashboardContainerViewState
import io.github.openflocon.library.designsystem.FloconTheme
-import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
internal fun DashboardButtonView(
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardCheckBoxView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardCheckBoxView.kt
index 471de32ee..c8ad08187 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardCheckBoxView.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardCheckBoxView.kt
@@ -12,11 +12,11 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.github.openflocon.flocondesktop.features.dashboard.model.DashboardContainerViewState
import io.github.openflocon.library.designsystem.FloconTheme
import io.github.openflocon.library.designsystem.components.FloconCheckbox
-import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
internal fun DashboardCheckBoxView(
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardLabelView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardLabelView.kt
index 0c19e5aa6..c15896fe7 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardLabelView.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardLabelView.kt
@@ -8,10 +8,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.github.openflocon.flocondesktop.features.dashboard.model.DashboardContainerViewState
import io.github.openflocon.library.designsystem.FloconTheme
-import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
internal fun DashboardLabelView(
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardTextFieldView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardTextFieldView.kt
index 2f655d876..648d8ba90 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardTextFieldView.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardTextFieldView.kt
@@ -14,6 +14,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.github.openflocon.flocondesktop.features.dashboard.model.DashboardContainerViewState
import io.github.openflocon.library.designsystem.FloconTheme
@@ -21,7 +22,6 @@ import io.github.openflocon.library.designsystem.components.FloconIcon
import io.github.openflocon.library.designsystem.components.FloconIconTonalButton
import io.github.openflocon.library.designsystem.components.FloconTextFieldWithoutM3
import io.github.openflocon.library.designsystem.components.defaultPlaceHolder
-import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun DashboardTextFieldView(
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardTextView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardTextView.kt
index 4a62ccd2d..1ac3e054e 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardTextView.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardTextView.kt
@@ -14,11 +14,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.github.openflocon.flocondesktop.features.dashboard.model.DashboardContainerViewState
import io.github.openflocon.library.designsystem.FloconTheme
import io.github.openflocon.library.designsystem.components.FloconTextFieldWithoutM3
-import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
internal fun DashboardTextView(
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/database/view/DatabaseResultView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/database/view/DatabaseResultView.kt
index 82fe1ce84..63b8de7cf 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/database/view/DatabaseResultView.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/database/view/DatabaseResultView.kt
@@ -126,8 +126,14 @@ fun DatabaseResultView(
)
selectedItem = item
- KoinPlatform.getKoin().get()
- .navigate(DatabaseRoutes.Detail(item, result.columns))
+ KoinPlatform.getKoin()
+ .get()
+ .navigate(
+ DatabaseRoutes.Detail(
+ item,
+ result.columns
+ )
+ )
}
}
@@ -144,8 +150,14 @@ fun DatabaseResultView(
)
selectedItem = item
- KoinPlatform.getKoin().get()
- .navigate(DatabaseRoutes.Detail(item, result.columns))
+ KoinPlatform.getKoin()
+ .get()
+ .navigate(
+ DatabaseRoutes.Detail(
+ item,
+ result.columns
+ )
+ )
}
}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/deeplinks/view/DeeplinkItemView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/deeplinks/view/DeeplinkItemView.kt
index 6b382cc34..0924aebcd 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/deeplinks/view/DeeplinkItemView.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/deeplinks/view/DeeplinkItemView.kt
@@ -35,6 +35,7 @@ import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import io.github.openflocon.flocondesktop.features.deeplinks.model.DeeplinkPart
@@ -43,7 +44,6 @@ import io.github.openflocon.flocondesktop.features.deeplinks.model.previewDeepli
import io.github.openflocon.library.designsystem.FloconTheme
import io.github.openflocon.library.designsystem.components.FloconIcon
import io.github.openflocon.library.designsystem.components.FloconIconTonalButton
-import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun DeeplinkItemView(
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/deeplinks/view/DeeplinkScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/deeplinks/view/DeeplinkScreen.kt
index 62e345784..8ca1dd762 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/deeplinks/view/DeeplinkScreen.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/deeplinks/view/DeeplinkScreen.kt
@@ -15,6 +15,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.github.openflocon.flocondesktop.features.deeplinks.DeepLinkViewModel
@@ -26,7 +27,6 @@ import io.github.openflocon.library.designsystem.components.FloconFeature
import io.github.openflocon.library.designsystem.components.FloconPageTopBar
import io.github.openflocon.library.designsystem.components.FloconVerticalScrollbar
import io.github.openflocon.library.designsystem.components.rememberFloconScrollbarAdapter
-import org.jetbrains.compose.ui.tooling.preview.Preview
import org.koin.compose.viewmodel.koinViewModel
@Composable
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/view/FilesScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/view/FilesScreen.kt
index 1c4164257..670124569 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/view/FilesScreen.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/view/FilesScreen.kt
@@ -20,6 +20,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.github.openflocon.flocondesktop.features.files.FilesViewModel
@@ -33,7 +34,6 @@ import io.github.openflocon.library.designsystem.FloconTheme
import io.github.openflocon.library.designsystem.components.FloconFeature
import io.github.openflocon.library.designsystem.components.FloconVerticalScrollbar
import io.github.openflocon.library.designsystem.components.rememberFloconScrollbarAdapter
-import org.jetbrains.compose.ui.tooling.preview.Preview
import org.koin.compose.viewmodel.koinViewModel
@Composable
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/images/view/ImagesScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/images/view/ImagesScreen.kt
index c2aa41487..c46004d61 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/images/view/ImagesScreen.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/images/view/ImagesScreen.kt
@@ -28,6 +28,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
@@ -48,7 +49,6 @@ import io.github.openflocon.library.designsystem.components.FloconIconButton
import io.github.openflocon.library.designsystem.components.FloconPageTopBar
import io.github.openflocon.library.designsystem.components.FloconVerticalScrollbar
import io.github.openflocon.library.designsystem.components.rememberFloconScrollbarAdapter
-import org.jetbrains.compose.ui.tooling.preview.Preview
import org.koin.compose.viewmodel.koinViewModel
import kotlin.collections.component1
import kotlin.collections.component2
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/view/components/DetailHeadersView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/view/components/DetailHeadersView.kt
index af1e66c46..dd15b6175 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/view/components/DetailHeadersView.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/view/components/DetailHeadersView.kt
@@ -11,6 +11,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
@@ -20,7 +21,6 @@ import io.github.openflocon.library.designsystem.FloconTheme
import io.github.openflocon.library.designsystem.components.FloconButton
import io.github.openflocon.library.designsystem.components.FloconHorizontalDivider
import io.github.openflocon.library.designsystem.components.FloconLineDescription
-import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun DetailHeadersView(
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/NetworkItemView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/NetworkItemView.kt
index f8055dd71..5a149112c 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/NetworkItemView.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/NetworkItemView.kt
@@ -31,6 +31,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.github.openflocon.domain.network.models.NetworkTextFilterColumns
@@ -52,7 +53,6 @@ import io.github.openflocon.library.designsystem.common.FloconContextMenuItem
import io.github.openflocon.library.designsystem.common.buildMenu
import io.github.openflocon.library.designsystem.components.FloconCheckbox
import io.github.openflocon.library.designsystem.components.FloconSurface
-import org.jetbrains.compose.ui.tooling.preview.Preview
private val replayColor = Color(0xFF242D44)
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/components/MethodView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/components/MethodView.kt
index 280cb0b20..835f54919 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/components/MethodView.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/components/MethodView.kt
@@ -18,6 +18,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -25,7 +26,6 @@ import io.github.openflocon.flocondesktop.features.network.list.model.NetworkMet
import io.github.openflocon.library.designsystem.FloconTheme
import org.jetbrains.compose.resources.DrawableResource
import org.jetbrains.compose.resources.painterResource
-import org.jetbrains.compose.ui.tooling.preview.Preview
val getMethodBackground = Color(0xFF007BFF).copy(alpha = 0.3f) // Muted blue for GET
val getMethodText = Color(0xFF007BFF)
@@ -96,20 +96,20 @@ fun NetworkTag(
) {
Row(
modifier =
- modifier
- .clip(RoundedCornerShape(20.dp))
- .background(
- color = backgroundColor,
- )
- .then(
- if (onClick != null) {
- Modifier.clickable(onClick = onClick)
- } else {
- Modifier
- },
- )
- .padding(horizontal = 4.dp, vertical = 2.dp)
- .padding(contentPadding),
+ modifier
+ .clip(RoundedCornerShape(20.dp))
+ .background(
+ color = backgroundColor,
+ )
+ .then(
+ if (onClick != null) {
+ Modifier.clickable(onClick = onClick)
+ } else {
+ Modifier
+ },
+ )
+ .padding(horizontal = 4.dp, vertical = 2.dp)
+ .padding(contentPadding),
// Padding inside the tag
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/components/StatusView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/components/StatusView.kt
index 719c292dc..180dfbaa7 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/components/StatusView.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/components/StatusView.kt
@@ -9,13 +9,13 @@ 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.tooling.preview.Preview
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.github.openflocon.flocondesktop.features.network.list.model.NetworkStatusUi
import io.github.openflocon.library.designsystem.FloconTheme
import io.github.openflocon.library.designsystem.components.FloconLinearProgressIndicator
-import org.jetbrains.compose.ui.tooling.preview.Preview
// Custom colors for networkStatusUi/method views to integrate better with the theme
val successTagBackground = Color(0xFF28A745).copy(alpha = 0.3f) // Muted green for success
@@ -77,10 +77,10 @@ private fun StatusView_Preview() {
FloconTheme {
StatusView(
status =
- NetworkStatusUi(
- text = "200",
- status = NetworkStatusUi.Status.SUCCESS,
- ),
+ NetworkStatusUi(
+ text = "200",
+ status = NetworkStatusUi.Status.SUCCESS,
+ ),
)
}
}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/filters/MethodFilterDropdown.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/filters/MethodFilterDropdown.kt
index 48af50158..be0457441 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/filters/MethodFilterDropdown.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/filters/MethodFilterDropdown.kt
@@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import io.github.openflocon.flocondesktop.features.network.list.model.NetworkMethodUi
@@ -15,7 +16,6 @@ import io.github.openflocon.flocondesktop.features.network.list.model.header.col
import io.github.openflocon.flocondesktop.features.network.list.model.header.columns.base.filter.previewMethodFilterState
import io.github.openflocon.flocondesktop.features.network.list.view.components.MethodView
import io.github.openflocon.library.designsystem.FloconTheme
-import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun MethodFilterDropdownContent(
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/filters/TextFilterDropdown.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/filters/TextFilterDropdown.kt
index 46c5a9bb7..fe44d9512 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/filters/TextFilterDropdown.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/filters/TextFilterDropdown.kt
@@ -33,6 +33,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import io.github.openflocon.flocondesktop.common.ui.interactions.hover
@@ -45,7 +46,6 @@ import io.github.openflocon.library.designsystem.components.FloconSmallIconButto
import io.github.openflocon.library.designsystem.components.FloconSwitch
import io.github.openflocon.library.designsystem.components.FloconTextFieldWithoutM3
import io.github.openflocon.library.designsystem.components.defaultPlaceHolder
-import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun TextFilterDropdownContent(
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/mock/list/view/MockLineView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/mock/list/view/MockLineView.kt
index 1bf5c1ee7..e62b6364a 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/mock/list/view/MockLineView.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/mock/list/view/MockLineView.kt
@@ -16,6 +16,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.github.openflocon.flocondesktop.features.network.mock.edition.model.MockNetworkMethodUi
@@ -26,7 +27,6 @@ import io.github.openflocon.library.designsystem.components.FloconCheckbox
import io.github.openflocon.library.designsystem.components.FloconIconButton
import io.github.openflocon.library.designsystem.components.FloconSurface
import io.github.openflocon.library.designsystem.components.FloconSwitch
-import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun MockLineView(
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/mock/list/view/NetworkMocksScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/mock/list/view/NetworkMocksScreen.kt
index 0b8f8d98c..c2e80c03d 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/mock/list/view/NetworkMocksScreen.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/mock/list/view/NetworkMocksScreen.kt
@@ -14,6 +14,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.github.openflocon.flocondesktop.features.network.mock.NetworkMocksViewModel
@@ -25,7 +26,6 @@ import io.github.openflocon.library.designsystem.components.FloconButton
import io.github.openflocon.library.designsystem.components.FloconDialogHeader
import io.github.openflocon.library.designsystem.components.FloconDropdownMenuItem
import io.github.openflocon.library.designsystem.components.FloconOverflow
-import org.jetbrains.compose.ui.tooling.preview.Preview
import org.koin.compose.viewmodel.koinViewModel
@OptIn(ExperimentalMaterial3Api::class)
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferenceRow.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferenceRow.kt
index 6b12c6792..d6d6ca9bb 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferenceRow.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferenceRow.kt
@@ -25,6 +25,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.github.openflocon.flocondesktop.features.sharedpreferences.model.SharedPreferencesRowUiModel
import io.github.openflocon.flocondesktop.features.sharedpreferences.model.previewSharedPreferencesBooleanRowUiModel
@@ -37,7 +38,6 @@ import io.github.openflocon.library.designsystem.components.FloconCheckbox
import io.github.openflocon.library.designsystem.components.FloconIcon
import io.github.openflocon.library.designsystem.components.FloconIconTonalButton
import io.github.openflocon.library.designsystem.components.FloconTextField
-import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun SharedPreferenceRowView(
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferencesScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferencesScreen.kt
index c1992449d..070883873 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferencesScreen.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/view/SharedPreferencesScreen.kt
@@ -24,6 +24,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.github.openflocon.flocondesktop.features.sharedpreferences.SharedPreferencesViewModel
@@ -37,7 +38,6 @@ import io.github.openflocon.library.designsystem.FloconTheme
import io.github.openflocon.library.designsystem.components.FloconFeature
import io.github.openflocon.library.designsystem.components.FloconVerticalScrollbar
import io.github.openflocon.library.designsystem.components.rememberFloconScrollbarAdapter
-import org.jetbrains.compose.ui.tooling.preview.Preview
import org.koin.compose.viewmodel.koinViewModel
@Composable
diff --git a/FloconDesktop/composeApp/src/desktopMain/java/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.desktop.kt b/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.desktop.kt
similarity index 82%
rename from FloconDesktop/composeApp/src/desktopMain/java/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.desktop.kt
rename to FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.desktop.kt
index f4fe216a5..76a5024e0 100644
--- a/FloconDesktop/composeApp/src/desktopMain/java/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.desktop.kt
+++ b/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.desktop.kt
@@ -10,6 +10,7 @@ import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.type
import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.window.FrameWindowScope
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowPlacement
import androidx.compose.ui.window.WindowPosition
@@ -19,6 +20,19 @@ import flocondesktop.composeapp.generated.resources.app_icon
import io.github.openflocon.library.designsystem.components.escape.LocalEscapeHandlerStack
import org.jetbrains.compose.resources.painterResource
+//actual fun rememberFloconWindowState(
+// placement: WindowPlacement,
+// position: WindowPosition,
+// size: DpSize
+//): FloconWindowState {
+// return FloconWindowStateDesktop(
+// WindowState(
+// placement = WindowPlacement.Floating,
+// position = WindowPosition(Alignment.Center),
+// )
+// )
+//}
+
data class FloconWindowStateDesktop(
val windowState: WindowState,
) : FloconWindowState
@@ -38,7 +52,7 @@ actual fun FloconWindow(
state: FloconWindowState,
onCloseRequest: () -> Unit,
alwaysOnTop: Boolean,
- content: @Composable () -> Unit,
+ content: @Composable FrameWindowScope.() -> Unit,
) {
val handlers = remember { mutableStateListOf<() -> Boolean>() }
diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/DI.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/DI.kt
index de5a70b3f..49dd35893 100644
--- a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/DI.kt
+++ b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/DI.kt
@@ -1,8 +1,13 @@
package io.github.openflocon.domain.adb
+import io.github.openflocon.domain.adb.usecase.ExecuteAdbCommandUseCase
+import io.github.openflocon.domain.adb.usecase.GetDeviceSerialUseCase
+import io.github.openflocon.domain.adb.usecase.SendCommandUseCase
import org.koin.core.module.dsl.factoryOf
import org.koin.dsl.module
internal val adbModule = module {
factoryOf(::ExecuteAdbCommandUseCase)
+ factoryOf(::SendCommandUseCase)
+ factoryOf(::GetDeviceSerialUseCase)
}
diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/ExecuteAdbCommandUseCase.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/usecase/ExecuteAdbCommandUseCase.kt
similarity index 95%
rename from FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/ExecuteAdbCommandUseCase.kt
rename to FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/usecase/ExecuteAdbCommandUseCase.kt
index 24bd3840b..0ed68047f 100644
--- a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/ExecuteAdbCommandUseCase.kt
+++ b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/usecase/ExecuteAdbCommandUseCase.kt
@@ -1,4 +1,4 @@
-package io.github.openflocon.domain.adb
+package io.github.openflocon.domain.adb.usecase
import io.github.openflocon.domain.adb.model.AdbCommandTargetDomainModel
import io.github.openflocon.domain.adb.repository.AdbRepository
diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/usecase/GetDeviceSerialUseCase.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/usecase/GetDeviceSerialUseCase.kt
new file mode 100644
index 000000000..ca844effd
--- /dev/null
+++ b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/usecase/GetDeviceSerialUseCase.kt
@@ -0,0 +1,13 @@
+package io.github.openflocon.domain.adb.usecase
+
+import io.github.openflocon.domain.adb.repository.AdbRepository
+
+class GetDeviceSerialUseCase internal constructor(
+ private val adbRepository: AdbRepository
+) {
+ suspend operator fun invoke(
+ deviceId: String
+ ): String {
+ return adbRepository.getAdbSerial(deviceId).orEmpty()
+ }
+}
diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/usecase/SendCommandUseCase.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/usecase/SendCommandUseCase.kt
new file mode 100644
index 000000000..9845b412a
--- /dev/null
+++ b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/usecase/SendCommandUseCase.kt
@@ -0,0 +1,24 @@
+package io.github.openflocon.domain.adb.usecase
+
+import io.github.openflocon.domain.adb.repository.AdbRepository
+import io.github.openflocon.domain.common.Either
+import io.github.openflocon.domain.settings.repository.SettingsRepository
+import kotlinx.coroutines.flow.firstOrNull
+
+class SendCommandUseCase(
+ private val adbRepository: AdbRepository,
+ private val settingsRepository: SettingsRepository
+) {
+
+ suspend operator fun invoke(
+ deviceSerial: String,
+ vararg args: String
+ ): Either = Either.catch {
+ return adbRepository.executeAdbCommand(
+ adbPath = settingsRepository.adbPath.firstOrNull() ?: throw Throwable("error"),
+ command = args.joinToString(separator = " "),
+ deviceSerial = deviceSerial
+ )
+ }
+
+}
diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/deeplink/usecase/ExecuteDeeplinkUseCase.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/deeplink/usecase/ExecuteDeeplinkUseCase.kt
index fdc5bb595..f30bbcfde 100644
--- a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/deeplink/usecase/ExecuteDeeplinkUseCase.kt
+++ b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/deeplink/usecase/ExecuteDeeplinkUseCase.kt
@@ -1,6 +1,6 @@
package io.github.openflocon.domain.deeplink.usecase
-import io.github.openflocon.domain.adb.ExecuteAdbCommandUseCase
+import io.github.openflocon.domain.adb.usecase.ExecuteAdbCommandUseCase
import io.github.openflocon.domain.adb.model.AdbCommandTargetDomainModel
import io.github.openflocon.domain.deeplink.models.DeeplinkDomainModel
import io.github.openflocon.domain.deeplink.repository.DeeplinkRepository
diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/device/usecase/StartRecordingVideoUseCase.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/device/usecase/StartRecordingVideoUseCase.kt
index 53e037a39..8f1d78d79 100644
--- a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/device/usecase/StartRecordingVideoUseCase.kt
+++ b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/device/usecase/StartRecordingVideoUseCase.kt
@@ -1,6 +1,6 @@
package io.github.openflocon.domain.device.usecase
-import io.github.openflocon.domain.adb.ExecuteAdbCommandUseCase
+import io.github.openflocon.domain.adb.usecase.ExecuteAdbCommandUseCase
import io.github.openflocon.domain.adb.model.AdbCommandTargetDomainModel
import io.github.openflocon.domain.common.Either
import io.github.openflocon.domain.common.Failure
diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/device/usecase/StopRecordingVideoUseCase.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/device/usecase/StopRecordingVideoUseCase.kt
index e75a68837..1b3be5129 100644
--- a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/device/usecase/StopRecordingVideoUseCase.kt
+++ b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/device/usecase/StopRecordingVideoUseCase.kt
@@ -1,6 +1,6 @@
package io.github.openflocon.domain.device.usecase
-import io.github.openflocon.domain.adb.ExecuteAdbCommandUseCase
+import io.github.openflocon.domain.adb.usecase.ExecuteAdbCommandUseCase
import io.github.openflocon.domain.adb.model.AdbCommandTargetDomainModel
import io.github.openflocon.domain.common.Either
import io.github.openflocon.domain.common.Failure
diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/device/usecase/TakeScreenshotUseCase.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/device/usecase/TakeScreenshotUseCase.kt
index 85bcff92e..eaa17d055 100644
--- a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/device/usecase/TakeScreenshotUseCase.kt
+++ b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/device/usecase/TakeScreenshotUseCase.kt
@@ -1,6 +1,6 @@
package io.github.openflocon.domain.device.usecase
-import io.github.openflocon.domain.adb.ExecuteAdbCommandUseCase
+import io.github.openflocon.domain.adb.usecase.ExecuteAdbCommandUseCase
import io.github.openflocon.domain.adb.model.AdbCommandTargetDomainModel
import io.github.openflocon.domain.common.Either
import io.github.openflocon.domain.common.Failure
diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/settings/usecase/StartAdbForwardUseCase.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/settings/usecase/StartAdbForwardUseCase.kt
index 9f6e4d9c9..ff1fbe77f 100644
--- a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/settings/usecase/StartAdbForwardUseCase.kt
+++ b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/settings/usecase/StartAdbForwardUseCase.kt
@@ -1,7 +1,7 @@
package io.github.openflocon.domain.settings.usecase
import io.github.openflocon.domain.Constant
-import io.github.openflocon.domain.adb.ExecuteAdbCommandUseCase
+import io.github.openflocon.domain.adb.usecase.ExecuteAdbCommandUseCase
import io.github.openflocon.domain.adb.model.AdbCommandTargetDomainModel
import io.github.openflocon.domain.common.Either
diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/settings/usecase/TestAdbUseCase.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/settings/usecase/TestAdbUseCase.kt
index af2298fdd..2cf3ee9b6 100644
--- a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/settings/usecase/TestAdbUseCase.kt
+++ b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/settings/usecase/TestAdbUseCase.kt
@@ -1,6 +1,6 @@
package io.github.openflocon.domain.settings.usecase
-import io.github.openflocon.domain.adb.ExecuteAdbCommandUseCase
+import io.github.openflocon.domain.adb.usecase.ExecuteAdbCommandUseCase
import io.github.openflocon.domain.adb.model.AdbCommandTargetDomainModel
import io.github.openflocon.domain.common.Either
diff --git a/FloconDesktop/gradle/libs.versions.toml b/FloconDesktop/gradle/libs.versions.toml
index abf3af0d8..ae213030e 100644
--- a/FloconDesktop/gradle/libs.versions.toml
+++ b/FloconDesktop/gradle/libs.versions.toml
@@ -16,7 +16,7 @@ composeMultiplatform = "1.10.0-alpha03"
junit = "4.13.2"
kermit = "2.0.8"
koin = "4.1.0"
-kotlin = "2.2.0"
+kotlin = "2.2.20"
kotlinx-coroutines = "1.10.2"
kotlinx-dateTime = "0.7.1"
kotlinx-serialization = "1.9.0"
diff --git a/FloconDesktop/library/designsystem/build.gradle.kts b/FloconDesktop/library/designsystem/build.gradle.kts
index fec6b83c6..6d0c0f9ce 100644
--- a/FloconDesktop/library/designsystem/build.gradle.kts
+++ b/FloconDesktop/library/designsystem/build.gradle.kts
@@ -3,7 +3,7 @@ plugins {
alias(libs.plugins.composeCompiler)
alias(libs.plugins.composeMultiplatform)
- alias(libs.plugins.composeHotReload)
+// alias(libs.plugins.composeHotReload)
}
kotlin {
diff --git a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconCheckbox.kt b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconCheckbox.kt
index 5354a6ad9..72cb1c01e 100644
--- a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconCheckbox.kt
+++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconCheckbox.kt
@@ -10,7 +10,7 @@ import io.github.openflocon.library.designsystem.FloconTheme
@Composable
fun FloconCheckbox(
checked: Boolean,
- onCheckedChange: (Boolean) -> Unit,
+ onCheckedChange: ((Boolean) -> Unit)?,
modifier: Modifier = Modifier,
uncheckedColor: Color = FloconTheme.colorPalette.primary
) {
diff --git a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconScrollableTabRow.kt b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconScrollableTabRow.kt
new file mode 100644
index 000000000..a5d2c7646
--- /dev/null
+++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconScrollableTabRow.kt
@@ -0,0 +1,21 @@
+package io.github.openflocon.library.designsystem.components
+
+import androidx.compose.material3.ScrollableTabRow
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun FloconScrollableTabRow(
+ selectedTabIndex: Int,
+ modifier: Modifier = Modifier,
+ tabs: @Composable () -> Unit
+) {
+ ScrollableTabRow(
+ selectedTabIndex = selectedTabIndex,
+ modifier = modifier,
+ edgePadding = 0.dp,
+ divider = {},
+ tabs = tabs
+ )
+}
diff --git a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconTab.kt b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconTab.kt
new file mode 100644
index 000000000..0e512fafe
--- /dev/null
+++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconTab.kt
@@ -0,0 +1,53 @@
+package io.github.openflocon.library.designsystem.components
+
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.Tab
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun FloconTab(
+ selected: Boolean,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ selectedContentColor: Color = LocalContentColor.current,
+ unselectedContentColor: Color = selectedContentColor,
+ content: @Composable ColumnScope.() -> Unit
+) {
+ Tab(
+ selected = selected,
+ onClick = onClick,
+ content = content,
+ selectedContentColor = selectedContentColor,
+ unselectedContentColor = unselectedContentColor,
+ modifier = modifier
+ )
+}
+
+@Composable
+fun FloconTab(
+ text: String,
+ selected: Boolean,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ selectedContentColor: Color = LocalContentColor.current,
+ unselectedContentColor: Color = selectedContentColor,
+) {
+ FloconTab(
+ selected = selected,
+ onClick = onClick,
+ selectedContentColor = selectedContentColor,
+ unselectedContentColor = unselectedContentColor,
+ modifier = modifier
+ ) {
+ Text(
+ text = text,
+ modifier = Modifier.padding(4.dp)
+ )
+ }
+}
diff --git a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconTextValue.kt b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconTextValue.kt
new file mode 100644
index 000000000..ea8300c98
--- /dev/null
+++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconTextValue.kt
@@ -0,0 +1,48 @@
+package io.github.openflocon.library.designsystem.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.selection.SelectionContainer
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import io.github.openflocon.library.designsystem.FloconTheme
+import io.github.openflocon.library.designsystem.theme.contentColorFor
+
+@Composable
+fun FloconTextValue(
+ label: String,
+ value: String,
+ modifier: Modifier = Modifier,
+ valueContainerColor: Color = FloconTheme.colorPalette.primary
+) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = modifier.padding(2.dp)
+ ) {
+ Text(
+ text = label,
+ style = FloconTheme.typography.labelSmall,
+ modifier = Modifier.weight(1f)
+ )
+ SelectionContainer(
+ modifier = Modifier.weight(1f)
+ .clip(FloconTheme.shapes.small)
+ .background(valueContainerColor)
+ .padding(4.dp)
+ ) {
+ Text(
+ text = value,
+ style = FloconTheme.typography.labelSmall,
+ color = FloconTheme.colorPalette.contentColorFor(valueContainerColor)
+ )
+ }
+ }
+}
diff --git a/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/WindowScene.kt b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/WindowScene.kt
index 5b43f8a18..bc2dd6d5e 100644
--- a/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/WindowScene.kt
+++ b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/WindowScene.kt
@@ -17,9 +17,10 @@ import io.github.openflocon.navigation.FloconRoute
import kotlin.uuid.ExperimentalUuidApi
@Immutable
-data class WindowScene(
- private val entry: NavEntry,
+private data class WindowScene(
override val previousEntries: List>,
+ private val entry: NavEntry,
+ private val properties: WindowProperties,
private val onBack: () -> Unit
) : OverlayScene {
@@ -51,10 +52,12 @@ class WindowSceneStrategy : SceneStrategy {
override fun SceneStrategyScope.calculateScene(entries: List>): Scene? {
val entry = entries.last()
+ val properties = entry.metadata[WINDOW_KEY] ?: return null
- if (entry.metadata[IS_WINDOW] == true) {
+ if (properties is WindowProperties) {
return WindowScene(
entry = entry,
+ properties = properties,
previousEntries = entries.dropLast(1),
onBack = onBack
)
@@ -77,3 +80,7 @@ class WindowSceneStrategy : SceneStrategy {
}
}
}
+
+private data class WindowProperties(
+ val title: String
+)