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