From 092de4c363e9c0544950201a253e6fa4d7814c26 Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Wed, 13 Aug 2025 22:42:11 +0200 Subject: [PATCH 01/24] feature: System notification --- .../openflocon/flocondesktop/app/ui/settings/SettingsScreen.kt | 3 +++ 1 file changed, 3 insertions(+) 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..68fbdb10e 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 @@ -23,6 +23,8 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import co.touchlab.kermit.Logger +import io.github.openflocon.flocondesktop.common.ui.window.FloconWindow +import io.github.openflocon.flocondesktop.common.ui.window.createFloconWindowState import flocondesktop.composeapp.generated.resources.Res import flocondesktop.composeapp.generated.resources.general_save import flocondesktop.composeapp.generated.resources.settings_adb_setup_title @@ -43,6 +45,7 @@ import org.koin.compose.viewmodel.koinViewModel @Composable fun SettingsScreen( + onCloseRequest: () -> Unit, modifier: Modifier = Modifier, ) { val viewModel: SettingsViewModel = koinViewModel() From af545383bedbbe0bcdaa180ded7f5455bb1d32b3 Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Wed, 13 Aug 2025 22:46:15 +0200 Subject: [PATCH 02/24] fix: Settings --- .../openflocon/flocondesktop/app/ui/settings/SettingsScreen.kt | 1 - 1 file changed, 1 deletion(-) 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 68fbdb10e..e83d37dec 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 @@ -45,7 +45,6 @@ import org.koin.compose.viewmodel.koinViewModel @Composable fun SettingsScreen( - onCloseRequest: () -> Unit, modifier: Modifier = Modifier, ) { val viewModel: SettingsViewModel = koinViewModel() From 06c9dec594c79d518ce61292b7af5a62b49d2950 Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Fri, 15 Aug 2025 17:07:51 +0200 Subject: [PATCH 03/24] feature: Device screen --- .../openflocon/flocondesktop/device/DI.kt | 8 ++ .../flocondesktop/device/DeviceAction.kt | 4 + .../flocondesktop/device/DeviceScreen.kt | 89 +++++++++++++++++++ .../flocondesktop/device/DeviceUiState.kt | 26 ++++++ .../flocondesktop/device/DeviceViewModel.kt | 53 +++++++++++ .../domain/adb/usecase/SendCommandUseCase.kt | 20 +++++ 6 files changed, 200 insertions(+) create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DI.kt create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceAction.kt create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceScreen.kt create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceUiState.kt create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceViewModel.kt create mode 100644 FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/usecase/SendCommandUseCase.kt 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..54403cb1e --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DI.kt @@ -0,0 +1,8 @@ +package io.github.openflocon.flocondesktop.device + +import org.koin.core.module.dsl.viewModelOf +import org.koin.dsl.module + +internal val deviceModule = module { + viewModelOf(::DeviceViewModel) +} 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..8171c9367 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceAction.kt @@ -0,0 +1,4 @@ +package io.github.openflocon.flocondesktop.device + +internal interface 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..7f1bad18e --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceScreen.kt @@ -0,0 +1,89 @@ +package io.github.openflocon.flocondesktop.device + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import io.github.openflocon.domain.device.models.DeviceId +import io.github.openflocon.flocondesktop.common.ui.window.FloconWindow +import io.github.openflocon.flocondesktop.common.ui.window.createFloconWindowState +import io.github.openflocon.library.designsystem.FloconTheme +import org.jetbrains.compose.ui.tooling.preview.Preview +import org.koin.compose.viewmodel.koinViewModel +import org.koin.core.parameter.parametersOf + +@Composable +internal fun DeviceScreen( + deviceId: DeviceId, + onCloseRequest: () -> Unit +) { + val viewModel = koinViewModel { + parametersOf(deviceId) + } + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + Content( + uiState = uiState, + onCloseRequest = onCloseRequest, + onAction = viewModel::onAction + ) +} + +@Composable +private fun Content( + uiState: DeviceUiState, + onCloseRequest: () -> Unit, + onAction: (DeviceAction) -> Unit +) { + FloconWindow( + title = "Device", + onCloseRequest = onCloseRequest, + state = createFloconWindowState() + ) { + Column { + TextValue("Model:", uiState.model) + TextValue("Brand:", uiState.brand) + TextValue("CPU:", uiState.cpu) + TextValue("MEM:", uiState.mem) + TextValue("Battery:", uiState.battery) + TextValue("SerialNumber:", uiState.serialNumber) + TextValue("VersionRelease:", uiState.versionRelease) + TextValue("VersionSdk:", uiState.versionSdk) + } + } +} + +@Composable +private fun TextValue( + label: String, + value: String +) { + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = label + ) + Text( + text = value + ) + } +} + +@Composable +@Preview +private fun Preview() { + FloconTheme { + Content( + uiState = previewDeviceUiState(), + onCloseRequest = {}, + onAction = {} + ) + } +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceUiState.kt new file mode 100644 index 000000000..d50100407 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceUiState.kt @@ -0,0 +1,26 @@ +package io.github.openflocon.flocondesktop.device + +import androidx.compose.runtime.Immutable + +@Immutable +data class DeviceUiState( + val model: String, + val brand: String, + val versionRelease: String, + val versionSdk: String, + val serialNumber: String, + val battery: String, + val cpu: String, + val mem: String +) + +internal fun previewDeviceUiState() = DeviceUiState( + model = "", + brand = "", + versionRelease = "", + versionSdk = "", + serialNumber = "", + battery = "", + cpu = "", + mem = "" +) 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..8433f886b --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceViewModel.kt @@ -0,0 +1,53 @@ +package io.github.openflocon.flocondesktop.device + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import io.github.openflocon.domain.adb.usecase.SendCommandUseCase +import io.github.openflocon.domain.common.getOrNull +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +internal class DeviceViewModel( + val deviceSerial: String, + val sendCommandUseCase: SendCommandUseCase +) : ViewModel() { + + private val _uiState = MutableStateFlow( + DeviceUiState( + model = "", + brand = "", + versionRelease = "", + versionSdk = "", + serialNumber = "", + battery = "", + cpu = "", + mem = "" + ) + ) + val uiState = _uiState.asStateFlow() + + init { + sendCommand() + } + + fun onAction(action: DeviceAction) { + + } + + private fun sendCommand() { + viewModelScope.launch { + _uiState.update { state -> + state.copy( + model = sendCommandUseCase("shell", "getprop", "ro.product.model").getOrNull().orEmpty().replace("\n", ""), + brand = sendCommandUseCase("shell", "getprop", "ro.product.brand").getOrNull().orEmpty().replace("\n", ""), + versionRelease = sendCommandUseCase("shell", "getprop", "ro.build.version.release").getOrNull().orEmpty().replace("\n", ""), + versionSdk = sendCommandUseCase("shell", "getprop", "ro.build.version.sdk").getOrNull().orEmpty().replace("\n", ""), + serialNumber = sendCommandUseCase("shell", "getprop", "ro.serialno").getOrNull().orEmpty().replace("\n", ""), + ) + } + } + } + +} 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..cc5e8fcd0 --- /dev/null +++ b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/usecase/SendCommandUseCase.kt @@ -0,0 +1,20 @@ +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(vararg args: String): Either = Either.catch { + return adbRepository.executeAdbCommand( + adbPath = settingsRepository.adbPath.firstOrNull() ?: throw Throwable("error"), + command = args.joinToString(separator = " ") + ) + } + +} From 22dc37b71293b565dede9e9a91cc864f9cd91a4f Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Fri, 15 Aug 2025 17:31:31 +0200 Subject: [PATCH 04/24] feature: Page --- .../flocondesktop/device/DeviceAction.kt | 5 +- .../flocondesktop/device/DeviceScreen.kt | 69 ++++++++++++++++--- .../flocondesktop/device/DeviceUiState.kt | 4 ++ .../flocondesktop/device/DeviceViewModel.kt | 7 ++ .../components/FloconScrollableTabRow.kt | 18 +++++ .../designsystem/components/FloconTab.kt | 38 ++++++++++ 6 files changed, 131 insertions(+), 10 deletions(-) create mode 100644 FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconScrollableTabRow.kt create mode 100644 FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconTab.kt 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 index 8171c9367..e470d84dc 100644 --- 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 @@ -1,4 +1,7 @@ package io.github.openflocon.flocondesktop.device -internal interface DeviceAction { +internal sealed interface DeviceAction { + + data class SelectTab(val index: Int) : 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 index 7f1bad18e..250ad6d7a 100644 --- 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 @@ -3,8 +3,11 @@ package io.github.openflocon.flocondesktop.device import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.unit.dp @@ -13,6 +16,9 @@ import io.github.openflocon.domain.device.models.DeviceId import io.github.openflocon.flocondesktop.common.ui.window.FloconWindow import io.github.openflocon.flocondesktop.common.ui.window.createFloconWindowState import io.github.openflocon.library.designsystem.FloconTheme +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.jetbrains.compose.ui.tooling.preview.Preview import org.koin.compose.viewmodel.koinViewModel import org.koin.core.parameter.parametersOf @@ -40,24 +46,69 @@ private fun Content( onCloseRequest: () -> Unit, onAction: (DeviceAction) -> Unit ) { + val pagerState = rememberPagerState { 2 } + + LaunchedEffect(uiState.selectedIndex) { + pagerState.animateScrollToPage(uiState.selectedIndex) + } + FloconWindow( title = "Device", onCloseRequest = onCloseRequest, state = createFloconWindowState() ) { - Column { - TextValue("Model:", uiState.model) - TextValue("Brand:", uiState.brand) - TextValue("CPU:", uiState.cpu) - TextValue("MEM:", uiState.mem) - TextValue("Battery:", uiState.battery) - TextValue("SerialNumber:", uiState.serialNumber) - TextValue("VersionRelease:", uiState.versionRelease) - TextValue("VersionSdk:", uiState.versionSdk) + FloconSurface { + Column { + FloconScrollableTabRow( + selectedTabIndex = uiState.selectedIndex + ) { + FloconTab( + text = "Info", + selected = true, + onClick = { onAction(DeviceAction.SelectTab(0)) } + ) + FloconTab( + text = "Permission", + selected = true, + onClick = { onAction(DeviceAction.SelectTab(1)) } + ) + } + HorizontalPager( + state = pagerState, + userScrollEnabled = false + ) { index -> + when (index) { + 0 -> InfoPage(uiState) + 1 -> PermissionPage() + else -> Unit + } + } + } } } } +@Composable +private fun InfoPage( + uiState: DeviceUiState +) { + Column { + TextValue("Model:", uiState.model) + TextValue("Brand:", uiState.brand) + TextValue("CPU:", uiState.cpu) + TextValue("MEM:", uiState.mem) + TextValue("Battery:", uiState.battery) + TextValue("SerialNumber:", uiState.serialNumber) + TextValue("VersionRelease:", uiState.versionRelease) + TextValue("VersionSdk:", uiState.versionSdk) + } +} + +@Composable +private fun PermissionPage() { + +} + @Composable private fun TextValue( label: String, diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceUiState.kt index d50100407..e280f34dd 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceUiState.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceUiState.kt @@ -4,6 +4,8 @@ import androidx.compose.runtime.Immutable @Immutable data class DeviceUiState( + val selectedIndex: Int, + val model: String, val brand: String, val versionRelease: String, @@ -15,6 +17,8 @@ data class DeviceUiState( ) internal fun previewDeviceUiState() = DeviceUiState( + selectedIndex = 0, + model = "", brand = "", versionRelease = "", 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 index 8433f886b..94695ec08 100644 --- 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 @@ -16,6 +16,7 @@ internal class DeviceViewModel( private val _uiState = MutableStateFlow( DeviceUiState( + selectedIndex = 0, model = "", brand = "", versionRelease = "", @@ -33,7 +34,13 @@ internal class DeviceViewModel( } fun onAction(action: DeviceAction) { + when (action) { + is DeviceAction.SelectTab -> onSelect(action) + } + } + private fun onSelect(action: DeviceAction.SelectTab) { + _uiState.update { it.copy(selectedIndex = action.index) } } private fun sendCommand() { 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..0c7741917 --- /dev/null +++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconScrollableTabRow.kt @@ -0,0 +1,18 @@ +package io.github.openflocon.library.designsystem.components + +import androidx.compose.material3.ScrollableTabRow +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun FloconScrollableTabRow( + selectedTabIndex: Int, + modifier: Modifier = Modifier, + tabs: @Composable () -> Unit +) { + ScrollableTabRow( + selectedTabIndex = selectedTabIndex, + modifier = modifier, + 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..04d21f865 --- /dev/null +++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconTab.kt @@ -0,0 +1,38 @@ +package io.github.openflocon.library.designsystem.components + +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.material3.Tab +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun FloconTab( + selected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, + content: @Composable ColumnScope.() -> Unit +) { + Tab( + selected = selected, + onClick = onClick, + content = content, + modifier = modifier + ) +} + +@Composable +fun FloconTab( + text: String, + selected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + FloconTab( + selected = selected, + onClick = onClick, + modifier = modifier + ) { + Text(text) + } +} From b8f7db05dc55d0095c59f36b2f9dc60eff8404a7 Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Fri, 15 Aug 2025 17:43:26 +0200 Subject: [PATCH 05/24] feature: Rework window size --- .../openflocon/flocondesktop/device/DeviceScreen.kt | 13 ++++++++++--- .../common/ui/window/FloconWindow.desktop.kt | 13 +++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) rename FloconDesktop/composeApp/src/desktopMain/{java => kotlin}/io/github/openflocon/flocondesktop/common/ui/window/FloconWindow.desktop.kt (87%) 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 index 250ad6d7a..09ce805a9 100644 --- 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 @@ -3,6 +3,8 @@ package io.github.openflocon.flocondesktop.device import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.Text @@ -10,11 +12,12 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.openflocon.domain.device.models.DeviceId import io.github.openflocon.flocondesktop.common.ui.window.FloconWindow -import io.github.openflocon.flocondesktop.common.ui.window.createFloconWindowState +import io.github.openflocon.flocondesktop.common.ui.window.rememberFloconWindowState import io.github.openflocon.library.designsystem.FloconTheme import io.github.openflocon.library.designsystem.components.FloconScrollableTabRow import io.github.openflocon.library.designsystem.components.FloconSurface @@ -22,6 +25,7 @@ import io.github.openflocon.library.designsystem.components.FloconTab import org.jetbrains.compose.ui.tooling.preview.Preview import org.koin.compose.viewmodel.koinViewModel import org.koin.core.parameter.parametersOf +import java.awt.Dimension @Composable internal fun DeviceScreen( @@ -55,9 +59,12 @@ private fun Content( FloconWindow( title = "Device", onCloseRequest = onCloseRequest, - state = createFloconWindowState() + state = rememberFloconWindowState() ) { - FloconSurface { + window.minimumSize = Dimension(500, 500) // TODO + FloconSurface( + modifier = Modifier.fillMaxSize() + ) { Column { FloconScrollableTabRow( selectedTabIndex = uiState.selectedIndex 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 87% 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..95c486e0e 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 @@ -19,6 +19,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 From ea4ea1c2cf6e718a055dbac66170997fd5d5064b Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Fri, 15 Aug 2025 18:47:09 +0200 Subject: [PATCH 06/24] feature: dymsys --- .../flocondesktop/device/DeviceAction.kt | 2 + .../flocondesktop/device/DeviceScreen.kt | 123 +++++++++++++----- .../flocondesktop/device/DeviceViewModel.kt | 18 ++- 3 files changed, 107 insertions(+), 36 deletions(-) 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 index e470d84dc..6b4c53112 100644 --- 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 @@ -4,4 +4,6 @@ internal sealed interface DeviceAction { data class SelectTab(val index: Int) : DeviceAction + data object Refresh : 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 index 09ce805a9..8c1974179 100644 --- 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 @@ -1,24 +1,33 @@ package io.github.openflocon.flocondesktop.device +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column 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.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Refresh +import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect 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.domain.device.models.DeviceId import io.github.openflocon.flocondesktop.common.ui.window.FloconWindow import io.github.openflocon.flocondesktop.common.ui.window.rememberFloconWindowState import io.github.openflocon.library.designsystem.FloconTheme +import io.github.openflocon.library.designsystem.components.FloconIconButton import io.github.openflocon.library.designsystem.components.FloconScrollableTabRow import io.github.openflocon.library.designsystem.components.FloconSurface import io.github.openflocon.library.designsystem.components.FloconTab @@ -65,29 +74,48 @@ private fun Content( FloconSurface( modifier = Modifier.fillMaxSize() ) { - Column { - FloconScrollableTabRow( - selectedTabIndex = uiState.selectedIndex + Scaffold( + topBar = { + Column( + modifier = Modifier.background(FloconTheme.colorPalette.surfaceVariant) + ) { + Header( + uiState = uiState, + onAction = onAction + ) + FloconScrollableTabRow( + selectedTabIndex = uiState.selectedIndex + ) { + FloconTab( + text = "Info", + selected = true, + onClick = { onAction(DeviceAction.SelectTab(0)) } + ) + FloconTab( + text = "Permission", + selected = true, + onClick = { onAction(DeviceAction.SelectTab(1)) } + ) + } + } + }, + containerColor = FloconTheme.colorPalette.panel + ) { + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .padding(16.dp) + .padding(it) ) { - FloconTab( - text = "Info", - selected = true, - onClick = { onAction(DeviceAction.SelectTab(0)) } - ) - FloconTab( - text = "Permission", - selected = true, - onClick = { onAction(DeviceAction.SelectTab(1)) } - ) - } - HorizontalPager( - state = pagerState, - userScrollEnabled = false - ) { index -> - when (index) { - 0 -> InfoPage(uiState) - 1 -> PermissionPage() - else -> Unit + HorizontalPager( + state = pagerState, + userScrollEnabled = false + ) { index -> + when (index) { + 0 -> InfoPage(uiState) + 1 -> PermissionPage() + else -> Unit + } } } } @@ -95,19 +123,38 @@ private fun Content( } } +@Composable +private fun Header( + uiState: DeviceUiState, + onAction: (DeviceAction) -> Unit +) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "${uiState.model} (${uiState.serialNumber})", + style = FloconTheme.typography.headlineSmall, + modifier = Modifier.weight(1f) + ) + FloconIconButton( + imageVector = Icons.Outlined.Refresh, + onClick = { onAction(DeviceAction.Refresh) } + ) + } +} + @Composable private fun InfoPage( uiState: DeviceUiState ) { Column { - TextValue("Model:", uiState.model) - TextValue("Brand:", uiState.brand) - TextValue("CPU:", uiState.cpu) - TextValue("MEM:", uiState.mem) - TextValue("Battery:", uiState.battery) - TextValue("SerialNumber:", uiState.serialNumber) - TextValue("VersionRelease:", uiState.versionRelease) - TextValue("VersionSdk:", uiState.versionSdk) + TextValue("Brand", uiState.brand) + TextValue("CPU", uiState.cpu) + TextValue("MEM", uiState.mem) + TextValue("Battery", uiState.battery) + TextValue("SerialNumber", uiState.serialNumber) + TextValue("VersionRelease", uiState.versionRelease) + TextValue("VersionSdk", uiState.versionSdk) } } @@ -123,13 +170,21 @@ private fun TextValue( ) { Row( horizontalArrangement = Arrangement.spacedBy(4.dp), - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(2.dp) ) { Text( - text = label + text = label, + style = FloconTheme.typography.labelSmall, + modifier = Modifier.weight(1f) ) Text( - text = value + text = value, + style = FloconTheme.typography.labelSmall, + modifier = Modifier.weight(1f) + .clip(RoundedCornerShape(4.dp)) + .background(FloconTheme.colorPalette.surfaceVariant) + .padding(2.dp) ) } } 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 index 94695ec08..bda32f797 100644 --- 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 @@ -30,12 +30,14 @@ internal class DeviceViewModel( val uiState = _uiState.asStateFlow() init { - sendCommand() + onRefresh() + deviceInfo() } fun onAction(action: DeviceAction) { when (action) { is DeviceAction.SelectTab -> onSelect(action) + DeviceAction.Refresh -> onRefresh() } } @@ -43,7 +45,19 @@ internal class DeviceViewModel( _uiState.update { it.copy(selectedIndex = action.index) } } - private fun sendCommand() { + private fun onRefresh() { + viewModelScope.launch { + _uiState.update { state -> + state.copy( + cpu = sendCommandUseCase("shell", "dumpsys", "cpuinfo").getOrNull().orEmpty(), + battery = sendCommandUseCase("shell", "dumpsys", "battery").getOrNull().orEmpty(), + mem = sendCommandUseCase("shell", "dumpsys", "meminfo").getOrNull().orEmpty(), + ) + } + } + } + + private fun deviceInfo() { viewModelScope.launch { _uiState.update { state -> state.copy( From 5d60c068b2858593228c3397c1b2465f0e7aba45 Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Fri, 15 Aug 2025 20:20:19 +0200 Subject: [PATCH 07/24] feature: Add CPU info --- .../flocondesktop/device/DeviceScreen.kt | 53 +++------ .../flocondesktop/device/DeviceViewModel.kt | 107 +++++++++++++++++- .../designsystem/components/FloconTab.kt | 7 +- .../components/FloconTextValue.kt | 40 +++++++ 4 files changed, 167 insertions(+), 40 deletions(-) create mode 100644 FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconTextValue.kt 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 index 8c1974179..43864600d 100644 --- 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 @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Refresh @@ -20,7 +19,6 @@ import androidx.compose.runtime.LaunchedEffect 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.domain.device.models.DeviceId @@ -31,6 +29,7 @@ import io.github.openflocon.library.designsystem.components.FloconIconButton 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 io.github.openflocon.library.designsystem.components.FloconTextValue import org.jetbrains.compose.ui.tooling.preview.Preview import org.koin.compose.viewmodel.koinViewModel import org.koin.core.parameter.parametersOf @@ -77,7 +76,7 @@ private fun Content( Scaffold( topBar = { Column( - modifier = Modifier.background(FloconTheme.colorPalette.surfaceVariant) + modifier = Modifier.background(FloconTheme.colorPalette.surfaceVariant.copy(alpha = 1f)) ) { Header( uiState = uiState, @@ -134,7 +133,9 @@ private fun Header( Text( text = "${uiState.model} (${uiState.serialNumber})", style = FloconTheme.typography.headlineSmall, - modifier = Modifier.weight(1f) + modifier = Modifier + .weight(1f) + .padding(16.dp) ) FloconIconButton( imageVector = Icons.Outlined.Refresh, @@ -147,14 +148,16 @@ private fun Header( private fun InfoPage( uiState: DeviceUiState ) { - Column { - TextValue("Brand", uiState.brand) - TextValue("CPU", uiState.cpu) - TextValue("MEM", uiState.mem) - TextValue("Battery", uiState.battery) - TextValue("SerialNumber", uiState.serialNumber) - TextValue("VersionRelease", uiState.versionRelease) - TextValue("VersionSdk", uiState.versionSdk) + Column( + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + FloconTextValue("Brand", uiState.brand) + FloconTextValue("CPU", uiState.cpu) + FloconTextValue("Memory", uiState.mem) + FloconTextValue("Battery", uiState.battery) + FloconTextValue("Serial number", uiState.serialNumber) + FloconTextValue("Version - Release", uiState.versionRelease) + FloconTextValue("Version - Sdk", uiState.versionSdk) } } @@ -163,32 +166,6 @@ private fun PermissionPage() { } -@Composable -private fun TextValue( - label: String, - value: String -) { - 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) - ) - Text( - text = value, - style = FloconTheme.typography.labelSmall, - modifier = Modifier.weight(1f) - .clip(RoundedCornerShape(4.dp)) - .background(FloconTheme.colorPalette.surfaceVariant) - .padding(2.dp) - ) - } -} - @Composable @Preview private fun Preview() { 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 index bda32f797..7e54a68a4 100644 --- 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 @@ -49,7 +49,7 @@ internal class DeviceViewModel( viewModelScope.launch { _uiState.update { state -> state.copy( - cpu = sendCommandUseCase("shell", "dumpsys", "cpuinfo").getOrNull().orEmpty(), + cpu = main(sendCommandUseCase("shell", "dumpsys", "cpuinfo").getOrNull().orEmpty()), battery = sendCommandUseCase("shell", "dumpsys", "battery").getOrNull().orEmpty(), mem = sendCommandUseCase("shell", "dumpsys", "meminfo").getOrNull().orEmpty(), ) @@ -71,4 +71,109 @@ internal class DeviceViewModel( } } + data class ProcessCpuInfo( + val pid: Int, + val cpuPercent: Double, + val name: String + ) + + data class GlobalCpuLoad( + val load1m: Double, + val load5m: Double, + val load15m: Double + ) + + fun parseCpuInfoOutput(output: String): Pair> { + val processes = mutableListOf() + var globalLoad: GlobalCpuLoad? = null + + // Regex pour capturer les lignes de processus. + // Similaire à la version Python, peut nécessiter des ajustements. + // Groupes : 1: CPU%, 2: PID, 3: Name + val processLineRegex = Regex( + """^\s*([\d.]+)[k%]?\s+""" + // CPU % (ex: "10.0", "0.0k", "5%") + """(?:\S+\s+)*?""" + // Skip other columns non-greedily + """(\d+)\s+""" + // PID + """(?:[a-zA-Z0-9_/.-]+\s+)*?""" + // More potential intermediate columns + """([a-zA-Z0-9_.-]+(?:[:][a-zA-Z0-9_.-]+)?(?:\s*\[.*?])?)$""" // Process name/package + ) + + val loadAverageRegex = Regex("""Load: (\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)""") + + output.lines().forEach { line -> + // Recherche de la ligne de charge globale + loadAverageRegex.find(line)?.let { matchResult -> + try { + globalLoad = GlobalCpuLoad( + load1m = matchResult.groupValues[1].toDouble(), + load5m = matchResult.groupValues[2].toDouble(), + load15m = matchResult.groupValues[3].toDouble() + ) + return@forEach // Similaire à 'continue' dans une boucle classique + } catch (e: NumberFormatException) { + // Gérer le cas où la conversion en Double échoue + } + } + + + // Recherche des lignes de processus + processLineRegex.find(line)?.let { matchResult -> + try { + val cpuPercentStr = matchResult.groupValues[1].replace("k", "") + val cpuPercent = cpuPercentStr.toDouble() + val pid = matchResult.groupValues[2].toInt() + val name = matchResult.groupValues[3].trim() + + // Ignorer les lignes qui ne sont pas de vrais processus + if (name.equals("Name", ignoreCase = true) || name.equals("PID", ignoreCase = true)) { + return@forEach + } + + processes.add(ProcessCpuInfo(pid, cpuPercent, name)) + } catch (e: NumberFormatException) { + // println("Skipping line due to NumberFormatException: $line -> ${e.message}") + } catch (e: IndexOutOfBoundsException) { + // println("Skipping line due to IndexOutOfBoundsException (regex group not found): $line") + } + } + } + + return globalLoad to processes.sortedByDescending { it.cpuPercent } + } + + fun main(cpuOutput: String?): String { + println("Récupération des informations CPU...\n") + + if (cpuOutput != null) { + val (globalLoad, processList) = parseCpuInfoOutput(cpuOutput) + + return buildString { + globalLoad?.let { + appendLine("Charge CPU globale (Load Average):") + appendLine(" 1 min: %.2f".format(it.load1m)) + appendLine(" 5 min: %.2f".format(it.load5m)) + appendLine(" 15 min: %.2f".format(it.load15m)) + appendLine("-".repeat(30)) + } + + if (processList.isNotEmpty()) { + appendLine("%-8s %-7s %s".format("CPU%", "PID", "Nom du Processus")) + appendLine("%-8s %-7s %s".format("----", "---", "----------------")) + processList.forEach { proc -> + appendLine("%-8.1f %-7d %s".format(proc.cpuPercent, proc.pid, proc.name)) + } + } else { + appendLine("Aucun processus n'a pu être parsé.") + appendLine("\nSortie brute pour débogage (premiers 1000 caractères):") + appendLine(cpuOutput.take(1000)) + } + } + } else { + println("Impossible de récupérer la sortie de dumpsys cpuinfo.") + } + + return "" + } + + } 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 index 04d21f865..b39bf202f 100644 --- 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 @@ -1,10 +1,12 @@ package io.github.openflocon.library.designsystem.components import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.padding import androidx.compose.material3.Tab import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp @Composable fun FloconTab( @@ -33,6 +35,9 @@ fun FloconTab( onClick = onClick, modifier = modifier ) { - Text(text) + 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..06a815a46 --- /dev/null +++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconTextValue.kt @@ -0,0 +1,40 @@ +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.shape.RoundedCornerShape +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.unit.dp +import io.github.openflocon.library.designsystem.FloconTheme + +@Composable +fun FloconTextValue( + label: String, + value: String +) { + 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) + ) + Text( + text = value, + style = FloconTheme.typography.labelSmall, + modifier = Modifier.weight(1f) + .clip(RoundedCornerShape(4.dp)) + .background(FloconTheme.colorPalette.surfaceVariant) + .padding(4.dp) + ) + } +} From 29c00a42d423f060e9c6398748cf9fe752dcddab Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Fri, 5 Sep 2025 13:51:03 +0200 Subject: [PATCH 08/24] fix: Usecase --- .../ui/view/topbar/device/TopBarDeviceView.kt | 35 +++++++++++++++---- .../common/ui/window/FloconWindow.kt | 3 +- .../flocondesktop/device/DeviceScreen.kt | 11 +++--- .../flocondesktop/device/DeviceViewModel.kt | 35 +++++++++++++------ .../common/ui/window/FloconWindow.desktop.kt | 27 +++++++------- .../io/github/openflocon/domain/adb/DI.kt | 5 +++ .../{ => usecase}/ExecuteAdbCommandUseCase.kt | 2 +- .../adb/usecase/GetDeviceSerialUseCase.kt | 13 +++++++ .../domain/adb/usecase/SendCommandUseCase.kt | 8 +++-- .../usecase/ExecuteDeeplinkUseCase.kt | 2 +- .../usecase/StartRecordingVideoUseCase.kt | 2 +- .../usecase/StopRecordingVideoUseCase.kt | 2 +- .../device/usecase/TakeScreenshotUseCase.kt | 2 +- .../usecase/StartAdbForwardUseCase.kt | 2 +- .../domain/settings/usecase/TestAdbUseCase.kt | 2 +- .../components/FloconTextValue.kt | 4 +-- 16 files changed, 107 insertions(+), 48 deletions(-) rename FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/{ => usecase}/ExecuteAdbCommandUseCase.kt (95%) create mode 100644 FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/adb/usecase/GetDeviceSerialUseCase.kt 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..e2b48e5ed 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 @@ -23,6 +22,11 @@ import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.MobileOff import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -49,6 +53,8 @@ internal fun TopBarDeviceView( selected: Boolean = false, onDelete: (() -> Unit)? = null, ) { + var openDetail by remember { mutableStateOf(false) } + Row( modifier = modifier .then( @@ -125,15 +131,21 @@ internal fun TopBarDeviceView( ), ) } + FloconIconButton( + onClick = { openDetail = true } + ) { + FloconIcon( + imageVector = Icons.Outlined.Details + ) + } if (!selected && onDelete != null) { 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( @@ -145,6 +157,15 @@ internal fun TopBarDeviceView( } } } + + if (openDetail) { + key(device.id) { + DeviceScreen( + deviceId = device.id, + onCloseRequest = { openDetail = false } + ) + } + } } @Preview 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/DeviceScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceScreen.kt index 43864600d..b7bd6913c 100644 --- 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 @@ -23,7 +23,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.openflocon.domain.device.models.DeviceId import io.github.openflocon.flocondesktop.common.ui.window.FloconWindow -import io.github.openflocon.flocondesktop.common.ui.window.rememberFloconWindowState +import io.github.openflocon.flocondesktop.common.ui.window.createFloconWindowState import io.github.openflocon.library.designsystem.FloconTheme import io.github.openflocon.library.designsystem.components.FloconIconButton import io.github.openflocon.library.designsystem.components.FloconScrollableTabRow @@ -33,7 +33,6 @@ import io.github.openflocon.library.designsystem.components.FloconTextValue import org.jetbrains.compose.ui.tooling.preview.Preview import org.koin.compose.viewmodel.koinViewModel import org.koin.core.parameter.parametersOf -import java.awt.Dimension @Composable internal fun DeviceScreen( @@ -67,16 +66,16 @@ private fun Content( FloconWindow( title = "Device", onCloseRequest = onCloseRequest, - state = rememberFloconWindowState() + state = createFloconWindowState() ) { - window.minimumSize = Dimension(500, 500) // TODO +// window.minimumSize = Dimension(500, 500) // TODO FloconSurface( modifier = Modifier.fillMaxSize() ) { Scaffold( topBar = { Column( - modifier = Modifier.background(FloconTheme.colorPalette.surfaceVariant.copy(alpha = 1f)) + modifier = Modifier.background(FloconTheme.colorPalette.primary.copy(alpha = 1f)) ) { Header( uiState = uiState, @@ -98,7 +97,7 @@ private fun Content( } } }, - containerColor = FloconTheme.colorPalette.panel + containerColor = FloconTheme.colorPalette.primary ) { Column( modifier = Modifier 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 index 7e54a68a4..2f41406f1 100644 --- 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 @@ -2,6 +2,7 @@ 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 kotlinx.coroutines.flow.MutableStateFlow @@ -10,8 +11,9 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch internal class DeviceViewModel( - val deviceSerial: String, - val sendCommandUseCase: SendCommandUseCase + val deviceId: String, + val sendCommandUseCase: SendCommandUseCase, + val deviceSerialUseCase: GetDeviceSerialUseCase ) : ViewModel() { private val _uiState = MutableStateFlow( @@ -29,9 +31,15 @@ internal class DeviceViewModel( ) val uiState = _uiState.asStateFlow() + private var deviceSerial: String = "" + init { onRefresh() deviceInfo() + + viewModelScope.launch { + deviceSerial = deviceSerialUseCase(deviceId) + } } fun onAction(action: DeviceAction) { @@ -49,9 +57,9 @@ internal class DeviceViewModel( viewModelScope.launch { _uiState.update { state -> state.copy( - cpu = main(sendCommandUseCase("shell", "dumpsys", "cpuinfo").getOrNull().orEmpty()), - battery = sendCommandUseCase("shell", "dumpsys", "battery").getOrNull().orEmpty(), - mem = sendCommandUseCase("shell", "dumpsys", "meminfo").getOrNull().orEmpty(), + cpu = main(sendCommand("shell", "dumpsys", "cpuinfo")), + battery = sendCommand("shell", "dumpsys", "battery"), + mem = sendCommand("shell", "dumpsys", "meminfo") ) } } @@ -61,16 +69,23 @@ internal class DeviceViewModel( viewModelScope.launch { _uiState.update { state -> state.copy( - model = sendCommandUseCase("shell", "getprop", "ro.product.model").getOrNull().orEmpty().replace("\n", ""), - brand = sendCommandUseCase("shell", "getprop", "ro.product.brand").getOrNull().orEmpty().replace("\n", ""), - versionRelease = sendCommandUseCase("shell", "getprop", "ro.build.version.release").getOrNull().orEmpty().replace("\n", ""), - versionSdk = sendCommandUseCase("shell", "getprop", "ro.build.version.sdk").getOrNull().orEmpty().replace("\n", ""), - serialNumber = sendCommandUseCase("shell", "getprop", "ro.serialno").getOrNull().orEmpty().replace("\n", ""), + 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") ) } } } + private suspend fun sendCommand(vararg args: String): String { + return sendCommandUseCase(deviceSerial, *args) + .getOrNull() + .orEmpty() + .replace("\n", "") + } + data class ProcessCpuInfo( val pid: Int, val cpuPercent: Double, diff --git a/FloconDesktop/composeApp/src/desktopMain/kotlin/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 index 95c486e0e..76a5024e0 100644 --- a/FloconDesktop/composeApp/src/desktopMain/kotlin/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,18 +20,18 @@ 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), - ) - ) -} +//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, @@ -51,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 index cc5e8fcd0..9845b412a 100644 --- 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 @@ -10,10 +10,14 @@ class SendCommandUseCase( private val settingsRepository: SettingsRepository ) { - suspend operator fun invoke(vararg args: String): Either = Either.catch { + 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 = " ") + 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/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 index 06a815a46..13f7655b1 100644 --- 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 @@ -32,8 +32,8 @@ fun FloconTextValue( text = value, style = FloconTheme.typography.labelSmall, modifier = Modifier.weight(1f) - .clip(RoundedCornerShape(4.dp)) - .background(FloconTheme.colorPalette.surfaceVariant) + .clip(FloconTheme.shapes.small) + .background(FloconTheme.colorPalette.primary) .padding(4.dp) ) } From 63e6ec5a2757697eddd7c31e77ceffed0ae6b681 Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Mon, 22 Sep 2025 15:55:47 +0200 Subject: [PATCH 09/24] feature: Improve UI & Add permission --- .../src/main/AndroidManifest.xml | 3 + FloconDesktop/build.gradle.kts | 2 +- FloconDesktop/composeApp/build.gradle.kts | 2 +- .../flocondesktop/device/DeviceAction.kt | 2 + .../flocondesktop/device/DeviceScreen.kt | 101 +++++++++++++----- .../flocondesktop/device/DeviceUiState.kt | 14 ++- .../flocondesktop/device/DeviceViewModel.kt | 72 +++++++++++-- .../flocondesktop/device/Permissions.kt | 19 ++++ .../library/designsystem/build.gradle.kts | 2 +- .../components/FloconScrollableTabRow.kt | 3 + .../designsystem/components/FloconTab.kt | 12 ++- 11 files changed, 192 insertions(+), 40 deletions(-) create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/Permissions.kt 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"> + + + - when (index) { - 0 -> InfoPage(uiState) - 1 -> PermissionPage() - else -> Unit - } + ) { index -> + when (index) { + 0 -> InfoPage(uiState) + 1 -> PermissionPage( + uiState = uiState, + onAction = onAction + ) + + else -> Unit } } } @@ -161,8 +173,45 @@ private fun InfoPage( } @Composable -private fun PermissionPage() { - +private fun PermissionPage( + uiState: DeviceUiState, + onAction: (DeviceAction) -> Unit +) { + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + items( + items = uiState.permissions, + key = { it.name } + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = { +// onAction( +// DeviceAction.ChangePermission( +// permissions = it.permissions, +// granted = it.granted +// ) +// ) + }) + ) { + Text( + text = it.name, + modifier = Modifier.weight(1f) + ) + Text( + text = it.status + ) + FloconCheckbox( + checked = it.granted, + uncheckedColor = FloconTheme.colorPalette.secondary, + onCheckedChange = {} + ) + } + } + } } @Composable diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceUiState.kt index e280f34dd..70840cd8e 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceUiState.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceUiState.kt @@ -13,7 +13,16 @@ data class DeviceUiState( val serialNumber: String, val battery: String, val cpu: String, - val mem: String + val mem: String, + + val permissions: List +) + +@Immutable +data class PermissionUiState( + val name: String, + val status: String, + val granted: Boolean ) internal fun previewDeviceUiState() = DeviceUiState( @@ -26,5 +35,6 @@ internal fun previewDeviceUiState() = DeviceUiState( serialNumber = "", battery = "", cpu = "", - mem = "" + mem = "", + permissions = emptyList() ) 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 index 2f41406f1..a8e7d4d0b 100644 --- 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 @@ -5,6 +5,8 @@ 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.domain.device.usecase.GetCurrentDeviceIdAndPackageNameUseCase +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update @@ -13,7 +15,8 @@ import kotlinx.coroutines.launch internal class DeviceViewModel( val deviceId: String, val sendCommandUseCase: SendCommandUseCase, - val deviceSerialUseCase: GetDeviceSerialUseCase + val deviceSerialUseCase: GetDeviceSerialUseCase, + val currentDeviceAppsUseCase: GetCurrentDeviceIdAndPackageNameUseCase ) : ViewModel() { private val _uiState = MutableStateFlow( @@ -26,7 +29,8 @@ internal class DeviceViewModel( serialNumber = "", battery = "", cpu = "", - mem = "" + mem = "", + permissions = emptyList() ) ) val uiState = _uiState.asStateFlow() @@ -34,11 +38,12 @@ internal class DeviceViewModel( private var deviceSerial: String = "" init { - onRefresh() - deviceInfo() - - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { deviceSerial = deviceSerialUseCase(deviceId) + + onRefresh() + deviceInfo() + fetchPermission() } } @@ -46,6 +51,17 @@ internal class DeviceViewModel( when (action) { is DeviceAction.SelectTab -> onSelect(action) DeviceAction.Refresh -> onRefresh() + is DeviceAction.ChangePermission -> onChangePermission(action) + } + } + + private fun onChangePermission(action: DeviceAction.ChangePermission) { + viewModelScope.launch { + if (action.granted) { + revokePermission(action.permissions) + } else { + grantPermission(action.permissions) + } } } @@ -79,11 +95,52 @@ internal class DeviceViewModel( } } + private fun fetchPermission() { + viewModelScope.launch { + val packageName = currentDeviceAppsUseCase()?.packageName ?: return@launch + val command = sendCommand("shell", "cmd", "appops", "get", packageName) + val permissions = command.lines() + .mapNotNull { line -> + val list = line.split(":") + + PermissionUiState( + name = list.getOrNull(0) ?: return@mapNotNull null, + status = list.getOrNull(1) ?: return@mapNotNull null, + granted = false + ) + } + .sortedBy(PermissionUiState::name) + +// val granted = sendCommand("shell", "dumpsys", "package", packageName, "|", "grep", "permission") +// val permissions = Permissions.entries +// .map { permissions -> +// PermissionUiState( +// permissions = permissions, +// granted = granted.contains(permissions.value) +// ) +// } + + _uiState.update { it.copy(permissions = permissions) } + } + } + + private suspend fun grantPermission(permissions: Permissions) { + val packageName = currentDeviceAppsUseCase() ?: return + + sendCommand("shell", "pm", "grant", packageName.packageName, permissions.value) + } + + private suspend fun revokePermission(permissions: Permissions) { + val packageName = currentDeviceAppsUseCase() ?: return + + sendCommand("shell", "pm", "revoke", packageName.packageName, permissions.value) + } + private suspend fun sendCommand(vararg args: String): String { return sendCommandUseCase(deviceSerial, *args) .getOrNull() .orEmpty() - .replace("\n", "") + .removeSuffix("\n") } data class ProcessCpuInfo( @@ -190,5 +247,4 @@ internal class DeviceViewModel( return "" } - } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/Permissions.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/Permissions.kt new file mode 100644 index 000000000..1257e259c --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/Permissions.kt @@ -0,0 +1,19 @@ +package io.github.openflocon.flocondesktop.device + +enum class Permissions( + val label: String, + val value: String, +) { + BACKGROUND_LOCATION( + label = "Background location", + value = "android.permission.ACCESS_BACKGROUND_LOCATION" + ), + ACCESS_COARSE_LOCATION( + label = "Approximate location", + value = "android.permission.ACCESS_COARSE_LOCATION" + ), + ACCESS_FINE_LOCATION( + label = "Precise location", + value = "android.permission.ACCESS_FINE_LOCATION" + ) +} 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/FloconScrollableTabRow.kt b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconScrollableTabRow.kt index 0c7741917..a5d2c7646 100644 --- 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 @@ -3,6 +3,7 @@ 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( @@ -13,6 +14,8 @@ fun FloconScrollableTabRow( 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 index b39bf202f..0e512fafe 100644 --- 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 @@ -2,10 +2,12 @@ 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 @@ -13,12 +15,16 @@ 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 ) } @@ -28,11 +34,15 @@ fun FloconTab( text: String, selected: Boolean, onClick: () -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + selectedContentColor: Color = LocalContentColor.current, + unselectedContentColor: Color = selectedContentColor, ) { FloconTab( selected = selected, onClick = onClick, + selectedContentColor = selectedContentColor, + unselectedContentColor = unselectedContentColor, modifier = modifier ) { Text( From da69d593820a467640825693a4aaa15d17cccd86 Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Mon, 22 Sep 2025 16:51:43 +0200 Subject: [PATCH 10/24] fix: Permission --- .../flocondesktop/device/DeviceAction.kt | 2 +- .../flocondesktop/device/DeviceScreen.kt | 51 ++++---- .../flocondesktop/device/DeviceUiState.kt | 40 ------ .../flocondesktop/device/DeviceViewModel.kt | 116 +++++++++++------- .../flocondesktop/device/Permissions.kt | 19 --- .../device/models/ContentUiState.kt | 12 ++ .../flocondesktop/device/models/CpuUiState.kt | 17 +++ .../device/models/DeviceUiState.kt | 20 +++ .../device/models/InfoUiState.kt | 22 ++++ .../device/models/MemoryUiState.kt | 17 +++ .../device/models/PermissionUiState.kt | 18 +++ .../designsystem/components/FloconCheckbox.kt | 2 +- .../components/FloconTextValue.kt | 18 +-- 13 files changed, 222 insertions(+), 132 deletions(-) delete mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceUiState.kt delete mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/Permissions.kt create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/ContentUiState.kt create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/CpuUiState.kt create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/DeviceUiState.kt create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/InfoUiState.kt create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/MemoryUiState.kt create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/PermissionUiState.kt 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 index 63ec2162f..0d5454566 100644 --- 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 @@ -6,6 +6,6 @@ internal sealed interface DeviceAction { data object Refresh : DeviceAction - data class ChangePermission(val permissions: Permissions, val granted: Boolean) : DeviceAction + data class ChangePermission(val permission: String, val granted: Boolean) : 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 index dc091e144..511564042 100644 --- 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 @@ -20,11 +20,14 @@ import androidx.compose.runtime.LaunchedEffect 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.domain.device.models.DeviceId import io.github.openflocon.flocondesktop.common.ui.window.FloconWindow import io.github.openflocon.flocondesktop.common.ui.window.createFloconWindowState +import io.github.openflocon.flocondesktop.device.models.DeviceUiState +import io.github.openflocon.flocondesktop.device.models.previewDeviceUiState import io.github.openflocon.library.designsystem.FloconTheme import io.github.openflocon.library.designsystem.components.FloconCheckbox import io.github.openflocon.library.designsystem.components.FloconHorizontalDivider @@ -63,12 +66,12 @@ private fun Content( ) { val pagerState = rememberPagerState { 2 } - LaunchedEffect(uiState.selectedIndex) { - pagerState.animateScrollToPage(uiState.selectedIndex) + LaunchedEffect(uiState.contentState.selectedIndex) { + pagerState.animateScrollToPage(uiState.contentState.selectedIndex) } FloconWindow( - title = "Device", + title = "Device - ${uiState.infoState.model}", onCloseRequest = onCloseRequest, state = createFloconWindowState() ) { @@ -86,18 +89,18 @@ private fun Content( onAction = onAction ) FloconScrollableTabRow( - selectedTabIndex = uiState.selectedIndex, + selectedTabIndex = uiState.contentState.selectedIndex, modifier = Modifier.fillMaxWidth() ) { FloconTab( text = "Info", - selected = true, + selected = uiState.contentState.selectedIndex == 0, onClick = { onAction(DeviceAction.SelectTab(0)) }, selectedContentColor = FloconTheme.colorPalette.onSurface ) FloconTab( text = "Permission", - selected = true, + selected = uiState.contentState.selectedIndex == 1, onClick = { onAction(DeviceAction.SelectTab(1)) }, selectedContentColor = FloconTheme.colorPalette.onSurface ) @@ -112,8 +115,8 @@ private fun Content( HorizontalPager( state = pagerState, userScrollEnabled = false, - contentPadding = PaddingValues(16.dp), - pageSpacing = 16.dp, + contentPadding = PaddingValues(8.dp), + pageSpacing = 8.dp, modifier = Modifier .fillMaxSize() .padding(it) @@ -142,7 +145,7 @@ private fun Header( verticalAlignment = Alignment.CenterVertically ) { Text( - text = "${uiState.model} (${uiState.serialNumber})", + text = "${uiState.infoState.model} (${uiState.infoState.serialNumber})", style = FloconTheme.typography.headlineSmall, modifier = Modifier .weight(1f) @@ -160,15 +163,16 @@ private fun InfoPage( uiState: DeviceUiState ) { Column( - verticalArrangement = Arrangement.spacedBy(4.dp) + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.fillMaxSize() ) { - FloconTextValue("Brand", uiState.brand) - FloconTextValue("CPU", uiState.cpu) - FloconTextValue("Memory", uiState.mem) - FloconTextValue("Battery", uiState.battery) - FloconTextValue("Serial number", uiState.serialNumber) - FloconTextValue("Version - Release", uiState.versionRelease) - FloconTextValue("Version - Sdk", uiState.versionSdk) + FloconTextValue("Brand", uiState.infoState.brand) +// FloconTextValue("CPU", uiState.cpu) +// FloconTextValue("Memory", uiState.mem) + FloconTextValue("Battery", uiState.infoState.battery) + FloconTextValue("Serial number", uiState.infoState.serialNumber) + FloconTextValue("Version - Release", uiState.infoState.versionRelease) + FloconTextValue("Version - Sdk", uiState.infoState.versionSdk) } } @@ -181,13 +185,15 @@ private fun PermissionPage( modifier = Modifier.fillMaxSize() ) { items( - items = uiState.permissions, + items = uiState.permissionState.list, key = { it.name } ) { Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() + .clip(FloconTheme.shapes.medium) .clickable(onClick = { // onAction( // DeviceAction.ChangePermission( @@ -196,18 +202,17 @@ private fun PermissionPage( // ) // ) }) + .padding(4.dp) ) { Text( text = it.name, + style = FloconTheme.typography.labelSmall, modifier = Modifier.weight(1f) ) - Text( - text = it.status - ) FloconCheckbox( checked = it.granted, - uncheckedColor = FloconTheme.colorPalette.secondary, - onCheckedChange = {} + onCheckedChange = null, + uncheckedColor = FloconTheme.colorPalette.primary, ) } } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceUiState.kt deleted file mode 100644 index 70840cd8e..000000000 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceUiState.kt +++ /dev/null @@ -1,40 +0,0 @@ -package io.github.openflocon.flocondesktop.device - -import androidx.compose.runtime.Immutable - -@Immutable -data class DeviceUiState( - val selectedIndex: Int, - - val model: String, - val brand: String, - val versionRelease: String, - val versionSdk: String, - val serialNumber: String, - val battery: String, - val cpu: String, - val mem: String, - - val permissions: List -) - -@Immutable -data class PermissionUiState( - val name: String, - val status: String, - val granted: Boolean -) - -internal fun previewDeviceUiState() = DeviceUiState( - selectedIndex = 0, - - model = "", - brand = "", - versionRelease = "", - versionSdk = "", - serialNumber = "", - battery = "", - cpu = "", - mem = "", - permissions = emptyList() -) 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 index a8e7d4d0b..94a08d70a 100644 --- 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 @@ -6,9 +6,18 @@ 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.domain.device.usecase.GetCurrentDeviceIdAndPackageNameUseCase +import io.github.openflocon.flocondesktop.device.models.ContentUiState +import io.github.openflocon.flocondesktop.device.models.CpuUiState +import io.github.openflocon.flocondesktop.device.models.DeviceUiState +import io.github.openflocon.flocondesktop.device.models.InfoUiState +import io.github.openflocon.flocondesktop.device.models.MemoryUiState +import io.github.openflocon.flocondesktop.device.models.PermissionItem +import io.github.openflocon.flocondesktop.device.models.PermissionUiState import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -19,21 +28,47 @@ internal class DeviceViewModel( val currentDeviceAppsUseCase: GetCurrentDeviceIdAndPackageNameUseCase ) : ViewModel() { - private val _uiState = MutableStateFlow( - DeviceUiState( - selectedIndex = 0, + private val contentState = MutableStateFlow(ContentUiState(selectedIndex = 0)) + private val infoState = MutableStateFlow( + InfoUiState( model = "", brand = "", versionRelease = "", versionSdk = "", serialNumber = "", - battery = "", - cpu = "", - mem = "", - permissions = emptyList() + battery = "" ) ) - val uiState = _uiState.asStateFlow() + private val memoryState = MutableStateFlow(MemoryUiState(emptyList())) + private val cpuState = MutableStateFlow(CpuUiState(emptyList())) + private val permissionState = MutableStateFlow(PermissionUiState(emptyList())) + + val uiState = combine( + contentState, + infoState, + memoryState, + cpuState, + permissionState + ) { content, info, memory, cpu, permission -> + DeviceUiState( + contentState = content, + infoState = info, + memoryState = memory, + cpuState = cpu, + permissionState = permission + ) + } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = DeviceUiState( + contentState = contentState.value, + infoState = infoState.value, + memoryState = memoryState.value, + cpuState = cpuState.value, + permissionState = permissionState.value + ) + ) private var deviceSerial: String = "" @@ -58,32 +93,32 @@ internal class DeviceViewModel( private fun onChangePermission(action: DeviceAction.ChangePermission) { viewModelScope.launch { if (action.granted) { - revokePermission(action.permissions) + revokePermission(action.permission) } else { - grantPermission(action.permissions) + grantPermission(action.permission) } } } private fun onSelect(action: DeviceAction.SelectTab) { - _uiState.update { it.copy(selectedIndex = action.index) } + contentState.update { it.copy(selectedIndex = action.index) } } private fun onRefresh() { viewModelScope.launch { - _uiState.update { state -> - state.copy( - cpu = main(sendCommand("shell", "dumpsys", "cpuinfo")), - battery = sendCommand("shell", "dumpsys", "battery"), - mem = sendCommand("shell", "dumpsys", "meminfo") - ) - } +// _uiState.update { state -> +// state.copy( +// cpu = main(sendCommand("shell", "dumpsys", "cpuinfo")), +// battery = sendCommand("shell", "dumpsys", "battery"), +// mem = sendCommand("shell", "dumpsys", "meminfo") +// ) +// } } } private fun deviceInfo() { viewModelScope.launch { - _uiState.update { state -> + infoState.update { state -> state.copy( model = sendCommand("shell", "getprop", "ro.product.model"), brand = sendCommand("shell", "getprop", "ro.product.brand"), @@ -98,42 +133,37 @@ internal class DeviceViewModel( private fun fetchPermission() { viewModelScope.launch { val packageName = currentDeviceAppsUseCase()?.packageName ?: return@launch - val command = sendCommand("shell", "cmd", "appops", "get", packageName) + val command = sendCommand("shell", "dumpsys", "package", packageName) val permissions = command.lines() + .dropWhile { !it.contains("install permissions:") } + .drop(1) + .takeWhile { it.contains("granted=") } + .map { it.trim() } + .filter { it.startsWith(PERMISSION_PREFIX) } .mapNotNull { line -> val list = line.split(":") - PermissionUiState( - name = list.getOrNull(0) ?: return@mapNotNull null, - status = list.getOrNull(1) ?: return@mapNotNull null, - granted = false + PermissionItem( + name = list.getOrNull(0)?.removePrefix(PERMISSION_PREFIX) ?: return@mapNotNull null, + granted = list.getOrNull(1)?.contains("granted=true") ?: return@mapNotNull null, ) } - .sortedBy(PermissionUiState::name) - -// val granted = sendCommand("shell", "dumpsys", "package", packageName, "|", "grep", "permission") -// val permissions = Permissions.entries -// .map { permissions -> -// PermissionUiState( -// permissions = permissions, -// granted = granted.contains(permissions.value) -// ) -// } - - _uiState.update { it.copy(permissions = permissions) } + .sortedBy(PermissionItem::name) + + permissionState.update { it.copy(list = permissions) } } } - private suspend fun grantPermission(permissions: Permissions) { + private suspend fun grantPermission(permission: String) { val packageName = currentDeviceAppsUseCase() ?: return - sendCommand("shell", "pm", "grant", packageName.packageName, permissions.value) + sendCommand("shell", "pm", "grant", packageName.packageName, "${PERMISSION_PREFIX}$permission") } - private suspend fun revokePermission(permissions: Permissions) { + private suspend fun revokePermission(permission: String) { val packageName = currentDeviceAppsUseCase() ?: return - sendCommand("shell", "pm", "revoke", packageName.packageName, permissions.value) + sendCommand("shell", "pm", "revoke", packageName.packageName, "$PERMISSION_PREFIX$permission") } private suspend fun sendCommand(vararg args: String): String { @@ -247,4 +277,8 @@ internal class DeviceViewModel( return "" } + companion object { + private const val PERMISSION_PREFIX = "android.permission." + } + } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/Permissions.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/Permissions.kt deleted file mode 100644 index 1257e259c..000000000 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/Permissions.kt +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.openflocon.flocondesktop.device - -enum class Permissions( - val label: String, - val value: String, -) { - BACKGROUND_LOCATION( - label = "Background location", - value = "android.permission.ACCESS_BACKGROUND_LOCATION" - ), - ACCESS_COARSE_LOCATION( - label = "Approximate location", - value = "android.permission.ACCESS_COARSE_LOCATION" - ), - ACCESS_FINE_LOCATION( - label = "Precise location", - value = "android.permission.ACCESS_FINE_LOCATION" - ) -} 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..ef09a8d3e --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/ContentUiState.kt @@ -0,0 +1,12 @@ +package io.github.openflocon.flocondesktop.device.models + +import androidx.compose.runtime.Immutable + +@Immutable +data class ContentUiState( + val selectedIndex: Int +) + +fun previewContentUiState() = ContentUiState( + selectedIndex = 0 +) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/CpuUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/CpuUiState.kt new file mode 100644 index 000000000..090eeb6c8 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/CpuUiState.kt @@ -0,0 +1,17 @@ +package io.github.openflocon.flocondesktop.device.models + +import androidx.compose.runtime.Immutable + +@Immutable +data class CpuUiState( + val list: List +) + +@Immutable +data class CpuItem( + val name: String +) + +fun previewCpuUiState() = CpuUiState( + list = emptyList() +) 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..3893c07d7 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/DeviceUiState.kt @@ -0,0 +1,20 @@ +package io.github.openflocon.flocondesktop.device.models + +import androidx.compose.runtime.Immutable + +@Immutable +data class DeviceUiState( + val contentState: ContentUiState, + val infoState: InfoUiState, + val cpuState: CpuUiState, + val memoryState: MemoryUiState, + val permissionState: PermissionUiState +) + +internal fun previewDeviceUiState() = DeviceUiState( + contentState = previewContentUiState(), + cpuState = previewCpuUiState(), + memoryState = previewMemoryUiState(), + infoState = previewInfoUiState(), + permissionState = previewPermissionUiState() +) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/InfoUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/InfoUiState.kt new file mode 100644 index 000000000..72c80782b --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/InfoUiState.kt @@ -0,0 +1,22 @@ +package io.github.openflocon.flocondesktop.device.models + +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/models/MemoryUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/MemoryUiState.kt new file mode 100644 index 000000000..755ab2a20 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/MemoryUiState.kt @@ -0,0 +1,17 @@ +package io.github.openflocon.flocondesktop.device.models + +import androidx.compose.runtime.Immutable + +@Immutable +data class MemoryUiState( + val list: List +) + +@Immutable +data class MemoryItem( + val name: String +) + +fun previewMemoryUiState() = MemoryUiState( + list = emptyList() +) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/PermissionUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/PermissionUiState.kt new file mode 100644 index 000000000..b11964944 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/PermissionUiState.kt @@ -0,0 +1,18 @@ +package io.github.openflocon.flocondesktop.device.models + +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( + list = emptyList() +) 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/FloconTextValue.kt b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconTextValue.kt index 13f7655b1..c160dab4c 100644 --- 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 @@ -4,7 +4,7 @@ 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.shape.RoundedCornerShape +import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -16,25 +16,29 @@ import io.github.openflocon.library.designsystem.FloconTheme @Composable fun FloconTextValue( label: String, - value: String + value: String, + modifier: Modifier = Modifier ) { Row( horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(2.dp) + modifier = modifier.padding(2.dp) ) { Text( text = label, style = FloconTheme.typography.labelSmall, modifier = Modifier.weight(1f) ) - Text( - text = value, - style = FloconTheme.typography.labelSmall, + SelectionContainer( modifier = Modifier.weight(1f) .clip(FloconTheme.shapes.small) .background(FloconTheme.colorPalette.primary) .padding(4.dp) - ) + ) { + Text( + text = value, + style = FloconTheme.typography.labelSmall + ) + } } } From dffcf8ef9088e67da27207d9509de3d0a177df5d Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Mon, 22 Sep 2025 17:12:23 +0200 Subject: [PATCH 11/24] fix: CPU --- .../flocondesktop/device/DeviceScreen.kt | 83 ++++++++++- .../flocondesktop/device/DeviceViewModel.kt | 130 +++++------------- .../flocondesktop/device/models/CpuUiState.kt | 8 +- 3 files changed, 118 insertions(+), 103 deletions(-) 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 index 511564042..e0513eef4 100644 --- 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 @@ -26,6 +26,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.openflocon.domain.device.models.DeviceId import io.github.openflocon.flocondesktop.common.ui.window.FloconWindow import io.github.openflocon.flocondesktop.common.ui.window.createFloconWindowState +import io.github.openflocon.flocondesktop.device.models.CpuItem import io.github.openflocon.flocondesktop.device.models.DeviceUiState import io.github.openflocon.flocondesktop.device.models.previewDeviceUiState import io.github.openflocon.library.designsystem.FloconTheme @@ -64,7 +65,7 @@ private fun Content( onCloseRequest: () -> Unit, onAction: (DeviceAction) -> Unit ) { - val pagerState = rememberPagerState { 2 } + val pagerState = rememberPagerState { 3 } LaunchedEffect(uiState.contentState.selectedIndex) { pagerState.animateScrollToPage(uiState.contentState.selectedIndex) @@ -99,11 +100,17 @@ private fun Content( selectedContentColor = FloconTheme.colorPalette.onSurface ) FloconTab( - text = "Permission", + text = "Cpu", selected = uiState.contentState.selectedIndex == 1, onClick = { onAction(DeviceAction.SelectTab(1)) }, selectedContentColor = FloconTheme.colorPalette.onSurface ) + FloconTab( + text = "Permission", + selected = uiState.contentState.selectedIndex == 2, + onClick = { onAction(DeviceAction.SelectTab(2)) }, + selectedContentColor = FloconTheme.colorPalette.onSurface + ) } FloconHorizontalDivider( modifier = Modifier.fillMaxWidth(), @@ -123,7 +130,12 @@ private fun Content( ) { index -> when (index) { 0 -> InfoPage(uiState) - 1 -> PermissionPage( + 1 -> CpuPage( + uiState = uiState, + onAction = onAction + ) + + 2 -> PermissionPage( uiState = uiState, onAction = onAction ) @@ -176,6 +188,71 @@ private fun InfoPage( } } +@Composable +private fun CpuPage( + uiState: DeviceUiState, + onAction: (DeviceAction) -> Unit +) { + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + items( + items = uiState.cpuState.list, + key = CpuItem::packageName + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clip(FloconTheme.shapes.medium) + .padding(4.dp) + ) { + Text( + text = it.cpuUsage.toString(), + style = FloconTheme.typography.labelSmall, + modifier = Modifier.weight(.1f) + ) + Text( + text = it.userPercentage.toString(), + style = FloconTheme.typography.labelSmall, + modifier = Modifier.weight(.1f) + ) + Text( + text = it.kernelPercentage.toString(), + style = FloconTheme.typography.labelSmall, + modifier = Modifier.weight(.1f) + ) + Text( + text = it.pId.toString(), + style = FloconTheme.typography.labelSmall, + modifier = Modifier.weight(.1f) + ) + Text( + text = it.packageName, + style = FloconTheme.typography.labelSmall, + modifier = Modifier.weight(1f) + ) + Text( + text = it.userPercentage.toString(), + style = FloconTheme.typography.labelSmall, + modifier = Modifier.weight(.1f) + ) + Text( + text = it.majorFaults.toString(), + style = FloconTheme.typography.labelSmall, + modifier = Modifier.weight(.1f) + ) + Text( + text = it.minorFaults.toString(), + style = FloconTheme.typography.labelSmall, + modifier = Modifier.weight(.1f) + ) + } + } + } +} + @Composable private fun PermissionPage( uiState: DeviceUiState, 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 index 94a08d70a..3ff230870 100644 --- 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 @@ -7,6 +7,7 @@ import io.github.openflocon.domain.adb.usecase.SendCommandUseCase import io.github.openflocon.domain.common.getOrNull import io.github.openflocon.domain.device.usecase.GetCurrentDeviceIdAndPackageNameUseCase import io.github.openflocon.flocondesktop.device.models.ContentUiState +import io.github.openflocon.flocondesktop.device.models.CpuItem import io.github.openflocon.flocondesktop.device.models.CpuUiState import io.github.openflocon.flocondesktop.device.models.DeviceUiState import io.github.openflocon.flocondesktop.device.models.InfoUiState @@ -105,8 +106,10 @@ internal class DeviceViewModel( } private fun onRefresh() { + refreshCpu() viewModelScope.launch { -// _uiState.update { state -> + + // _uiState.update { state -> // state.copy( // cpu = main(sendCommand("shell", "dumpsys", "cpuinfo")), // battery = sendCommand("shell", "dumpsys", "battery"), @@ -173,112 +176,41 @@ internal class DeviceViewModel( .removeSuffix("\n") } - data class ProcessCpuInfo( - val pid: Int, - val cpuPercent: Double, - val name: String - ) - - data class GlobalCpuLoad( - val load1m: Double, - val load5m: Double, - val load15m: Double - ) - - fun parseCpuInfoOutput(output: String): Pair> { - val processes = mutableListOf() - var globalLoad: GlobalCpuLoad? = null - - // Regex pour capturer les lignes de processus. - // Similaire à la version Python, peut nécessiter des ajustements. - // Groupes : 1: CPU%, 2: PID, 3: Name - val processLineRegex = Regex( - """^\s*([\d.]+)[k%]?\s+""" + // CPU % (ex: "10.0", "0.0k", "5%") - """(?:\S+\s+)*?""" + // Skip other columns non-greedily - """(\d+)\s+""" + // PID - """(?:[a-zA-Z0-9_/.-]+\s+)*?""" + // More potential intermediate columns - """([a-zA-Z0-9_.-]+(?:[:][a-zA-Z0-9_.-]+)?(?:\s*\[.*?])?)$""" // Process name/package - ) - - val loadAverageRegex = Regex("""Load: (\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)""") - - output.lines().forEach { line -> - // Recherche de la ligne de charge globale - loadAverageRegex.find(line)?.let { matchResult -> - try { - globalLoad = GlobalCpuLoad( - load1m = matchResult.groupValues[1].toDouble(), - load5m = matchResult.groupValues[2].toDouble(), - load15m = matchResult.groupValues[3].toDouble() - ) - return@forEach // Similaire à 'continue' dans une boucle classique - } catch (e: NumberFormatException) { - // Gérer le cas où la conversion en Double échoue - } - } - - - // Recherche des lignes de processus - processLineRegex.find(line)?.let { matchResult -> - try { - val cpuPercentStr = matchResult.groupValues[1].replace("k", "") - val cpuPercent = cpuPercentStr.toDouble() - val pid = matchResult.groupValues[2].toInt() - val name = matchResult.groupValues[3].trim() - - // Ignorer les lignes qui ne sont pas de vrais processus - if (name.equals("Name", ignoreCase = true) || name.equals("PID", ignoreCase = true)) { - return@forEach + 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 } - - processes.add(ProcessCpuInfo(pid, cpuPercent, name)) - } catch (e: NumberFormatException) { - // println("Skipping line due to NumberFormatException: $line -> ${e.message}") - } catch (e: IndexOutOfBoundsException) { - // println("Skipping line due to IndexOutOfBoundsException (regex group not found): $line") - } - } - } - - return globalLoad to processes.sortedByDescending { it.cpuPercent } - } - - fun main(cpuOutput: String?): String { - println("Récupération des informations CPU...\n") - - if (cpuOutput != null) { - val (globalLoad, processList) = parseCpuInfoOutput(cpuOutput) - - return buildString { - globalLoad?.let { - appendLine("Charge CPU globale (Load Average):") - appendLine(" 1 min: %.2f".format(it.load1m)) - appendLine(" 5 min: %.2f".format(it.load5m)) - appendLine(" 15 min: %.2f".format(it.load15m)) - appendLine("-".repeat(30)) } + .sortedBy(CpuItem::cpuUsage) + .distinctBy(CpuItem::packageName) + .toList() - if (processList.isNotEmpty()) { - appendLine("%-8s %-7s %s".format("CPU%", "PID", "Nom du Processus")) - appendLine("%-8s %-7s %s".format("----", "---", "----------------")) - processList.forEach { proc -> - appendLine("%-8.1f %-7d %s".format(proc.cpuPercent, proc.pid, proc.name)) - } - } else { - appendLine("Aucun processus n'a pu être parsé.") - appendLine("\nSortie brute pour débogage (premiers 1000 caractères):") - appendLine(cpuOutput.take(1000)) - } - } - } else { - println("Impossible de récupérer la sortie de dumpsys cpuinfo.") + cpuState.update { it.copy(list = items) } } - - return "" } companion object { private const val PERMISSION_PREFIX = "android.permission." + 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/models/CpuUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/CpuUiState.kt index 090eeb6c8..5a7fecad9 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/CpuUiState.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/CpuUiState.kt @@ -9,7 +9,13 @@ data class CpuUiState( @Immutable data class CpuItem( - val name: String + val cpuUsage: Double, + val pId: Int, + val packageName: String, + val userPercentage: Double, + val kernelPercentage: Double, + val minorFaults: Int?, + val majorFaults: Int? ) fun previewCpuUiState() = CpuUiState( From a9782a7d0ee38aa5b90a8e82641395aa0c52d6fc Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Mon, 22 Sep 2025 22:20:30 +0200 Subject: [PATCH 12/24] feature: Improve UI --- .../flocondesktop/device/DeviceScreen.kt | 160 +++--------------- .../flocondesktop/device/DeviceViewModel.kt | 17 +- .../flocondesktop/device/pages/CpuPage.kt | 141 +++++++++++++++ .../flocondesktop/device/pages/InfoPage.kt | 102 +++++++++++ .../device/pages/PermissionPage.kt | 79 +++++++++ .../components/FloconTextValue.kt | 10 +- 6 files changed, 356 insertions(+), 153 deletions(-) create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/CpuPage.kt create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/InfoPage.kt create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/PermissionPage.kt 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 index e0513eef4..1f78c4c81 100644 --- 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 @@ -1,6 +1,5 @@ package io.github.openflocon.flocondesktop.device -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -8,10 +7,9 @@ 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.items import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Refresh import androidx.compose.material3.Text @@ -20,17 +18,17 @@ import androidx.compose.runtime.LaunchedEffect 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.domain.device.models.DeviceId import io.github.openflocon.flocondesktop.common.ui.window.FloconWindow import io.github.openflocon.flocondesktop.common.ui.window.createFloconWindowState -import io.github.openflocon.flocondesktop.device.models.CpuItem import io.github.openflocon.flocondesktop.device.models.DeviceUiState import io.github.openflocon.flocondesktop.device.models.previewDeviceUiState +import io.github.openflocon.flocondesktop.device.pages.CpuPage +import io.github.openflocon.flocondesktop.device.pages.InfoPage +import io.github.openflocon.flocondesktop.device.pages.PermissionPage import io.github.openflocon.library.designsystem.FloconTheme -import io.github.openflocon.library.designsystem.components.FloconCheckbox import io.github.openflocon.library.designsystem.components.FloconHorizontalDivider import io.github.openflocon.library.designsystem.components.FloconIconButton import io.github.openflocon.library.designsystem.components.FloconScaffold @@ -129,14 +127,14 @@ private fun Content( .padding(it) ) { index -> when (index) { - 0 -> InfoPage(uiState) + 0 -> InfoPage(uiState.infoState) 1 -> CpuPage( - uiState = uiState, + state = uiState.cpuState, onAction = onAction ) 2 -> PermissionPage( - uiState = uiState, + state = uiState.permissionState, onAction = onAction ) @@ -156,143 +154,27 @@ private fun Header( Row( verticalAlignment = Alignment.CenterVertically ) { - Text( - text = "${uiState.infoState.model} (${uiState.infoState.serialNumber})", - style = FloconTheme.typography.headlineSmall, + Column( modifier = Modifier - .weight(1f) + .fillMaxWidth() .padding(16.dp) - ) - FloconIconButton( - imageVector = Icons.Outlined.Refresh, - onClick = { onAction(DeviceAction.Refresh) } - ) - } -} - -@Composable -private fun InfoPage( - uiState: DeviceUiState -) { - Column( - verticalArrangement = Arrangement.spacedBy(4.dp), - modifier = Modifier.fillMaxSize() - ) { - FloconTextValue("Brand", uiState.infoState.brand) -// FloconTextValue("CPU", uiState.cpu) -// FloconTextValue("Memory", uiState.mem) - FloconTextValue("Battery", uiState.infoState.battery) - FloconTextValue("Serial number", uiState.infoState.serialNumber) - FloconTextValue("Version - Release", uiState.infoState.versionRelease) - FloconTextValue("Version - Sdk", uiState.infoState.versionSdk) - } -} - -@Composable -private fun CpuPage( - uiState: DeviceUiState, - onAction: (DeviceAction) -> Unit -) { - LazyColumn( - modifier = Modifier.fillMaxSize() - ) { - items( - items = uiState.cpuState.list, - key = CpuItem::packageName - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(4.dp), - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .clip(FloconTheme.shapes.medium) - .padding(4.dp) - ) { - Text( - text = it.cpuUsage.toString(), - style = FloconTheme.typography.labelSmall, - modifier = Modifier.weight(.1f) - ) - Text( - text = it.userPercentage.toString(), - style = FloconTheme.typography.labelSmall, - modifier = Modifier.weight(.1f) - ) - Text( - text = it.kernelPercentage.toString(), - style = FloconTheme.typography.labelSmall, - modifier = Modifier.weight(.1f) - ) - Text( - text = it.pId.toString(), - style = FloconTheme.typography.labelSmall, - modifier = Modifier.weight(.1f) - ) - Text( - text = it.packageName, - style = FloconTheme.typography.labelSmall, - modifier = Modifier.weight(1f) - ) - Text( - text = it.userPercentage.toString(), - style = FloconTheme.typography.labelSmall, - modifier = Modifier.weight(.1f) - ) - Text( - text = it.majorFaults.toString(), - style = FloconTheme.typography.labelSmall, - modifier = Modifier.weight(.1f) - ) - Text( - text = it.minorFaults.toString(), - style = FloconTheme.typography.labelSmall, - modifier = Modifier.weight(.1f) - ) - } - } - } -} - -@Composable -private fun PermissionPage( - uiState: DeviceUiState, - onAction: (DeviceAction) -> Unit -) { - LazyColumn( - modifier = Modifier.fillMaxSize() - ) { - items( - items = uiState.permissionState.list, - key = { it.name } + .weight(1f) ) { - Row( - horizontalArrangement = Arrangement.spacedBy(4.dp), - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .clip(FloconTheme.shapes.medium) - .clickable(onClick = { -// onAction( -// DeviceAction.ChangePermission( -// permissions = it.permissions, -// granted = it.granted -// ) -// ) - }) - .padding(4.dp) - ) { + Text( + text = uiState.infoState.model, + style = FloconTheme.typography.headlineSmall + ) + SelectionContainer { Text( - text = it.name, - style = FloconTheme.typography.labelSmall, - modifier = Modifier.weight(1f) - ) - FloconCheckbox( - checked = it.granted, - onCheckedChange = null, - uncheckedColor = FloconTheme.colorPalette.primary, + text = uiState.infoState.serialNumber, + style = FloconTheme.typography.labelSmall ) } } + FloconIconButton( + imageVector = Icons.Outlined.Refresh, + onClick = { onAction(DeviceAction.Refresh) } + ) } } 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 index 3ff230870..02ab75aa0 100644 --- 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 @@ -78,8 +78,6 @@ internal class DeviceViewModel( deviceSerial = deviceSerialUseCase(deviceId) onRefresh() - deviceInfo() - fetchPermission() } } @@ -98,6 +96,7 @@ internal class DeviceViewModel( } else { grantPermission(action.permission) } + fetchPermission() } } @@ -107,15 +106,11 @@ internal class DeviceViewModel( private fun onRefresh() { refreshCpu() + deviceInfo() + fetchPermission() viewModelScope.launch { - - // _uiState.update { state -> -// state.copy( -// cpu = main(sendCommand("shell", "dumpsys", "cpuinfo")), -// battery = sendCommand("shell", "dumpsys", "battery"), + // battery = sendCommand("shell", "dumpsys", "battery"), // mem = sendCommand("shell", "dumpsys", "meminfo") -// ) -// } } } @@ -138,7 +133,7 @@ internal class DeviceViewModel( val packageName = currentDeviceAppsUseCase()?.packageName ?: return@launch val command = sendCommand("shell", "dumpsys", "package", packageName) val permissions = command.lines() - .dropWhile { !it.contains("install permissions:") } + .dropWhile { !it.contains("runtime permissions:") } .drop(1) .takeWhile { it.contains("granted=") } .map { it.trim() } @@ -200,7 +195,7 @@ internal class DeviceViewModel( null } } - .sortedBy(CpuItem::cpuUsage) + .sortedByDescending(CpuItem::cpuUsage) .distinctBy(CpuItem::packageName) .toList() diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/CpuPage.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/CpuPage.kt new file mode 100644 index 000000000..3112b2d0c --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/CpuPage.kt @@ -0,0 +1,141 @@ +package io.github.openflocon.flocondesktop.device.pages + +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.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 io.github.openflocon.flocondesktop.device.DeviceAction +import io.github.openflocon.flocondesktop.device.models.CpuUiState +import io.github.openflocon.library.designsystem.FloconTheme +import io.github.openflocon.library.designsystem.components.FloconHorizontalDivider + +@Composable +internal fun CpuPage( + state: CpuUiState, + onAction: (DeviceAction) -> 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 = state.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 != state.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/InfoPage.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/InfoPage.kt new file mode 100644 index 000000000..61dd681d2 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/InfoPage.kt @@ -0,0 +1,102 @@ +package io.github.openflocon.flocondesktop.device.pages + +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.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import io.github.openflocon.flocondesktop.device.models.InfoUiState +import io.github.openflocon.library.designsystem.FloconTheme +import io.github.openflocon.library.designsystem.components.FloconSection +import io.github.openflocon.library.designsystem.components.FloconTextValue + +@Composable +internal fun InfoPage( + state: InfoUiState +) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + ) { + General(state) + } +} + +@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("CPU", uiState.cpu) +// FloconTextValue("Memory", uiState.mem) + 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/PermissionPage.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/PermissionPage.kt new file mode 100644 index 000000000..facb77b12 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/PermissionPage.kt @@ -0,0 +1,79 @@ +package io.github.openflocon.flocondesktop.device.pages + +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.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import io.github.openflocon.flocondesktop.device.DeviceAction +import io.github.openflocon.flocondesktop.device.models.PermissionUiState +import io.github.openflocon.library.designsystem.FloconTheme +import io.github.openflocon.library.designsystem.components.FloconCheckbox +import io.github.openflocon.library.designsystem.components.FloconHorizontalDivider + +@Composable +internal fun PermissionPage( + state: PermissionUiState, + onAction: (DeviceAction) -> 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 = state.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( + DeviceAction.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 != state.list.lastIndex) { + FloconHorizontalDivider( + color = FloconTheme.colorPalette.secondary + ) + } + } + } +} 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 index c160dab4c..ea8300c98 100644 --- 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 @@ -10,14 +10,17 @@ 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 + modifier: Modifier = Modifier, + valueContainerColor: Color = FloconTheme.colorPalette.primary ) { Row( horizontalArrangement = Arrangement.spacedBy(4.dp), @@ -32,12 +35,13 @@ fun FloconTextValue( SelectionContainer( modifier = Modifier.weight(1f) .clip(FloconTheme.shapes.small) - .background(FloconTheme.colorPalette.primary) + .background(valueContainerColor) .padding(4.dp) ) { Text( text = value, - style = FloconTheme.typography.labelSmall + style = FloconTheme.typography.labelSmall, + color = FloconTheme.colorPalette.contentColorFor(valueContainerColor) ) } } From c9e25d02c821ef1de77aa8df9fd51d7bdca7b8ac Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Tue, 23 Sep 2025 09:48:07 +0200 Subject: [PATCH 13/24] fix: COmment --- .../io/github/openflocon/flocondesktop/device/pages/InfoPage.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/InfoPage.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/InfoPage.kt index 61dd681d2..0c37c2655 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/InfoPage.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/InfoPage.kt @@ -49,8 +49,6 @@ private fun General( value = state.brand, valueContainerColor = FloconTheme.colorPalette.secondary ) -// FloconTextValue("CPU", uiState.cpu) -// FloconTextValue("Memory", uiState.mem) FloconTextValue( label = "Battery", value = state.battery, From 65f0b068fc712ed716ec3041eb54c068a23d9afa Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Tue, 23 Sep 2025 15:36:27 +0200 Subject: [PATCH 14/24] feature: Add battery --- .../flocondesktop/device/DeviceScreen.kt | 25 ++- .../flocondesktop/device/DeviceViewModel.kt | 147 ++++++++++++++++-- .../device/models/BatteryUiState.kt | 46 ++++++ .../device/models/DeviceUiState.kt | 6 +- .../device/models/MemoryUiState.kt | 4 +- .../flocondesktop/device/pages/BatteryPage.kt | 93 +++++++++++ .../flocondesktop/device/pages/MemoryPage.kt | 100 ++++++++++++ 7 files changed, 405 insertions(+), 16 deletions(-) create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/BatteryUiState.kt create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/BatteryPage.kt create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/MemoryPage.kt 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 index 1f78c4c81..19ca3b844 100644 --- 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 @@ -23,10 +23,13 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.openflocon.domain.device.models.DeviceId import io.github.openflocon.flocondesktop.common.ui.window.FloconWindow import io.github.openflocon.flocondesktop.common.ui.window.createFloconWindowState +import io.github.openflocon.flocondesktop.device.models.BatteryUiState import io.github.openflocon.flocondesktop.device.models.DeviceUiState import io.github.openflocon.flocondesktop.device.models.previewDeviceUiState +import io.github.openflocon.flocondesktop.device.pages.BatteryPage import io.github.openflocon.flocondesktop.device.pages.CpuPage import io.github.openflocon.flocondesktop.device.pages.InfoPage +import io.github.openflocon.flocondesktop.device.pages.MemoryPage import io.github.openflocon.flocondesktop.device.pages.PermissionPage import io.github.openflocon.library.designsystem.FloconTheme import io.github.openflocon.library.designsystem.components.FloconHorizontalDivider @@ -63,7 +66,7 @@ private fun Content( onCloseRequest: () -> Unit, onAction: (DeviceAction) -> Unit ) { - val pagerState = rememberPagerState { 3 } + val pagerState = rememberPagerState { 5 } LaunchedEffect(uiState.contentState.selectedIndex) { pagerState.animateScrollToPage(uiState.contentState.selectedIndex) @@ -104,11 +107,23 @@ private fun Content( selectedContentColor = FloconTheme.colorPalette.onSurface ) FloconTab( - text = "Permission", + text = "Memory", selected = uiState.contentState.selectedIndex == 2, onClick = { onAction(DeviceAction.SelectTab(2)) }, selectedContentColor = FloconTheme.colorPalette.onSurface ) + FloconTab( + text = "Permission", + selected = uiState.contentState.selectedIndex == 3, + onClick = { onAction(DeviceAction.SelectTab(3)) }, + selectedContentColor = FloconTheme.colorPalette.onSurface + ) + FloconTab( + text = "Battery", + selected = uiState.contentState.selectedIndex == 4, + onClick = { onAction(DeviceAction.SelectTab(4)) }, + selectedContentColor = FloconTheme.colorPalette.onSurface + ) } FloconHorizontalDivider( modifier = Modifier.fillMaxWidth(), @@ -132,11 +147,15 @@ private fun Content( state = uiState.cpuState, onAction = onAction ) + 2 -> MemoryPage(state = uiState.memoryState) - 2 -> PermissionPage( + 3 -> PermissionPage( state = uiState.permissionState, onAction = onAction ) + 4 -> BatteryPage( + state = uiState.batteryState + ) else -> Unit } 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 index 02ab75aa0..23109e732 100644 --- 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 @@ -6,11 +6,13 @@ 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.domain.device.usecase.GetCurrentDeviceIdAndPackageNameUseCase +import io.github.openflocon.flocondesktop.device.models.BatteryUiState import io.github.openflocon.flocondesktop.device.models.ContentUiState import io.github.openflocon.flocondesktop.device.models.CpuItem import io.github.openflocon.flocondesktop.device.models.CpuUiState import io.github.openflocon.flocondesktop.device.models.DeviceUiState import io.github.openflocon.flocondesktop.device.models.InfoUiState +import io.github.openflocon.flocondesktop.device.models.MemoryItem import io.github.openflocon.flocondesktop.device.models.MemoryUiState import io.github.openflocon.flocondesktop.device.models.PermissionItem import io.github.openflocon.flocondesktop.device.models.PermissionUiState @@ -40,6 +42,28 @@ internal class DeviceViewModel( battery = "" ) ) + private val batteryState = MutableStateFlow( + BatteryUiState( + acPowered = false, + usbPowered = false, + wirelessPowered = false, + dockPowered = false, + maxChargingCurrent = 0, + maxChargingVoltage = 0, + chargeCounter = 0, + status = 0, + health = 0, + present = false, + level = 0, + scale = 0, + voltage = 0, + temperature = 0, + technology = "", + chargingState = 0, + chargingPolicy = 0, + capacityLevel = 0 + ) + ) private val memoryState = MutableStateFlow(MemoryUiState(emptyList())) private val cpuState = MutableStateFlow(CpuUiState(emptyList())) private val permissionState = MutableStateFlow(PermissionUiState(emptyList())) @@ -49,14 +73,16 @@ internal class DeviceViewModel( infoState, memoryState, cpuState, - permissionState - ) { content, info, memory, cpu, permission -> + permissionState, + batteryState + ) { states -> DeviceUiState( - contentState = content, - infoState = info, - memoryState = memory, - cpuState = cpu, - permissionState = permission + contentState = states[0] as ContentUiState, + infoState = states[1] as InfoUiState, + memoryState = states[2] as MemoryUiState, + cpuState = states[3] as CpuUiState, + permissionState = states[4] as PermissionUiState, + batteryState = states[5] as BatteryUiState ) } .stateIn( @@ -67,7 +93,8 @@ internal class DeviceViewModel( infoState = infoState.value, memoryState = memoryState.value, cpuState = cpuState.value, - permissionState = permissionState.value + permissionState = permissionState.value, + batteryState = batteryState.value ) ) @@ -106,10 +133,12 @@ internal class DeviceViewModel( private fun onRefresh() { refreshCpu() + refreshMemory() + refreshBattery() deviceInfo() fetchPermission() viewModelScope.launch { - // battery = sendCommand("shell", "dumpsys", "battery"), + // battery = sendCommand("shell", "dumpsys", "battery"), // mem = sendCommand("shell", "dumpsys", "meminfo") } } @@ -203,9 +232,107 @@ internal class DeviceViewModel( } } + private fun refreshMemory() { + viewModelScope.launch(Dispatchers.IO) { + 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() + + memoryState.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)) + } + } + + private fun refreshBattery() { + viewModelScope.launch { + val batteryInfo = sendCommand("shell", "dumpsys", "battery") + + batteryState.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 PERMISSION_PREFIX = "android.permission." - 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""" + + // 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""" + + // MEM + private const val MEM_REGEX = """([\d,]+)K:\s+([a-zA-Z0-9._:-]+)\s+$${"pid"}\s+(\d+)(?:\s+/\s+([a-zA-Z\s]+))?$""" + + // Battery + 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/models/BatteryUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/BatteryUiState.kt new file mode 100644 index 000000000..1a0076642 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/BatteryUiState.kt @@ -0,0 +1,46 @@ +package io.github.openflocon.flocondesktop.device.models + +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/models/DeviceUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/DeviceUiState.kt index 3893c07d7..d1fa53445 100644 --- 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 @@ -8,7 +8,8 @@ data class DeviceUiState( val infoState: InfoUiState, val cpuState: CpuUiState, val memoryState: MemoryUiState, - val permissionState: PermissionUiState + val permissionState: PermissionUiState, + val batteryState: BatteryUiState ) internal fun previewDeviceUiState() = DeviceUiState( @@ -16,5 +17,6 @@ internal fun previewDeviceUiState() = DeviceUiState( cpuState = previewCpuUiState(), memoryState = previewMemoryUiState(), infoState = previewInfoUiState(), - permissionState = previewPermissionUiState() + permissionState = previewPermissionUiState(), + batteryState = previewBatteryUiState() ) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/MemoryUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/MemoryUiState.kt index 755ab2a20..8678bc166 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/MemoryUiState.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/MemoryUiState.kt @@ -9,7 +9,9 @@ data class MemoryUiState( @Immutable data class MemoryItem( - val name: String + val memoryUsage: String, + val processName: String, + val pid: Int ) fun previewMemoryUiState() = MemoryUiState( diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/BatteryPage.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/BatteryPage.kt new file mode 100644 index 000000000..928baec33 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/BatteryPage.kt @@ -0,0 +1,93 @@ +package io.github.openflocon.flocondesktop.device.pages + +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.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import io.github.openflocon.flocondesktop.device.models.BatteryUiState +import io.github.openflocon.library.designsystem.FloconTheme +import io.github.openflocon.library.designsystem.components.FloconTextValue + +@Composable +internal fun BatteryPage( + state: BatteryUiState +) { + 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 = state.technology.orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary) + FloconTextValue(label = "Health", value = state.health?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary) + FloconTextValue( + label = "Capacity", + value = state.capacityLevel?.toString().orEmpty(), + valueContainerColor = FloconTheme.colorPalette.secondary + ) + FloconTextValue(label = "AC Powered", value = state.acPowered?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary) + FloconTextValue( + label = "Charge counter", + value = state.chargeCounter?.toString().orEmpty(), + valueContainerColor = FloconTheme.colorPalette.secondary + ) + FloconTextValue( + label = "Charging policy", + value = state.chargingPolicy?.toString().orEmpty(), + valueContainerColor = FloconTheme.colorPalette.secondary + ) + FloconTextValue( + label = "Charging state", + value = state.chargingState?.toString().orEmpty(), + valueContainerColor = FloconTheme.colorPalette.secondary + ) + FloconTextValue( + label = "Max Charging current", + value = state.maxChargingCurrent?.toString().orEmpty(), + valueContainerColor = FloconTheme.colorPalette.secondary + ) + FloconTextValue( + label = "Max Charging voltage", + value = state.maxChargingVoltage?.toString().orEmpty(), + valueContainerColor = FloconTheme.colorPalette.secondary + ) + FloconTextValue( + label = "Dock powered", + value = state.dockPowered?.toString().orEmpty(), + valueContainerColor = FloconTheme.colorPalette.secondary + ) + FloconTextValue(label = "Level", value = state.level?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary) + FloconTextValue(label = "Voltage", value = state.voltage?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary) + FloconTextValue( + label = "Temperature", + value = state.temperature?.toString().orEmpty(), + valueContainerColor = FloconTheme.colorPalette.secondary + ) + FloconTextValue( + label = "Wireless powered", + value = state.wirelessPowered?.toString().orEmpty(), + valueContainerColor = FloconTheme.colorPalette.secondary + ) + FloconTextValue(label = "Scale", value = state.scale?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary) + FloconTextValue( + label = "USB Powered", + value = state.usbPowered?.toString().orEmpty(), + valueContainerColor = FloconTheme.colorPalette.secondary + ) + FloconTextValue(label = "Present", value = state.present?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary) + } +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/MemoryPage.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/MemoryPage.kt new file mode 100644 index 000000000..913b68c82 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/MemoryPage.kt @@ -0,0 +1,100 @@ +package io.github.openflocon.flocondesktop.device.pages + +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.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 io.github.openflocon.flocondesktop.device.models.MemoryUiState +import io.github.openflocon.library.designsystem.FloconTheme +import io.github.openflocon.library.designsystem.components.FloconHorizontalDivider + +@Composable +internal fun MemoryPage( + state: MemoryUiState +) { + 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 = state.list, + key = { _, item -> item.pid } + ) { index, item -> + BasicItem( + memoryUsage = item.memoryUsage, + pId = item.pid.toString(), + processName = item.processName, + style = FloconTheme.typography.labelSmall + ) + if (index != state.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) + ) + } +} From c9066df24931000e0521d765dc294d2472618994 Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Wed, 1 Oct 2025 10:27:06 +0200 Subject: [PATCH 15/24] feat: Rework tabs --- .../flocondesktop/device/DeviceAction.kt | 2 +- .../flocondesktop/device/DeviceScreen.kt | 74 +++++-------------- .../flocondesktop/device/DeviceTab.kt | 18 +++++ .../flocondesktop/device/DeviceViewModel.kt | 4 +- .../device/models/ContentUiState.kt | 5 +- 5 files changed, 44 insertions(+), 59 deletions(-) create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceTab.kt 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 index 0d5454566..f951068d9 100644 --- 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 @@ -2,7 +2,7 @@ package io.github.openflocon.flocondesktop.device internal sealed interface DeviceAction { - data class SelectTab(val index: Int) : DeviceAction + data class SelectTab(val selected: DeviceTab) : DeviceAction data object Refresh : 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 index 19ca3b844..3dd84091a 100644 --- 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 @@ -1,6 +1,5 @@ package io.github.openflocon.flocondesktop.device -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row @@ -16,6 +15,7 @@ import androidx.compose.material3.Text 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.unit.dp @@ -23,7 +23,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.openflocon.domain.device.models.DeviceId import io.github.openflocon.flocondesktop.common.ui.window.FloconWindow import io.github.openflocon.flocondesktop.common.ui.window.createFloconWindowState -import io.github.openflocon.flocondesktop.device.models.BatteryUiState import io.github.openflocon.flocondesktop.device.models.DeviceUiState import io.github.openflocon.flocondesktop.device.models.previewDeviceUiState import io.github.openflocon.flocondesktop.device.pages.BatteryPage @@ -38,7 +37,6 @@ 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 io.github.openflocon.library.designsystem.components.FloconTextValue import org.jetbrains.compose.ui.tooling.preview.Preview import org.koin.compose.viewmodel.koinViewModel import org.koin.core.parameter.parametersOf @@ -66,10 +64,11 @@ private fun Content( onCloseRequest: () -> Unit, onAction: (DeviceAction) -> Unit ) { - val pagerState = rememberPagerState { 5 } + val tabs = remember { DeviceTab.entries } + val pagerState = rememberPagerState { tabs.size } - LaunchedEffect(uiState.contentState.selectedIndex) { - pagerState.animateScrollToPage(uiState.contentState.selectedIndex) + LaunchedEffect(uiState.contentState.selectedTab) { + pagerState.animateScrollToPage(uiState.contentState.selectedTab.ordinal) } FloconWindow( @@ -91,39 +90,17 @@ private fun Content( onAction = onAction ) FloconScrollableTabRow( - selectedTabIndex = uiState.contentState.selectedIndex, + selectedTabIndex = uiState.contentState.selectedTab.ordinal, modifier = Modifier.fillMaxWidth() ) { - FloconTab( - text = "Info", - selected = uiState.contentState.selectedIndex == 0, - onClick = { onAction(DeviceAction.SelectTab(0)) }, - selectedContentColor = FloconTheme.colorPalette.onSurface - ) - FloconTab( - text = "Cpu", - selected = uiState.contentState.selectedIndex == 1, - onClick = { onAction(DeviceAction.SelectTab(1)) }, - selectedContentColor = FloconTheme.colorPalette.onSurface - ) - FloconTab( - text = "Memory", - selected = uiState.contentState.selectedIndex == 2, - onClick = { onAction(DeviceAction.SelectTab(2)) }, - selectedContentColor = FloconTheme.colorPalette.onSurface - ) - FloconTab( - text = "Permission", - selected = uiState.contentState.selectedIndex == 3, - onClick = { onAction(DeviceAction.SelectTab(3)) }, - selectedContentColor = FloconTheme.colorPalette.onSurface - ) - FloconTab( - text = "Battery", - selected = uiState.contentState.selectedIndex == 4, - onClick = { onAction(DeviceAction.SelectTab(4)) }, - selectedContentColor = FloconTheme.colorPalette.onSurface - ) + 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(), @@ -141,23 +118,12 @@ private fun Content( .fillMaxSize() .padding(it) ) { index -> - when (index) { - 0 -> InfoPage(uiState.infoState) - 1 -> CpuPage( - state = uiState.cpuState, - onAction = onAction - ) - 2 -> MemoryPage(state = uiState.memoryState) - - 3 -> PermissionPage( - state = uiState.permissionState, - onAction = onAction - ) - 4 -> BatteryPage( - state = uiState.batteryState - ) - - else -> Unit + when (tabs[index]) { + DeviceTab.INFORMATION -> InfoPage(uiState.infoState) + DeviceTab.BATTERY -> BatteryPage(uiState.batteryState) + DeviceTab.CPU -> CpuPage(uiState.cpuState, onAction) + DeviceTab.MEMORY -> MemoryPage(uiState.memoryState) + DeviceTab.PERMISSION -> PermissionPage(uiState.permissionState, 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..2e721e1d4 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DeviceTab.kt @@ -0,0 +1,18 @@ +package io.github.openflocon.flocondesktop.device + +enum class DeviceTab { + INFORMATION, + BATTERY, + CPU, + MEMORY, + PERMISSION +} + +val DeviceTab.title: String + get() = when (this) { + DeviceTab.INFORMATION -> "Info" + DeviceTab.BATTERY -> "Battery" + DeviceTab.CPU -> "CPU" + DeviceTab.MEMORY -> "Memory" + DeviceTab.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 index 23109e732..0ea79d68c 100644 --- 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 @@ -31,7 +31,7 @@ internal class DeviceViewModel( val currentDeviceAppsUseCase: GetCurrentDeviceIdAndPackageNameUseCase ) : ViewModel() { - private val contentState = MutableStateFlow(ContentUiState(selectedIndex = 0)) + private val contentState = MutableStateFlow(ContentUiState(selectedTab = DeviceTab.entries.first())) private val infoState = MutableStateFlow( InfoUiState( model = "", @@ -128,7 +128,7 @@ internal class DeviceViewModel( } private fun onSelect(action: DeviceAction.SelectTab) { - contentState.update { it.copy(selectedIndex = action.index) } + contentState.update { it.copy(selectedTab = action.selected) } } private fun onRefresh() { 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 index ef09a8d3e..32b26ab94 100644 --- 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 @@ -1,12 +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 selectedIndex: Int + val selectedTab: DeviceTab ) fun previewContentUiState() = ContentUiState( - selectedIndex = 0 + selectedTab = DeviceTab.INFORMATION ) From b29f48b6388c8d50796485b0e9796cc1cb32c8c1 Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Mon, 10 Nov 2025 11:52:46 +0100 Subject: [PATCH 16/24] fix: Build --- .../flocondesktop/app/ui/view/topbar/device/TopBarDeviceView.kt | 2 ++ 1 file changed, 2 insertions(+) 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 e2b48e5ed..526ef2ef6 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 @@ -19,6 +19,7 @@ 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.MobileOff import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -42,6 +43,7 @@ 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 org.jetbrains.compose.resources.stringResource From bf610b2a9bb420e3a49d01c73e3d124e2a007304 Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Wed, 12 Nov 2025 12:52:33 +0100 Subject: [PATCH 17/24] feature: Use navigation --- .../openflocon/flocondesktop/AppWindow.kt | 2 + .../openflocon/flocondesktop/app/AppAction.kt | 3 + .../openflocon/flocondesktop/app/AppScreen.kt | 5 +- .../flocondesktop/app/AppViewModel.kt | 6 ++ .../app/ui/view/topbar/MainScreenTopBar.kt | 2 + .../ui/view/topbar/TopBarDeviceAndAppView.kt | 2 + .../topbar/device/TopBarDeviceDropdown.kt | 2 + .../ui/view/topbar/device/TopBarDeviceView.kt | 19 +--- .../flocondesktop/device/DeviceScreen.kt | 97 ++++++++----------- .../flocondesktop/device/Navigation.kt | 24 +++++ .../navigation/scene/WindowScene.kt | 13 ++- 11 files changed, 99 insertions(+), 76 deletions(-) create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/Navigation.kt diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/AppWindow.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/AppWindow.kt index 6a54d6023..17e190ccf 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/AppWindow.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/AppWindow.kt @@ -18,6 +18,7 @@ import io.github.openflocon.flocondesktop.app.AppScreen import io.github.openflocon.flocondesktop.app.di.appModule import io.github.openflocon.flocondesktop.common.di.commonModule import io.github.openflocon.flocondesktop.core.di.coreModule +import io.github.openflocon.flocondesktop.device.deviceModule import io.github.openflocon.flocondesktop.features.featuresModule import io.github.openflocon.flocondesktop.features.network.NetworkRoutes import io.github.openflocon.library.designsystem.FloconTheme @@ -43,6 +44,7 @@ fun App() { dataCoreModule, dataLocalModule, dataRemoteModule, + deviceModule, // Temporary module { // scope { 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/view/topbar/MainScreenTopBar.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/MainScreenTopBar.kt index 4a5d43c46..cf5f5f661 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 @@ -41,6 +41,7 @@ fun MainScreenTopBar( recordState: RecordVideoStateUiModel, onRecordClicked: () -> Unit, onRestartClicked: () -> Unit, + onClickDetail: (DeviceItemUiModel) -> Unit, ) { Row( modifier = modifier @@ -57,6 +58,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..d6c48f783 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 @@ -26,6 +26,7 @@ internal fun TopBarDeviceAndAppView( deleteDevice: (DeviceItemUiModel) -> Unit, onAppSelected: (DeviceAppUiModel) -> Unit, deleteApp: (DeviceAppUiModel) -> Unit, + onClickDetail: (DeviceItemUiModel) -> Unit, modifier: Modifier = Modifier, ) { Row( @@ -37,6 +38,7 @@ internal fun TopBarDeviceAndAppView( state = devicesState, onDeviceSelected = onDeviceSelected, deleteDevice = deleteDevice, + onClickDetail = onClickDetail ) 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..ba0c6ae84 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 @@ -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) } @@ -72,6 +73,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 526ef2ef6..7037bf5f8 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 @@ -23,11 +23,6 @@ import androidx.compose.material.icons.outlined.Details import androidx.compose.material.icons.outlined.MobileOff import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.key -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -52,11 +47,10 @@ internal fun TopBarDeviceView( device: DeviceItemUiModel, modifier: Modifier = Modifier, onClick: (() -> Unit)? = null, + onClickDetail: (() -> Unit)? = null, selected: Boolean = false, onDelete: (() -> Unit)? = null, ) { - var openDetail by remember { mutableStateOf(false) } - Row( modifier = modifier .then( @@ -134,7 +128,7 @@ internal fun TopBarDeviceView( ) } FloconIconButton( - onClick = { openDetail = true } + onClick = { onClickDetail?.invoke() } ) { FloconIcon( imageVector = Icons.Outlined.Details @@ -159,15 +153,6 @@ internal fun TopBarDeviceView( } } } - - if (openDetail) { - key(device.id) { - DeviceScreen( - deviceId = device.id, - onCloseRequest = { openDetail = false } - ) - } - } } @Preview 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 index 3dd84091a..cb3687dc5 100644 --- 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 @@ -21,8 +21,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.openflocon.domain.device.models.DeviceId -import io.github.openflocon.flocondesktop.common.ui.window.FloconWindow -import io.github.openflocon.flocondesktop.common.ui.window.createFloconWindowState import io.github.openflocon.flocondesktop.device.models.DeviceUiState import io.github.openflocon.flocondesktop.device.models.previewDeviceUiState import io.github.openflocon.flocondesktop.device.pages.BatteryPage @@ -43,8 +41,7 @@ import org.koin.core.parameter.parametersOf @Composable internal fun DeviceScreen( - deviceId: DeviceId, - onCloseRequest: () -> Unit + deviceId: DeviceId ) { val viewModel = koinViewModel { parametersOf(deviceId) @@ -53,7 +50,6 @@ internal fun DeviceScreen( Content( uiState = uiState, - onCloseRequest = onCloseRequest, onAction = viewModel::onAction ) } @@ -61,7 +57,6 @@ internal fun DeviceScreen( @Composable private fun Content( uiState: DeviceUiState, - onCloseRequest: () -> Unit, onAction: (DeviceAction) -> Unit ) { val tabs = remember { DeviceTab.entries } @@ -71,60 +66,53 @@ private fun Content( pagerState.animateScrollToPage(uiState.contentState.selectedTab.ordinal) } - FloconWindow( - title = "Device - ${uiState.infoState.model}", - onCloseRequest = onCloseRequest, - state = createFloconWindowState() + FloconSurface( + modifier = Modifier.fillMaxSize() ) { -// window.minimumSize = Dimension(500, 500) // TODO - FloconSurface( - modifier = Modifier.fillMaxSize() - ) { - FloconScaffold( - topBar = { - Column( + FloconScaffold( + topBar = { + Column( + modifier = Modifier.fillMaxWidth() + ) { + Header( + uiState = uiState, + onAction = onAction + ) + FloconScrollableTabRow( + selectedTabIndex = uiState.contentState.selectedTab.ordinal, modifier = Modifier.fillMaxWidth() ) { - Header( - uiState = uiState, - onAction = onAction - ) - 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 - ) - } + 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 - ) } + FloconHorizontalDivider( + modifier = Modifier.fillMaxWidth(), + color = FloconTheme.colorPalette.primary + ) } - ) { - 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.infoState) - DeviceTab.BATTERY -> BatteryPage(uiState.batteryState) - DeviceTab.CPU -> CpuPage(uiState.cpuState, onAction) - DeviceTab.MEMORY -> MemoryPage(uiState.memoryState) - DeviceTab.PERMISSION -> PermissionPage(uiState.permissionState, onAction) - } + } + ) { + 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.infoState) + DeviceTab.BATTERY -> BatteryPage(uiState.batteryState) + DeviceTab.CPU -> CpuPage(uiState.cpuState, onAction) + DeviceTab.MEMORY -> MemoryPage(uiState.memoryState) + DeviceTab.PERMISSION -> PermissionPage(uiState.permissionState, onAction) } } } @@ -169,7 +157,6 @@ private fun Preview() { FloconTheme { Content( uiState = previewDeviceUiState(), - onCloseRequest = {}, onAction = {} ) } 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/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 +) From 619786d61ecfa1b53e0a43ab3cfe2495c075497e Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Wed, 12 Nov 2025 13:07:08 +0100 Subject: [PATCH 18/24] feature: Permission --- .../openflocon/flocondesktop/device/DI.kt | 2 + .../flocondesktop/device/DeviceAction.kt | 2 - .../flocondesktop/device/DeviceScreen.kt | 4 +- .../flocondesktop/device/DeviceViewModel.kt | 72 ++-------------- .../flocondesktop/device/PageViewModel.kt | 19 +++++ .../device/models/DeviceUiState.kt | 4 +- .../pages/permission/PermissionAction.kt | 8 ++ .../pages/{ => permission}/PermissionPage.kt | 31 +++++-- .../permission}/PermissionUiState.kt | 4 +- .../pages/permission/PermissionViewModel.kt | 83 +++++++++++++++++++ 10 files changed, 150 insertions(+), 79 deletions(-) create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/PageViewModel.kt create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionAction.kt rename FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/{ => permission}/PermissionPage.kt (76%) rename FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/{models => pages/permission}/PermissionUiState.kt (76%) create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionViewModel.kt 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 index 54403cb1e..c4a47f95f 100644 --- 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 @@ -1,8 +1,10 @@ package io.github.openflocon.flocondesktop.device +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) } 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 index f951068d9..2f8538313 100644 --- 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 @@ -6,6 +6,4 @@ internal sealed interface DeviceAction { data object Refresh : DeviceAction - data class ChangePermission(val permission: String, val granted: Boolean) : 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 index cb3687dc5..14c6bbe7c 100644 --- 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 @@ -27,7 +27,7 @@ import io.github.openflocon.flocondesktop.device.pages.BatteryPage import io.github.openflocon.flocondesktop.device.pages.CpuPage import io.github.openflocon.flocondesktop.device.pages.InfoPage import io.github.openflocon.flocondesktop.device.pages.MemoryPage -import io.github.openflocon.flocondesktop.device.pages.PermissionPage +import io.github.openflocon.flocondesktop.device.pages.permission.PermissionPage import io.github.openflocon.library.designsystem.FloconTheme import io.github.openflocon.library.designsystem.components.FloconHorizontalDivider import io.github.openflocon.library.designsystem.components.FloconIconButton @@ -112,7 +112,7 @@ private fun Content( DeviceTab.BATTERY -> BatteryPage(uiState.batteryState) DeviceTab.CPU -> CpuPage(uiState.cpuState, onAction) DeviceTab.MEMORY -> MemoryPage(uiState.memoryState) - DeviceTab.PERMISSION -> PermissionPage(uiState.permissionState, onAction) + DeviceTab.PERMISSION -> PermissionPage(uiState.deviceSerial) } } } 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 index 0ea79d68c..138f9fa59 100644 --- 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 @@ -14,8 +14,6 @@ import io.github.openflocon.flocondesktop.device.models.DeviceUiState import io.github.openflocon.flocondesktop.device.models.InfoUiState import io.github.openflocon.flocondesktop.device.models.MemoryItem import io.github.openflocon.flocondesktop.device.models.MemoryUiState -import io.github.openflocon.flocondesktop.device.models.PermissionItem -import io.github.openflocon.flocondesktop.device.models.PermissionUiState import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -66,23 +64,23 @@ internal class DeviceViewModel( ) private val memoryState = MutableStateFlow(MemoryUiState(emptyList())) private val cpuState = MutableStateFlow(CpuUiState(emptyList())) - private val permissionState = MutableStateFlow(PermissionUiState(emptyList())) + private val deviceSerial = MutableStateFlow("") val uiState = combine( contentState, infoState, memoryState, cpuState, - permissionState, - batteryState + batteryState, + deviceSerial ) { states -> DeviceUiState( contentState = states[0] as ContentUiState, infoState = states[1] as InfoUiState, memoryState = states[2] as MemoryUiState, cpuState = states[3] as CpuUiState, - permissionState = states[4] as PermissionUiState, - batteryState = states[5] as BatteryUiState + batteryState = states[4] as BatteryUiState, + deviceSerial = states[5] as String ) } .stateIn( @@ -93,16 +91,14 @@ internal class DeviceViewModel( infoState = infoState.value, memoryState = memoryState.value, cpuState = cpuState.value, - permissionState = permissionState.value, - batteryState = batteryState.value + batteryState = batteryState.value, + deviceSerial = deviceSerial.value ) ) - private var deviceSerial: String = "" - init { viewModelScope.launch(Dispatchers.IO) { - deviceSerial = deviceSerialUseCase(deviceId) + deviceSerial.value = deviceSerialUseCase(deviceId) onRefresh() } @@ -112,18 +108,6 @@ internal class DeviceViewModel( when (action) { is DeviceAction.SelectTab -> onSelect(action) DeviceAction.Refresh -> onRefresh() - is DeviceAction.ChangePermission -> onChangePermission(action) - } - } - - private fun onChangePermission(action: DeviceAction.ChangePermission) { - viewModelScope.launch { - if (action.granted) { - revokePermission(action.permission) - } else { - grantPermission(action.permission) - } - fetchPermission() } } @@ -136,7 +120,6 @@ internal class DeviceViewModel( refreshMemory() refreshBattery() deviceInfo() - fetchPermission() viewModelScope.launch { // battery = sendCommand("shell", "dumpsys", "battery"), // mem = sendCommand("shell", "dumpsys", "meminfo") @@ -157,44 +140,8 @@ internal class DeviceViewModel( } } - 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) - - permissionState.update { it.copy(list = permissions) } - } - } - - 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 suspend fun sendCommand(vararg args: String): String { - return sendCommandUseCase(deviceSerial, *args) + return sendCommandUseCase(deviceSerial.value, *args) .getOrNull() .orEmpty() .removeSuffix("\n") @@ -305,7 +252,6 @@ internal class DeviceViewModel( } companion object { - private const val PERMISSION_PREFIX = "android.permission." // CPU private const val CPU_REGEX = 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/DeviceUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/DeviceUiState.kt index d1fa53445..b7b02e487 100644 --- 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 @@ -4,19 +4,19 @@ import androidx.compose.runtime.Immutable @Immutable data class DeviceUiState( + val deviceSerial: String, val contentState: ContentUiState, val infoState: InfoUiState, val cpuState: CpuUiState, val memoryState: MemoryUiState, - val permissionState: PermissionUiState, val batteryState: BatteryUiState ) internal fun previewDeviceUiState() = DeviceUiState( + deviceSerial = "", contentState = previewContentUiState(), cpuState = previewCpuUiState(), memoryState = previewMemoryUiState(), infoState = previewInfoUiState(), - permissionState = previewPermissionUiState(), batteryState = previewBatteryUiState() ) 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/PermissionPage.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionPage.kt similarity index 76% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/PermissionPage.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionPage.kt index facb77b12..3d21689d9 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/PermissionPage.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionPage.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.device.pages +package io.github.openflocon.flocondesktop.device.pages.permission import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -12,20 +12,35 @@ 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 io.github.openflocon.flocondesktop.device.DeviceAction -import io.github.openflocon.flocondesktop.device.models.PermissionUiState +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( - state: PermissionUiState, - onAction: (DeviceAction) -> Unit + 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 @@ -39,7 +54,7 @@ internal fun PermissionPage( ) ) { itemsIndexed( - items = state.list, + items = uiState.list, key = { _, item -> item.name } ) { index, item -> Row( @@ -50,7 +65,7 @@ internal fun PermissionPage( .clip(FloconTheme.shapes.medium) .clickable(onClick = { onAction( - DeviceAction.ChangePermission( + PermissionAction.ChangePermission( permission = item.name, granted = item.granted ) @@ -69,7 +84,7 @@ internal fun PermissionPage( uncheckedColor = FloconTheme.colorPalette.secondary ) } - if (index != state.list.lastIndex) { + if (index != uiState.list.lastIndex) { FloconHorizontalDivider( color = FloconTheme.colorPalette.secondary ) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/PermissionUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionUiState.kt similarity index 76% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/PermissionUiState.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionUiState.kt index b11964944..a66d5024b 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/PermissionUiState.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/permission/PermissionUiState.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.device.models +package io.github.openflocon.flocondesktop.device.pages.permission import androidx.compose.runtime.Immutable @@ -14,5 +14,5 @@ data class PermissionItem( ) fun previewPermissionUiState() = PermissionUiState( - list = emptyList() + 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." + } + +} From 7feabc996061261b92aa546818da23491deafdba Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Wed, 12 Nov 2025 13:16:16 +0100 Subject: [PATCH 19/24] feature: CPU --- .../openflocon/flocondesktop/device/DI.kt | 2 + .../flocondesktop/device/DeviceScreen.kt | 4 +- .../flocondesktop/device/DeviceViewModel.kt | 51 +------------- .../device/models/DeviceUiState.kt | 2 - .../device/pages/cpu/CpuAction.kt | 3 + .../device/pages/{ => cpu}/CpuPage.kt | 29 ++++++-- .../{models => pages/cpu}/CpuUiState.kt | 2 +- .../device/pages/cpu/CpuViewModel.kt | 66 +++++++++++++++++++ 8 files changed, 99 insertions(+), 60 deletions(-) create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuAction.kt rename FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/{ => cpu}/CpuPage.kt (86%) rename FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/{models => pages/cpu}/CpuUiState.kt (86%) create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuViewModel.kt 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 index c4a47f95f..e1a3715f9 100644 --- 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 @@ -1,5 +1,6 @@ package io.github.openflocon.flocondesktop.device +import io.github.openflocon.flocondesktop.device.pages.cpu.CpuViewModel import io.github.openflocon.flocondesktop.device.pages.permission.PermissionViewModel import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module @@ -7,4 +8,5 @@ import org.koin.dsl.module internal val deviceModule = module { viewModelOf(::DeviceViewModel) viewModelOf(::PermissionViewModel) + viewModelOf(::CpuViewModel) } 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 index 14c6bbe7c..f048bf32b 100644 --- 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 @@ -24,7 +24,7 @@ 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.BatteryPage -import io.github.openflocon.flocondesktop.device.pages.CpuPage +import io.github.openflocon.flocondesktop.device.pages.cpu.CpuPage import io.github.openflocon.flocondesktop.device.pages.InfoPage import io.github.openflocon.flocondesktop.device.pages.MemoryPage import io.github.openflocon.flocondesktop.device.pages.permission.PermissionPage @@ -110,7 +110,7 @@ private fun Content( when (tabs[index]) { DeviceTab.INFORMATION -> InfoPage(uiState.infoState) DeviceTab.BATTERY -> BatteryPage(uiState.batteryState) - DeviceTab.CPU -> CpuPage(uiState.cpuState, onAction) + DeviceTab.CPU -> CpuPage(uiState.deviceSerial) DeviceTab.MEMORY -> MemoryPage(uiState.memoryState) DeviceTab.PERMISSION -> PermissionPage(uiState.deviceSerial) } 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 index 138f9fa59..49d0d19ac 100644 --- 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 @@ -5,11 +5,8 @@ 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.domain.device.usecase.GetCurrentDeviceIdAndPackageNameUseCase import io.github.openflocon.flocondesktop.device.models.BatteryUiState import io.github.openflocon.flocondesktop.device.models.ContentUiState -import io.github.openflocon.flocondesktop.device.models.CpuItem -import io.github.openflocon.flocondesktop.device.models.CpuUiState import io.github.openflocon.flocondesktop.device.models.DeviceUiState import io.github.openflocon.flocondesktop.device.models.InfoUiState import io.github.openflocon.flocondesktop.device.models.MemoryItem @@ -25,8 +22,7 @@ import kotlinx.coroutines.launch internal class DeviceViewModel( val deviceId: String, val sendCommandUseCase: SendCommandUseCase, - val deviceSerialUseCase: GetDeviceSerialUseCase, - val currentDeviceAppsUseCase: GetCurrentDeviceIdAndPackageNameUseCase + val deviceSerialUseCase: GetDeviceSerialUseCase ) : ViewModel() { private val contentState = MutableStateFlow(ContentUiState(selectedTab = DeviceTab.entries.first())) @@ -63,14 +59,12 @@ internal class DeviceViewModel( ) ) private val memoryState = MutableStateFlow(MemoryUiState(emptyList())) - private val cpuState = MutableStateFlow(CpuUiState(emptyList())) private val deviceSerial = MutableStateFlow("") val uiState = combine( contentState, infoState, memoryState, - cpuState, batteryState, deviceSerial ) { states -> @@ -78,9 +72,8 @@ internal class DeviceViewModel( contentState = states[0] as ContentUiState, infoState = states[1] as InfoUiState, memoryState = states[2] as MemoryUiState, - cpuState = states[3] as CpuUiState, - batteryState = states[4] as BatteryUiState, - deviceSerial = states[5] as String + batteryState = states[3] as BatteryUiState, + deviceSerial = states[4] as String ) } .stateIn( @@ -90,7 +83,6 @@ internal class DeviceViewModel( contentState = contentState.value, infoState = infoState.value, memoryState = memoryState.value, - cpuState = cpuState.value, batteryState = batteryState.value, deviceSerial = deviceSerial.value ) @@ -116,7 +108,6 @@ internal class DeviceViewModel( } private fun onRefresh() { - refreshCpu() refreshMemory() refreshBattery() deviceInfo() @@ -147,38 +138,6 @@ internal class DeviceViewModel( .removeSuffix("\n") } - 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() - - cpuState.update { it.copy(list = items) } - } - } - private fun refreshMemory() { viewModelScope.launch(Dispatchers.IO) { val output = sendCommand("shell", "dumpsys", "meminfo") @@ -253,10 +212,6 @@ internal class DeviceViewModel( 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""" - // MEM 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/models/DeviceUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/DeviceUiState.kt index b7b02e487..eac495389 100644 --- 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 @@ -7,7 +7,6 @@ data class DeviceUiState( val deviceSerial: String, val contentState: ContentUiState, val infoState: InfoUiState, - val cpuState: CpuUiState, val memoryState: MemoryUiState, val batteryState: BatteryUiState ) @@ -15,7 +14,6 @@ data class DeviceUiState( internal fun previewDeviceUiState() = DeviceUiState( deviceSerial = "", contentState = previewContentUiState(), - cpuState = previewCpuUiState(), memoryState = previewMemoryUiState(), infoState = previewInfoUiState(), batteryState = previewBatteryUiState() 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/CpuPage.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuPage.kt similarity index 86% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/CpuPage.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuPage.kt index 3112b2d0c..175baa0a8 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/CpuPage.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuPage.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.device.pages +package io.github.openflocon.flocondesktop.device.pages.cpu import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -11,6 +11,7 @@ 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 @@ -18,15 +19,29 @@ 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 io.github.openflocon.flocondesktop.device.DeviceAction -import io.github.openflocon.flocondesktop.device.models.CpuUiState +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( - state: CpuUiState, - onAction: (DeviceAction) -> Unit + 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 @@ -56,7 +71,7 @@ internal fun CpuPage( ) } itemsIndexed( - items = state.list, + items = uiState.list, key = { _, item -> item.pId } ) { index, item -> BasicItem( @@ -69,7 +84,7 @@ internal fun CpuPage( minorFaults = item.minorFaults?.toString().orEmpty(), style = FloconTheme.typography.labelSmall ) - if (index != state.list.lastIndex) { + if (index != uiState.list.lastIndex) { FloconHorizontalDivider( color = FloconTheme.colorPalette.secondary ) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/CpuUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuUiState.kt similarity index 86% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/CpuUiState.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuUiState.kt index 5a7fecad9..27ee7f18e 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/CpuUiState.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/cpu/CpuUiState.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.device.models +package io.github.openflocon.flocondesktop.device.pages.cpu import androidx.compose.runtime.Immutable 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""" + } + +} From af00101b4c48ff2084e19b5e0da0b7680b1cd129 Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Wed, 12 Nov 2025 13:24:03 +0100 Subject: [PATCH 20/24] feature: Battery --- .../openflocon/flocondesktop/device/DI.kt | 2 + .../flocondesktop/device/DeviceScreen.kt | 4 +- .../flocondesktop/device/DeviceViewModel.kt | 79 +------------- .../device/models/DeviceUiState.kt | 6 +- .../device/pages/battery/BatteryAction.kt | 3 + .../device/pages/{ => battery}/BatteryPage.kt | 57 ++++++---- .../battery}/BatteryUiState.kt | 2 +- .../device/pages/battery/BatteryViewModel.kt | 101 ++++++++++++++++++ 8 files changed, 149 insertions(+), 105 deletions(-) create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryAction.kt rename FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/{ => battery}/BatteryPage.kt (56%) rename FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/{models => pages/battery}/BatteryUiState.kt (94%) create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryViewModel.kt 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 index e1a3715f9..ccd16ff3c 100644 --- 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 @@ -1,5 +1,6 @@ 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.permission.PermissionViewModel import org.koin.core.module.dsl.viewModelOf @@ -9,4 +10,5 @@ internal val deviceModule = module { viewModelOf(::DeviceViewModel) viewModelOf(::PermissionViewModel) viewModelOf(::CpuViewModel) + viewModelOf(::BatteryViewModel) } 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 index f048bf32b..7c132ff8c 100644 --- 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 @@ -23,7 +23,7 @@ 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.BatteryPage +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.InfoPage import io.github.openflocon.flocondesktop.device.pages.MemoryPage @@ -109,7 +109,7 @@ private fun Content( ) { index -> when (tabs[index]) { DeviceTab.INFORMATION -> InfoPage(uiState.infoState) - DeviceTab.BATTERY -> BatteryPage(uiState.batteryState) + DeviceTab.BATTERY -> BatteryPage(uiState.deviceSerial) DeviceTab.CPU -> CpuPage(uiState.deviceSerial) DeviceTab.MEMORY -> MemoryPage(uiState.memoryState) DeviceTab.PERMISSION -> PermissionPage(uiState.deviceSerial) 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 index 49d0d19ac..888cbbd2c 100644 --- 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 @@ -5,7 +5,6 @@ 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.BatteryUiState import io.github.openflocon.flocondesktop.device.models.ContentUiState import io.github.openflocon.flocondesktop.device.models.DeviceUiState import io.github.openflocon.flocondesktop.device.models.InfoUiState @@ -36,28 +35,6 @@ internal class DeviceViewModel( battery = "" ) ) - private val batteryState = MutableStateFlow( - BatteryUiState( - acPowered = false, - usbPowered = false, - wirelessPowered = false, - dockPowered = false, - maxChargingCurrent = 0, - maxChargingVoltage = 0, - chargeCounter = 0, - status = 0, - health = 0, - present = false, - level = 0, - scale = 0, - voltage = 0, - temperature = 0, - technology = "", - chargingState = 0, - chargingPolicy = 0, - capacityLevel = 0 - ) - ) private val memoryState = MutableStateFlow(MemoryUiState(emptyList())) private val deviceSerial = MutableStateFlow("") @@ -65,15 +42,13 @@ internal class DeviceViewModel( contentState, infoState, memoryState, - batteryState, deviceSerial ) { states -> DeviceUiState( contentState = states[0] as ContentUiState, infoState = states[1] as InfoUiState, memoryState = states[2] as MemoryUiState, - batteryState = states[3] as BatteryUiState, - deviceSerial = states[4] as String + deviceSerial = states[3] as String ) } .stateIn( @@ -83,7 +58,6 @@ internal class DeviceViewModel( contentState = contentState.value, infoState = infoState.value, memoryState = memoryState.value, - batteryState = batteryState.value, deviceSerial = deviceSerial.value ) ) @@ -109,7 +83,6 @@ internal class DeviceViewModel( private fun onRefresh() { refreshMemory() - refreshBattery() deviceInfo() viewModelScope.launch { // battery = sendCommand("shell", "dumpsys", "battery"), @@ -180,60 +153,10 @@ internal class DeviceViewModel( } } - private fun refreshBattery() { - viewModelScope.launch { - val batteryInfo = sendCommand("shell", "dumpsys", "battery") - - batteryState.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 { // MEM private const val MEM_REGEX = """([\d,]+)K:\s+([a-zA-Z0-9._:-]+)\s+$${"pid"}\s+(\d+)(?:\s+/\s+([a-zA-Z\s]+))?$""" - - // Battery - 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/models/DeviceUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/DeviceUiState.kt index eac495389..2dee0c15b 100644 --- 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 @@ -7,14 +7,12 @@ data class DeviceUiState( val deviceSerial: String, val contentState: ContentUiState, val infoState: InfoUiState, - val memoryState: MemoryUiState, - val batteryState: BatteryUiState + val memoryState: MemoryUiState ) internal fun previewDeviceUiState() = DeviceUiState( deviceSerial = "", contentState = previewContentUiState(), memoryState = previewMemoryUiState(), - infoState = previewInfoUiState(), - batteryState = previewBatteryUiState() + infoState = previewInfoUiState() ) 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/BatteryPage.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryPage.kt similarity index 56% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/BatteryPage.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryPage.kt index 928baec33..ba34f6463 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/BatteryPage.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryPage.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.device.pages +package io.github.openflocon.flocondesktop.device.pages.battery import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -8,16 +8,33 @@ 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 io.github.openflocon.flocondesktop.device.models.BatteryUiState +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( - state: BatteryUiState + 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 @@ -32,62 +49,62 @@ internal fun BatteryPage( .verticalScroll(rememberScrollState()) .padding(8.dp) ) { - FloconTextValue(label = "Technology", value = state.technology.orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary) - FloconTextValue(label = "Health", value = state.health?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary) + 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 = state.capacityLevel?.toString().orEmpty(), + value = uiState.capacityLevel?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary ) - FloconTextValue(label = "AC Powered", value = state.acPowered?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary) + FloconTextValue(label = "AC Powered", value = uiState.acPowered?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary) FloconTextValue( label = "Charge counter", - value = state.chargeCounter?.toString().orEmpty(), + value = uiState.chargeCounter?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary ) FloconTextValue( label = "Charging policy", - value = state.chargingPolicy?.toString().orEmpty(), + value = uiState.chargingPolicy?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary ) FloconTextValue( label = "Charging state", - value = state.chargingState?.toString().orEmpty(), + value = uiState.chargingState?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary ) FloconTextValue( label = "Max Charging current", - value = state.maxChargingCurrent?.toString().orEmpty(), + value = uiState.maxChargingCurrent?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary ) FloconTextValue( label = "Max Charging voltage", - value = state.maxChargingVoltage?.toString().orEmpty(), + value = uiState.maxChargingVoltage?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary ) FloconTextValue( label = "Dock powered", - value = state.dockPowered?.toString().orEmpty(), + value = uiState.dockPowered?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary ) - FloconTextValue(label = "Level", value = state.level?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary) - FloconTextValue(label = "Voltage", value = state.voltage?.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 = state.temperature?.toString().orEmpty(), + value = uiState.temperature?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary ) FloconTextValue( label = "Wireless powered", - value = state.wirelessPowered?.toString().orEmpty(), + value = uiState.wirelessPowered?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary ) - FloconTextValue(label = "Scale", value = state.scale?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary) + FloconTextValue(label = "Scale", value = uiState.scale?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary) FloconTextValue( label = "USB Powered", - value = state.usbPowered?.toString().orEmpty(), + value = uiState.usbPowered?.toString().orEmpty(), valueContainerColor = FloconTheme.colorPalette.secondary ) - FloconTextValue(label = "Present", value = state.present?.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/models/BatteryUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryUiState.kt similarity index 94% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/BatteryUiState.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryUiState.kt index 1a0076642..febb56a00 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/BatteryUiState.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/battery/BatteryUiState.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.device.models +package io.github.openflocon.flocondesktop.device.pages.battery import androidx.compose.runtime.Immutable 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+)""" + } + +} From 26829d7ce7c4aeb56d80228893891822c9cd2643 Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Wed, 12 Nov 2025 13:34:45 +0100 Subject: [PATCH 21/24] feature: Info --- .../openflocon/flocondesktop/device/DI.kt | 2 + .../flocondesktop/device/DeviceScreen.kt | 40 ++++++++--------- .../flocondesktop/device/DeviceViewModel.kt | 37 +--------------- .../device/models/DeviceUiState.kt | 4 +- .../device/pages/info/InfoAction.kt | 4 ++ .../device/pages/{ => info}/InfoPage.kt | 25 +++++++++-- .../{models => pages/info}/InfoUiState.kt | 2 +- .../device/pages/info/InfoViewModel.kt | 44 +++++++++++++++++++ 8 files changed, 95 insertions(+), 63 deletions(-) create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoAction.kt rename FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/{ => info}/InfoPage.kt (82%) rename FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/{models => pages/info}/InfoUiState.kt (86%) create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoViewModel.kt 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 index ccd16ff3c..d32de1299 100644 --- 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 @@ -2,6 +2,7 @@ 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.permission.PermissionViewModel import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module @@ -11,4 +12,5 @@ internal val deviceModule = module { viewModelOf(::PermissionViewModel) viewModelOf(::CpuViewModel) viewModelOf(::BatteryViewModel) + viewModelOf(::InfoViewModel) } 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 index 7c132ff8c..69db7033f 100644 --- 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 @@ -23,10 +23,10 @@ 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.MemoryPage 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.InfoPage -import io.github.openflocon.flocondesktop.device.pages.MemoryPage +import io.github.openflocon.flocondesktop.device.pages.info.InfoPage import io.github.openflocon.flocondesktop.device.pages.permission.PermissionPage import io.github.openflocon.library.designsystem.FloconTheme import io.github.openflocon.library.designsystem.components.FloconHorizontalDivider @@ -108,7 +108,7 @@ private fun Content( .padding(it) ) { index -> when (tabs[index]) { - DeviceTab.INFORMATION -> InfoPage(uiState.infoState) + DeviceTab.INFORMATION -> InfoPage(uiState.deviceSerial) DeviceTab.BATTERY -> BatteryPage(uiState.deviceSerial) DeviceTab.CPU -> CpuPage(uiState.deviceSerial) DeviceTab.MEMORY -> MemoryPage(uiState.memoryState) @@ -127,23 +127,23 @@ private fun Header( Row( verticalAlignment = Alignment.CenterVertically ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - .weight(1f) - ) { - Text( - text = uiState.infoState.model, - style = FloconTheme.typography.headlineSmall - ) - SelectionContainer { - Text( - text = uiState.infoState.serialNumber, - style = FloconTheme.typography.labelSmall - ) - } - } +// Column( +// modifier = Modifier +// .fillMaxWidth() +// .padding(16.dp) +// .weight(1f) +// ) { +// Text( +// text = uiState.infoState.model, +// style = FloconTheme.typography.headlineSmall +// ) +// SelectionContainer { +// Text( +// text = uiState.infoState.serialNumber, +// style = FloconTheme.typography.labelSmall +// ) +// } +// } FloconIconButton( imageVector = Icons.Outlined.Refresh, onClick = { onAction(DeviceAction.Refresh) } 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 index 888cbbd2c..b5aee0288 100644 --- 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 @@ -7,7 +7,6 @@ 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.models.InfoUiState import io.github.openflocon.flocondesktop.device.models.MemoryItem import io.github.openflocon.flocondesktop.device.models.MemoryUiState import kotlinx.coroutines.Dispatchers @@ -25,30 +24,18 @@ internal class DeviceViewModel( ) : ViewModel() { private val contentState = MutableStateFlow(ContentUiState(selectedTab = DeviceTab.entries.first())) - private val infoState = MutableStateFlow( - InfoUiState( - model = "", - brand = "", - versionRelease = "", - versionSdk = "", - serialNumber = "", - battery = "" - ) - ) private val memoryState = MutableStateFlow(MemoryUiState(emptyList())) private val deviceSerial = MutableStateFlow("") val uiState = combine( contentState, - infoState, memoryState, deviceSerial ) { states -> DeviceUiState( contentState = states[0] as ContentUiState, - infoState = states[1] as InfoUiState, - memoryState = states[2] as MemoryUiState, - deviceSerial = states[3] as String + memoryState = states[1] as MemoryUiState, + deviceSerial = states[2] as String ) } .stateIn( @@ -56,7 +43,6 @@ internal class DeviceViewModel( started = SharingStarted.WhileSubscribed(5_000), initialValue = DeviceUiState( contentState = contentState.value, - infoState = infoState.value, memoryState = memoryState.value, deviceSerial = deviceSerial.value ) @@ -83,25 +69,6 @@ internal class DeviceViewModel( private fun onRefresh() { refreshMemory() - deviceInfo() - viewModelScope.launch { - // battery = sendCommand("shell", "dumpsys", "battery"), -// mem = sendCommand("shell", "dumpsys", "meminfo") - } - } - - private fun deviceInfo() { - viewModelScope.launch { - infoState.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") - ) - } - } } private suspend fun sendCommand(vararg args: String): String { 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 index 2dee0c15b..2bfae285a 100644 --- 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 @@ -6,13 +6,11 @@ import androidx.compose.runtime.Immutable data class DeviceUiState( val deviceSerial: String, val contentState: ContentUiState, - val infoState: InfoUiState, val memoryState: MemoryUiState ) internal fun previewDeviceUiState() = DeviceUiState( deviceSerial = "", contentState = previewContentUiState(), - memoryState = previewMemoryUiState(), - infoState = previewInfoUiState() + memoryState = previewMemoryUiState() ) 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/InfoPage.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoPage.kt similarity index 82% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/InfoPage.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoPage.kt index 0c37c2655..a35ce7243 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/InfoPage.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoPage.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.device.pages +package io.github.openflocon.flocondesktop.device.pages.info import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -11,17 +11,34 @@ 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 io.github.openflocon.flocondesktop.device.models.InfoUiState +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( - state: InfoUiState + 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), @@ -29,7 +46,7 @@ internal fun InfoPage( .fillMaxSize() .verticalScroll(rememberScrollState()) ) { - General(state) + General(uiState) } } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/InfoUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoUiState.kt similarity index 86% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/InfoUiState.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoUiState.kt index 72c80782b..d6ca00dc0 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/InfoUiState.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/info/InfoUiState.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.device.models +package io.github.openflocon.flocondesktop.device.pages.info import androidx.compose.runtime.Immutable 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") + ) + } + } + } + +} From 3e7da1372b244da5d1e5fba8a11898f922424804 Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Wed, 12 Nov 2025 15:28:27 +0100 Subject: [PATCH 22/24] feature: Move memory --- FloconDesktop/composeApp/build.gradle.kts | 1 + .../app/ui/settings/SettingsScreen.kt | 1 + .../app/ui/view/leftpannel/PannelView.kt | 2 +- .../openflocon/flocondesktop/device/DI.kt | 2 + .../flocondesktop/device/DeviceAction.kt | 2 - .../flocondesktop/device/DeviceScreen.kt | 87 ++++++------------- .../flocondesktop/device/DeviceTab.kt | 22 +++-- .../flocondesktop/device/DeviceViewModel.kt | 65 +------------- .../device/models/DeviceUiState.kt | 6 +- .../device/pages/memory/MemoryAction.kt | 3 + .../device/pages/{ => memory}/MemoryPage.kt | 27 ++++-- .../{models => pages/memory}/MemoryUiState.kt | 2 +- .../device/pages/memory/MemoryViewModel.kt | 74 ++++++++++++++++ .../analytics/view/AnalyticsRowView.kt | 1 + .../analytics/view/AnalyticsScreen.kt | 1 + .../dashboard/view/DashboardContainerView.kt | 1 + .../features/dashboard/view/DashboardView.kt | 2 +- .../view/items/DashboardButtonView.kt | 2 +- .../view/items/DashboardCheckBoxView.kt | 2 +- .../view/items/DashboardLabelView.kt | 2 +- .../view/items/DashboardPlainTextView.kt | 1 + .../view/items/DashboardTextFieldView.kt | 2 +- .../dashboard/view/items/DashboardTextView.kt | 2 +- .../database/view/DatabaseQueryView.kt | 1 + .../database/view/DatabaseResultView.kt | 1 + .../deeplinks/view/DeeplinkFreeformView.kt | 1 + .../deeplinks/view/DeeplinkItemView.kt | 2 +- .../features/deeplinks/view/DeeplinkScreen.kt | 2 +- .../features/files/view/FileItemRow.kt | 1 + .../features/files/view/FilesScreen.kt | 2 +- .../features/files/view/FilesTopBar.kt | 1 + .../features/images/view/ImageItemView.kt | 1 + .../features/images/view/ImagesScreen.kt | 2 +- .../network/body/NetworkJsonScreen.kt | 1 + .../network/detail/view/NetworkDetailView.kt | 1 + .../view/components/DetailHeadersView.kt | 2 +- .../network/list/view/NetworkItemView.kt | 2 +- .../list/view/components/MethodView.kt | 30 +++---- .../list/view/components/StatusView.kt | 10 +-- .../list/view/filters/MethodFilterDropdown.kt | 2 +- .../list/view/filters/TextFilterDropdown.kt | 2 +- .../list/view/header/NetworkItemHeaderView.kt | 1 + .../network/mock/list/view/MockLineView.kt | 2 +- .../mock/list/view/NetworkMocksScreen.kt | 2 +- .../view/SharedPreferenceRow.kt | 2 +- .../view/SharedPreferencesScreen.kt | 2 +- .../features/table/view/TableRowView.kt | 1 + .../features/table/view/TableScreen.kt | 1 + FloconDesktop/gradle/libs.versions.toml | 2 +- 49 files changed, 206 insertions(+), 181 deletions(-) create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryAction.kt rename FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/{ => memory}/MemoryPage.kt (80%) rename FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/{models => pages/memory}/MemoryUiState.kt (82%) create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryViewModel.kt diff --git a/FloconDesktop/composeApp/build.gradle.kts b/FloconDesktop/composeApp/build.gradle.kts index 4004e3b6c..c7c9a39ac 100644 --- a/FloconDesktop/composeApp/build.gradle.kts +++ b/FloconDesktop/composeApp/build.gradle.kts @@ -30,6 +30,7 @@ kotlin { freeCompilerArgs.add("-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi") freeCompilerArgs.add("-Xcontext-parameters") freeCompilerArgs.add("-Xcontext-sensitive-resolution") + freeCompilerArgs.add("-Xdata-flow-based-exhaustiveness") } @OptIn(ExperimentalKotlinGradlePluginApi::class) 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 e83d37dec..6202d9e05 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 @@ -20,6 +20,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 co.touchlab.kermit.Logger 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/device/DI.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/DI.kt index d32de1299..72e61d77f 100644 --- 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 @@ -3,6 +3,7 @@ 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 @@ -13,4 +14,5 @@ internal val deviceModule = module { 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 index 2f8538313..e42ee305e 100644 --- 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 @@ -4,6 +4,4 @@ internal sealed interface DeviceAction { data class SelectTab(val selected: DeviceTab) : DeviceAction - data object Refresh : 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 index 69db7033f..f7da8ae15 100644 --- 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 @@ -1,41 +1,37 @@ 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.Row 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.foundation.text.selection.SelectionContainer -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Refresh -import androidx.compose.material3.Text 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.MemoryPage 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.FloconIconButton 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.jetbrains.compose.ui.tooling.preview.Preview import org.koin.compose.viewmodel.koinViewModel import org.koin.core.parameter.parametersOf @@ -74,10 +70,6 @@ private fun Content( Column( modifier = Modifier.fillMaxWidth() ) { - Header( - uiState = uiState, - onAction = onAction - ) FloconScrollableTabRow( selectedTabIndex = uiState.contentState.selectedTab.ordinal, modifier = Modifier.fillMaxWidth() @@ -98,59 +90,36 @@ private fun Content( } } ) { - 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.memoryState) - DeviceTab.PERMISSION -> PermissionPage(uiState.deviceSerial) + 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 -private fun Header( - uiState: DeviceUiState, - onAction: (DeviceAction) -> Unit -) { - Row( - verticalAlignment = Alignment.CenterVertically - ) { -// Column( -// modifier = Modifier -// .fillMaxWidth() -// .padding(16.dp) -// .weight(1f) -// ) { -// Text( -// text = uiState.infoState.model, -// style = FloconTheme.typography.headlineSmall -// ) -// SelectionContainer { -// Text( -// text = uiState.infoState.serialNumber, -// style = FloconTheme.typography.labelSmall -// ) -// } -// } - FloconIconButton( - imageVector = Icons.Outlined.Refresh, - onClick = { onAction(DeviceAction.Refresh) } - ) - } -} - @Composable @Preview private fun Preview() { 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 index 2e721e1d4..c79817e8f 100644 --- 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 @@ -1,18 +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 { - INFORMATION, - BATTERY, CPU, MEMORY, - PERMISSION + PERMISSION, + INFORMATION, + BATTERY } val DeviceTab.title: String get() = when (this) { - DeviceTab.INFORMATION -> "Info" - DeviceTab.BATTERY -> "Battery" - DeviceTab.CPU -> "CPU" - DeviceTab.MEMORY -> "Memory" - DeviceTab.PERMISSION -> "Permission" + 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 index b5aee0288..ea4a126b9 100644 --- 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 @@ -7,8 +7,8 @@ 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.models.MemoryItem -import io.github.openflocon.flocondesktop.device.models.MemoryUiState +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 @@ -24,18 +24,15 @@ internal class DeviceViewModel( ) : ViewModel() { private val contentState = MutableStateFlow(ContentUiState(selectedTab = DeviceTab.entries.first())) - private val memoryState = MutableStateFlow(MemoryUiState(emptyList())) private val deviceSerial = MutableStateFlow("") val uiState = combine( contentState, - memoryState, deviceSerial ) { states -> DeviceUiState( contentState = states[0] as ContentUiState, - memoryState = states[1] as MemoryUiState, - deviceSerial = states[2] as String + deviceSerial = states[1] as String ) } .stateIn( @@ -43,7 +40,6 @@ internal class DeviceViewModel( started = SharingStarted.WhileSubscribed(5_000), initialValue = DeviceUiState( contentState = contentState.value, - memoryState = memoryState.value, deviceSerial = deviceSerial.value ) ) @@ -51,15 +47,12 @@ internal class DeviceViewModel( init { viewModelScope.launch(Dispatchers.IO) { deviceSerial.value = deviceSerialUseCase(deviceId) - - onRefresh() } } fun onAction(action: DeviceAction) { when (action) { is DeviceAction.SelectTab -> onSelect(action) - DeviceAction.Refresh -> onRefresh() } } @@ -67,10 +60,6 @@ internal class DeviceViewModel( contentState.update { it.copy(selectedTab = action.selected) } } - private fun onRefresh() { - refreshMemory() - } - private suspend fun sendCommand(vararg args: String): String { return sendCommandUseCase(deviceSerial.value, *args) .getOrNull() @@ -78,52 +67,4 @@ internal class DeviceViewModel( .removeSuffix("\n") } - private fun refreshMemory() { - viewModelScope.launch(Dispatchers.IO) { - 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() - - memoryState.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 { - - // MEM - 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/models/DeviceUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/DeviceUiState.kt index 2bfae285a..af343f144 100644 --- 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 @@ -5,12 +5,10 @@ import androidx.compose.runtime.Immutable @Immutable data class DeviceUiState( val deviceSerial: String, - val contentState: ContentUiState, - val memoryState: MemoryUiState + val contentState: ContentUiState ) internal fun previewDeviceUiState() = DeviceUiState( deviceSerial = "", - contentState = previewContentUiState(), - memoryState = previewMemoryUiState() + contentState = previewContentUiState() ) 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/MemoryPage.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryPage.kt similarity index 80% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/MemoryPage.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryPage.kt index 913b68c82..8dadde400 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/MemoryPage.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryPage.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.device.pages +package io.github.openflocon.flocondesktop.device.pages.memory import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -11,6 +11,7 @@ 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 @@ -18,13 +19,29 @@ 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 io.github.openflocon.flocondesktop.device.models.MemoryUiState +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( - state: MemoryUiState + 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 @@ -46,7 +63,7 @@ internal fun MemoryPage( ) } itemsIndexed( - items = state.list, + items = uiState.list, key = { _, item -> item.pid } ) { index, item -> BasicItem( @@ -55,7 +72,7 @@ internal fun MemoryPage( processName = item.processName, style = FloconTheme.typography.labelSmall ) - if (index != state.list.lastIndex) { + if (index != uiState.list.lastIndex) { FloconHorizontalDivider( color = FloconTheme.colorPalette.secondary ) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/MemoryUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryUiState.kt similarity index 82% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/MemoryUiState.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryUiState.kt index 8678bc166..997d7d928 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/models/MemoryUiState.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryUiState.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.device.models +package io.github.openflocon.flocondesktop.device.pages.memory import androidx.compose.runtime.Immutable 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..ee2506a0d --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/device/pages/memory/MemoryViewModel.kt @@ -0,0 +1,74 @@ +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.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +class MemoryViewModel( + deviceSerial: String, + sendCommandUseCase: SendCommandUseCase +) : PageViewModel(deviceSerial, sendCommandUseCase) { + + private val _uiState = MutableStateFlow(MemoryUiState(emptyList())) + val uiState = _uiState.asStateFlow() + + init { + refreshMemory() + } + + fun onAction(action: MemoryAction) { + + } + + private fun refreshMemory() { + viewModelScope.launch(Dispatchers.IO) { + 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/features/analytics/view/AnalyticsRowView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsRowView.kt index 0ee34768a..6bbdf67af 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 @@ -19,6 +19,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach import flocondesktop.composeapp.generated.resources.Res diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsScreen.kt index ac6d3ee45..220d466a3 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsScreen.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsScreen.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.paging.PagingData diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardContainerView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardContainerView.kt index e85f29b92..04f9707ea 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardContainerView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardContainerView.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +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.dashboard.model.DashboardContainerViewState 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/DashboardPlainTextView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardPlainTextView.kt index 08d074024..6cccbbf7d 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardPlainTextView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardPlainTextView.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import flocondesktop.composeapp.generated.resources.Res import flocondesktop.composeapp.generated.resources.open_external 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/DatabaseQueryView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/database/view/DatabaseQueryView.kt index f45127d8e..0a154fed5 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/database/view/DatabaseQueryView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/database/view/DatabaseQueryView.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.text.input.OffsetMapping import androidx.compose.ui.text.input.TransformedText import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import flocondesktop.composeapp.generated.resources.Res import flocondesktop.composeapp.generated.resources.auto_update 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..6028d5a5f 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 @@ -39,6 +39,7 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type import androidx.compose.ui.text.font.FontWeight 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.dp import androidx.compose.ui.util.fastForEachIndexed diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/deeplinks/view/DeeplinkFreeformView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/deeplinks/view/DeeplinkFreeformView.kt index 458c23180..7a4a64896 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/deeplinks/view/DeeplinkFreeformView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/deeplinks/view/DeeplinkFreeformView.kt @@ -17,6 +17,7 @@ import androidx.compose.runtime.setValue 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 flocondesktop.composeapp.generated.resources.Res import flocondesktop.composeapp.generated.resources.freeform_link 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/FileItemRow.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/view/FileItemRow.kt index 160b380ce..a171ead15 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/view/FileItemRow.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/view/FileItemRow.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.input.pointer.isShiftPressed import androidx.compose.ui.input.pointer.onPointerEvent import androidx.compose.ui.text.style.TextAlign 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.common.ui.ContextualView import io.github.openflocon.flocondesktop.features.files.model.FilePathUiModel 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/files/view/FilesTopBar.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/view/FilesTopBar.kt index 76f6cc6b5..82c56c4d2 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/view/FilesTopBar.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/view/FilesTopBar.kt @@ -23,6 +23,7 @@ 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 flocondesktop.composeapp.generated.resources.Res import flocondesktop.composeapp.generated.resources.with_folders_size diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/images/view/ImageItemView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/images/view/ImageItemView.kt index 5ad0b1a44..0bbb13acd 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/images/view/ImageItemView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/images/view/ImageItemView.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import coil3.PlatformContext import coil3.SingletonImageLoader 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/body/NetworkJsonScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/body/NetworkJsonScreen.kt index 0110ca73b..260fa9f79 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/body/NetworkJsonScreen.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/body/NetworkJsonScreen.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip 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.sp import com.sebastianneubauer.jsontree.search.rememberSearchState diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/view/NetworkDetailView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/view/NetworkDetailView.kt index ccd7f83df..6e1c49f93 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/view/NetworkDetailView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/view/NetworkDetailView.kt @@ -29,6 +29,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle 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/list/view/header/NetworkItemHeaderView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/header/NetworkItemHeaderView.kt index 93815e534..4fb118018 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/header/NetworkItemHeaderView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/header/NetworkItemHeaderView.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.width 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 flocondesktop.composeapp.generated.resources.Res import flocondesktop.composeapp.generated.resources.network_header_domain 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/commonMain/kotlin/io/github/openflocon/flocondesktop/features/table/view/TableRowView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/table/view/TableRowView.kt index 2502a69d3..9a05c0c71 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/table/view/TableRowView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/table/view/TableRowView.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/table/view/TableScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/table/view/TableScreen.kt index 53addd977..7019c4ef3 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/table/view/TableScreen.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/table/view/TableScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip 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 androidx.lifecycle.compose.collectAsStateWithLifecycle 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" From 1157eb092d24877961e4b24f85f3684f31cf47cd Mon Sep 17 00:00:00 2001 From: TEYSSANDIER Raphael Date: Wed, 12 Nov 2025 15:51:49 +0100 Subject: [PATCH 23/24] feature: Add refresh --- .../app/ui/view/topbar/MainScreenTopBar.kt | 2 + .../ui/view/topbar/TopBarDeviceAndAppView.kt | 4 +- .../topbar/device/TopBarDeviceDropdown.kt | 6 ++- .../ui/view/topbar/device/TopBarDeviceView.kt | 8 ++- .../device/pages/memory/MemoryViewModel.kt | 53 ++++++++++--------- 5 files changed, 44 insertions(+), 29 deletions(-) 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 cf5f5f661..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, 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 d6c48f783..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 @@ -38,7 +39,8 @@ internal fun TopBarDeviceAndAppView( state = devicesState, onDeviceSelected = onDeviceSelected, deleteDevice = deleteDevice, - onClickDetail = onClickDetail + 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 ba0c6ae84..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 @@ -52,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), ) } } 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 7037bf5f8..aec4b9998 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 @@ -20,6 +20,7 @@ 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 @@ -59,7 +60,7 @@ internal fun TopBarDeviceView( else Modifier ) - .padding(horizontal = 8.dp, 4.dp), + .padding(horizontal = 8.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp), ) { @@ -103,6 +104,7 @@ internal fun TopBarDeviceView( } Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically, ) { Column( @@ -127,14 +129,16 @@ internal fun TopBarDeviceView( ), ) } + FloconVerticalDivider(color = FloconTheme.colorPalette.secondary) FloconIconButton( onClick = { onClickDetail?.invoke() } ) { FloconIcon( - imageVector = Icons.Outlined.Details + imageVector = Icons.Outlined.Info ) } if (!selected && onDelete != null) { + FloconVerticalDivider(color = FloconTheme.colorPalette.secondary) Spacer(modifier = Modifier.weight(1f)) Box( Modifier 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 index ee2506a0d..538037cc9 100644 --- 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 @@ -4,10 +4,12 @@ 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, @@ -18,38 +20,41 @@ class MemoryViewModel( val uiState = _uiState.asStateFlow() init { - refreshMemory() + viewModelScope.launch(Dispatchers.IO) { + while (true) { + refreshMemory() + delay(10.seconds) + } + } } fun onAction(action: MemoryAction) { } - private fun refreshMemory() { - viewModelScope.launch(Dispatchers.IO) { - 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 - } + 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() + } + .toList() - _uiState.update { it.copy(list = items) } - } + _uiState.update { it.copy(list = items) } } private fun formatMemoryUsage(memoryUsageKB: Double?): String { From 0c8014c608d386139dbf325342ea9039ca09fc94 Mon Sep 17 00:00:00 2001 From: Raphael Teyssandier Date: Thu, 4 Dec 2025 16:12:18 +0100 Subject: [PATCH 24/24] fix: Build --- .../app/ui/settings/SettingsScreen.kt | 8 +++---- .../ui/view/topbar/device/TopBarDeviceView.kt | 1 + .../analytics/view/AnalyticsRowView.kt | 10 +++++---- .../analytics/view/AnalyticsScreen.kt | 1 - .../dashboard/view/DashboardContainerView.kt | 1 - .../view/items/DashboardPlainTextView.kt | 1 - .../database/view/DatabaseQueryView.kt | 1 - .../database/view/DatabaseResultView.kt | 21 ++++++++++++++----- .../deeplinks/view/DeeplinkFreeformView.kt | 1 - .../features/files/view/FileItemRow.kt | 1 - .../features/files/view/FilesTopBar.kt | 1 - .../features/images/view/ImageItemView.kt | 1 - .../network/body/NetworkJsonScreen.kt | 1 - .../network/detail/view/NetworkDetailView.kt | 1 - .../list/view/header/NetworkItemHeaderView.kt | 1 - .../features/table/view/TableRowView.kt | 1 - .../features/table/view/TableScreen.kt | 1 - 17 files changed, 27 insertions(+), 26 deletions(-) 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 6202d9e05..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 @@ -20,12 +20,9 @@ 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 co.touchlab.kermit.Logger -import io.github.openflocon.flocondesktop.common.ui.window.FloconWindow -import io.github.openflocon.flocondesktop.common.ui.window.createFloconWindowState import flocondesktop.composeapp.generated.resources.Res import flocondesktop.composeapp.generated.resources.general_save import flocondesktop.composeapp.generated.resources.settings_adb_setup_title @@ -136,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/topbar/device/TopBarDeviceView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/device/TopBarDeviceView.kt index aec4b9998..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 @@ -41,6 +41,7 @@ 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 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 6bbdf67af..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 @@ -19,7 +19,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach import flocondesktop.composeapp.generated.resources.Res @@ -74,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 @@ -100,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/analytics/view/AnalyticsScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsScreen.kt index 220d466a3..ac6d3ee45 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsScreen.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsScreen.kt @@ -32,7 +32,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.paging.PagingData diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardContainerView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardContainerView.kt index 04f9707ea..e85f29b92 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardContainerView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/DashboardContainerView.kt @@ -17,7 +17,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -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.dashboard.model.DashboardContainerViewState diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardPlainTextView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardPlainTextView.kt index 6cccbbf7d..08d074024 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardPlainTextView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/view/items/DashboardPlainTextView.kt @@ -17,7 +17,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import flocondesktop.composeapp.generated.resources.Res import flocondesktop.composeapp.generated.resources.open_external diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/database/view/DatabaseQueryView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/database/view/DatabaseQueryView.kt index 0a154fed5..f45127d8e 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/database/view/DatabaseQueryView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/database/view/DatabaseQueryView.kt @@ -37,7 +37,6 @@ import androidx.compose.ui.text.input.OffsetMapping import androidx.compose.ui.text.input.TransformedText import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.withStyle -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import flocondesktop.composeapp.generated.resources.Res import flocondesktop.composeapp.generated.resources.auto_update 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 6028d5a5f..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 @@ -39,7 +39,6 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type import androidx.compose.ui.text.font.FontWeight 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.dp import androidx.compose.ui.util.fastForEachIndexed @@ -127,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 + ) + ) } } @@ -145,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/DeeplinkFreeformView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/deeplinks/view/DeeplinkFreeformView.kt index 7a4a64896..458c23180 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/deeplinks/view/DeeplinkFreeformView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/deeplinks/view/DeeplinkFreeformView.kt @@ -17,7 +17,6 @@ import androidx.compose.runtime.setValue 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 flocondesktop.composeapp.generated.resources.Res import flocondesktop.composeapp.generated.resources.freeform_link diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/view/FileItemRow.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/view/FileItemRow.kt index a171ead15..160b380ce 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/view/FileItemRow.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/view/FileItemRow.kt @@ -27,7 +27,6 @@ import androidx.compose.ui.input.pointer.isShiftPressed import androidx.compose.ui.input.pointer.onPointerEvent import androidx.compose.ui.text.style.TextAlign 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.common.ui.ContextualView import io.github.openflocon.flocondesktop.features.files.model.FilePathUiModel diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/view/FilesTopBar.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/view/FilesTopBar.kt index 82c56c4d2..76f6cc6b5 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/view/FilesTopBar.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/view/FilesTopBar.kt @@ -23,7 +23,6 @@ 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 flocondesktop.composeapp.generated.resources.Res import flocondesktop.composeapp.generated.resources.with_folders_size diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/images/view/ImageItemView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/images/view/ImageItemView.kt index 0bbb13acd..5ad0b1a44 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/images/view/ImageItemView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/images/view/ImageItemView.kt @@ -26,7 +26,6 @@ import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import coil3.PlatformContext import coil3.SingletonImageLoader diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/body/NetworkJsonScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/body/NetworkJsonScreen.kt index 260fa9f79..0110ca73b 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/body/NetworkJsonScreen.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/body/NetworkJsonScreen.kt @@ -31,7 +31,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip 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.sp import com.sebastianneubauer.jsontree.search.rememberSearchState diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/view/NetworkDetailView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/view/NetworkDetailView.kt index 6e1c49f93..ccd7f83df 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/view/NetworkDetailView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/view/NetworkDetailView.kt @@ -29,7 +29,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/header/NetworkItemHeaderView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/header/NetworkItemHeaderView.kt index 4fb118018..93815e534 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/header/NetworkItemHeaderView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/header/NetworkItemHeaderView.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.width 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 flocondesktop.composeapp.generated.resources.Res import flocondesktop.composeapp.generated.resources.network_header_domain diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/table/view/TableRowView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/table/view/TableRowView.kt index 9a05c0c71..2502a69d3 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/table/view/TableRowView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/table/view/TableRowView.kt @@ -12,7 +12,6 @@ import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/table/view/TableScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/table/view/TableScreen.kt index 7019c4ef3..53addd977 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/table/view/TableScreen.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/table/view/TableScreen.kt @@ -26,7 +26,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip 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 androidx.lifecycle.compose.collectAsStateWithLifecycle