diff --git a/.gitignore b/.gitignore
index 654033cd5..25add166c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,3 +23,5 @@ local.properties
# Kotlin Multiplatform
.kotlin/
kotlin-js-store/
+
+FloconAndroid/.idea/
diff --git a/FloconAndroid/.idea/.gitignore b/FloconAndroid/.idea/.gitignore
deleted file mode 100644
index 26d33521a..000000000
--- a/FloconAndroid/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/FloconAndroid/.idea/.name b/FloconAndroid/.idea/.name
deleted file mode 100644
index b3405b3b3..000000000
--- a/FloconAndroid/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-My Application
\ No newline at end of file
diff --git a/FloconAndroid/.idea/AndroidProjectSystem.xml b/FloconAndroid/.idea/AndroidProjectSystem.xml
deleted file mode 100644
index 4a53bee8c..000000000
--- a/FloconAndroid/.idea/AndroidProjectSystem.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/FloconAndroid/.idea/appInsightsSettings.xml b/FloconAndroid/.idea/appInsightsSettings.xml
new file mode 100644
index 000000000..371f2e299
--- /dev/null
+++ b/FloconAndroid/.idea/appInsightsSettings.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FloconAndroid/.idea/compiler.xml b/FloconAndroid/.idea/compiler.xml
deleted file mode 100644
index b86273d94..000000000
--- a/FloconAndroid/.idea/compiler.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/FloconAndroid/.idea/deploymentTargetSelector.xml b/FloconAndroid/.idea/deploymentTargetSelector.xml
deleted file mode 100644
index 3c55daf52..000000000
--- a/FloconAndroid/.idea/deploymentTargetSelector.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/FloconAndroid/.idea/deviceManager.xml b/FloconAndroid/.idea/deviceManager.xml
deleted file mode 100644
index 91f95584d..000000000
--- a/FloconAndroid/.idea/deviceManager.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/FloconAndroid/.idea/gradle.xml b/FloconAndroid/.idea/gradle.xml
deleted file mode 100644
index 3d505e396..000000000
--- a/FloconAndroid/.idea/gradle.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/FloconAndroid/.idea/inspectionProfiles/Project_Default.xml b/FloconAndroid/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index f0c6ad08e..000000000
--- a/FloconAndroid/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/FloconAndroid/.idea/migrations.xml b/FloconAndroid/.idea/migrations.xml
deleted file mode 100644
index f8051a6f9..000000000
--- a/FloconAndroid/.idea/migrations.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/FloconAndroid/.idea/misc.xml b/FloconAndroid/.idea/misc.xml
deleted file mode 100644
index b2c751a35..000000000
--- a/FloconAndroid/.idea/misc.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/FloconAndroid/.idea/runConfigurations.xml b/FloconAndroid/.idea/runConfigurations.xml
deleted file mode 100644
index 16660f1d8..000000000
--- a/FloconAndroid/.idea/runConfigurations.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/FloconAndroid/.idea/runConfigurations/FloconAndroid__app_assembleDebug_.xml b/FloconAndroid/.idea/runConfigurations/FloconAndroid__app_assembleDebug_.xml
deleted file mode 100644
index 6dbf78c0e..000000000
--- a/FloconAndroid/.idea/runConfigurations/FloconAndroid__app_assembleDebug_.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
- true
-
- false
- false
-
- false
- false
-
-
-
\ No newline at end of file
diff --git a/FloconAndroid/.idea/vcs.xml b/FloconAndroid/.idea/vcs.xml
deleted file mode 100644
index 9bad34491..000000000
--- a/FloconAndroid/.idea/vcs.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/FloconAndroid/.idea/xcode.xml b/FloconAndroid/.idea/xcode.xml
deleted file mode 100644
index 4eb324224..000000000
--- a/FloconAndroid/.idea/xcode.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/FloconDesktop/composeApp/build.gradle.kts b/FloconDesktop/composeApp/build.gradle.kts
index 24b7d5d71..598940733 100644
--- a/FloconDesktop/composeApp/build.gradle.kts
+++ b/FloconDesktop/composeApp/build.gradle.kts
@@ -77,7 +77,12 @@ kotlin {
implementation(projects.domain)
implementation(projects.library.designsystem)
+
+ implementation(projects.navigation)
+
implementation(libs.kermit)
+
+// implementation(libs.material3.adaptive)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/App.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/App.kt
deleted file mode 100644
index e3643e523..000000000
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/App.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-@file:OptIn(ExperimentalFoundationApi::class)
-
-package io.github.openflocon.flocondesktop
-
-import androidx.compose.desktop.ui.tooling.preview.Preview
-import androidx.compose.foundation.ComposeFoundationFlags
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxScope
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.safeContentPadding
-import androidx.compose.material3.Button
-import androidx.compose.material3.OutlinedButton
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalUriHandler
-import androidx.compose.ui.unit.dp
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.composeunstyled.Text
-import com.flocon.data.remote.dataRemoteModule
-import io.github.openflocon.data.core.dataCoreModule
-import io.github.openflocon.data.local.dataLocalModule
-import io.github.openflocon.domain.adb.repository.AdbRepository
-import io.github.openflocon.domain.domainModule
-import io.github.openflocon.domain.settings.usecase.ObserveFontSizeMultiplierUseCase
-import io.github.openflocon.flocondesktop.adb.AdbRepositoryImpl
-import io.github.openflocon.flocondesktop.app.AppViewModel
-import io.github.openflocon.flocondesktop.app.di.appModule
-import io.github.openflocon.flocondesktop.app.version.VersionCheckerView
-import io.github.openflocon.flocondesktop.common.di.commonModule
-import io.github.openflocon.flocondesktop.common.ui.feedback.FeedbackDisplayerView
-import io.github.openflocon.flocondesktop.core.di.coreModule
-import io.github.openflocon.flocondesktop.features.featuresModule
-import io.github.openflocon.flocondesktop.main.di.mainModule
-import io.github.openflocon.flocondesktop.main.ui.MainScreen
-import io.github.openflocon.library.designsystem.FloconTheme
-import io.github.openflocon.library.designsystem.components.FloconSurface
-import io.github.openflocon.library.designsystem.components.panel.FloconPanelDisplayer
-import io.github.openflocon.library.designsystem.components.panel.LocalFloconPanelController
-import io.github.openflocon.library.designsystem.components.panel.rememberFloconPanelController
-import org.koin.compose.KoinApplication
-import org.koin.compose.koinInject
-import org.koin.compose.viewmodel.koinViewModel
-import org.koin.core.module.dsl.singleOf
-import org.koin.dsl.bind
-import org.koin.dsl.module
-
-@Composable
-fun App() {
- ComposeFoundationFlags.isNewContextMenuEnabled = true
-
- KoinApplication(
- application = {
- modules(
- commonModule,
- appModule,
- coreModule,
- mainModule,
- featuresModule,
- domainModule,
- dataCoreModule,
- dataLocalModule,
- dataRemoteModule,
- // Temporary
- module {
- singleOf(::AdbRepositoryImpl) bind AdbRepository::class
- },
- )
- },
- ) {
- val fontSizeMultiplier by koinInject()()
- .collectAsStateWithLifecycle()
-
- val panelController = rememberFloconPanelController()
-
- FloconTheme(
- fontSizeMultiplier = fontSizeMultiplier
- ) {
- val appViewModel: AppViewModel = koinViewModel()
-
- FloconSurface(
- modifier = Modifier
- .safeContentPadding()
- .fillMaxSize()
- ) {
- CompositionLocalProvider(LocalFloconPanelController provides panelController) {
- Box(
- modifier = Modifier.fillMaxSize()
- ) {
- MainScreen(
- modifier = Modifier
- .fillMaxSize(),
- )
- FloconPanelDisplayer(
- panelController = panelController,
- modifier = Modifier.fillMaxSize()
- )
- FeedbackDisplayerView()
- VersionCheckerView()
- }
- }
- }
- }
- }
-}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/AppWindow.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/AppWindow.kt
new file mode 100644
index 000000000..6a54d6023
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/AppWindow.kt
@@ -0,0 +1,70 @@
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package io.github.openflocon.flocondesktop
+
+import androidx.compose.foundation.ComposeFoundationFlags
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.flocon.data.remote.dataRemoteModule
+import io.github.openflocon.data.core.dataCoreModule
+import io.github.openflocon.data.local.dataLocalModule
+import io.github.openflocon.domain.adb.repository.AdbRepository
+import io.github.openflocon.domain.domainModule
+import io.github.openflocon.domain.settings.usecase.ObserveFontSizeMultiplierUseCase
+import io.github.openflocon.flocondesktop.adb.AdbRepositoryImpl
+import io.github.openflocon.flocondesktop.app.AppScreen
+import io.github.openflocon.flocondesktop.app.di.appModule
+import io.github.openflocon.flocondesktop.common.di.commonModule
+import io.github.openflocon.flocondesktop.core.di.coreModule
+import io.github.openflocon.flocondesktop.features.featuresModule
+import io.github.openflocon.flocondesktop.features.network.NetworkRoutes
+import io.github.openflocon.library.designsystem.FloconTheme
+import io.github.openflocon.navigation.MainFloconNavigationState
+import org.koin.compose.KoinApplication
+import org.koin.compose.koinInject
+import org.koin.core.module.dsl.singleOf
+import org.koin.dsl.bind
+import org.koin.dsl.module
+
+@Composable
+fun App() {
+ ComposeFoundationFlags.isNewContextMenuEnabled = true
+
+ KoinApplication(
+ application = {
+ modules(
+ commonModule,
+ appModule,
+ coreModule,
+ featuresModule,
+ domainModule,
+ dataCoreModule,
+ dataLocalModule,
+ dataRemoteModule,
+ // Temporary
+ module {
+// scope {
+// scoped { MainFloconNavigationState(MainRoutes.Main) }
+// }
+ single { MainFloconNavigationState(NetworkRoutes.Main) }
+ singleOf(::AdbRepositoryImpl) bind AdbRepository::class
+ },
+ )
+ },
+ ) {
+ val fontSizeMultiplier by koinInject()()
+ .collectAsStateWithLifecycle()
+
+// KoinScope(
+// scopeDefinition = { createScope(MainRoutes.Sub.Main) }
+// ) {
+ FloconTheme(
+ fontSizeMultiplier = fontSizeMultiplier
+ ) {
+ AppScreen()
+ }
+// }
+ }
+}
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
new file mode 100644
index 000000000..e6611e90d
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/AppAction.kt
@@ -0,0 +1,25 @@
+package io.github.openflocon.flocondesktop.app
+
+import io.github.openflocon.flocondesktop.app.ui.model.DeviceAppUiModel
+import io.github.openflocon.flocondesktop.app.ui.model.DeviceItemUiModel
+import io.github.openflocon.flocondesktop.app.ui.model.SubScreen
+
+internal sealed interface AppAction {
+
+ data class SelectMenu(val menu: SubScreen) : AppAction
+
+ data class DeleteApp(val app: DeviceAppUiModel) : AppAction
+
+ data class DeleteDevice(val device: DeviceItemUiModel) : AppAction
+
+ data class SelectApp(val app: DeviceAppUiModel) : AppAction
+
+ data class SelectDevice(val device: DeviceItemUiModel) : AppAction
+
+ data object Record : AppAction
+
+ data object Restart : AppAction
+
+ data object Screenshoot : 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
new file mode 100644
index 000000000..6baaa32d9
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/AppScreen.kt
@@ -0,0 +1,93 @@
+package io.github.openflocon.flocondesktop.app
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+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.features.analytics.analyticsRoutes
+import io.github.openflocon.flocondesktop.features.dashboard.dashboardRoutes
+import io.github.openflocon.flocondesktop.features.database.databaseRoutes
+import io.github.openflocon.flocondesktop.features.deeplinks.deeplinkRoutes
+import io.github.openflocon.flocondesktop.features.files.filesRoutes
+import io.github.openflocon.flocondesktop.features.images.imageRoutes
+import io.github.openflocon.flocondesktop.features.network.networkRoutes
+import io.github.openflocon.flocondesktop.features.sharedpreferences.sharedPreferencesRoutes
+import io.github.openflocon.flocondesktop.features.table.tableRoutes
+import io.github.openflocon.library.designsystem.FloconTheme
+import io.github.openflocon.navigation.FloconNavigation
+import io.github.openflocon.navigation.MainFloconNavigationState
+import io.github.openflocon.navigation.scene.DialogSceneStrategy
+import io.github.openflocon.navigation.scene.PanelSceneStrategy
+import io.github.openflocon.navigation.scene.WindowSceneStrategy
+import org.koin.compose.viewmodel.koinViewModel
+
+@Composable
+fun AppScreen() {
+ val viewModel = koinViewModel()
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
+ Content(
+ uiState = uiState,
+ navigationState = viewModel.navigationState,
+ onAction = viewModel::onAction
+ )
+}
+
+@Composable
+private fun Content(
+ uiState: AppUiState,
+ navigationState: MainFloconNavigationState,
+ onAction: (AppAction) -> Unit
+) {
+ FloconNavigation(
+ navigationState = navigationState,
+ sceneStrategy = PanelSceneStrategy()
+ .then(WindowSceneStrategy())
+ .then(DialogSceneStrategy())
+ .then(
+ MenuSceneStrategy(
+ menuContent = {
+ LeftPanelView(
+ state = uiState.menuState,
+ expanded = it,
+ onClickItem = { menu -> onAction(AppAction.SelectMenu(menu.screen)) }
+ )
+ },
+ topBarContent = {
+ MainScreenTopBar(
+ devicesState = uiState.deviceState,
+ appsState = uiState.appState,
+ recordState = uiState.recordState,
+ deleteApp = { onAction(AppAction.DeleteApp(it)) },
+ deleteDevice = { onAction(AppAction.DeleteDevice(it)) },
+ onDeviceSelected = { onAction(AppAction.SelectDevice(it)) },
+ onAppSelected = { onAction(AppAction.SelectApp(it)) },
+ onRecordClicked = { onAction(AppAction.Record) },
+ onRestartClicked = { onAction(AppAction.Restart) },
+ onTakeScreenshotClicked = { onAction(AppAction.Screenshoot) }
+ )
+ }
+ ))
+ .then(SinglePaneSceneStrategy()),
+ modifier = Modifier
+ .fillMaxSize()
+ .background(FloconTheme.colorPalette.surface)
+ ) {
+ analyticsRoutes()
+ dashboardRoutes()
+ databaseRoutes()
+ deeplinkRoutes()
+ filesRoutes()
+ imageRoutes()
+ networkRoutes()
+ sharedPreferencesRoutes()
+ tableRoutes()
+ settingsRoutes()
+ }
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/AppUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/AppUiState.kt
new file mode 100644
index 000000000..618a3c680
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/AppUiState.kt
@@ -0,0 +1,28 @@
+package io.github.openflocon.flocondesktop.app
+
+import androidx.compose.runtime.Immutable
+import io.github.openflocon.flocondesktop.app.ui.model.AppsStateUiModel
+import io.github.openflocon.flocondesktop.app.ui.model.DevicesStateUiModel
+import io.github.openflocon.flocondesktop.app.ui.model.RecordVideoStateUiModel
+import io.github.openflocon.flocondesktop.app.ui.model.SubScreen
+import io.github.openflocon.flocondesktop.app.ui.model.leftpanel.MenuState
+import io.github.openflocon.flocondesktop.app.ui.model.leftpanel.previewMenuState
+import io.github.openflocon.flocondesktop.app.ui.model.previewAppsStateUiModel
+import io.github.openflocon.flocondesktop.app.ui.model.previewDevicesStateUiModel
+
+@Immutable
+data class AppUiState(
+ val contentState: ContentUiState,
+ val menuState: MenuState,
+ val deviceState: DevicesStateUiModel,
+ val appState: AppsStateUiModel,
+ val recordState: RecordVideoStateUiModel
+)
+
+fun previewAppUiState() = AppUiState(
+ contentState = previewContentUiState(),
+ menuState = previewMenuState(SubScreen.Network),
+ deviceState = previewDevicesStateUiModel(),
+ appState = previewAppsStateUiModel(),
+ recordState = RecordVideoStateUiModel.Recording
+)
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 09afabb14..ffe43724b 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
@@ -3,38 +3,179 @@ package io.github.openflocon.flocondesktop.app
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import io.github.openflocon.domain.common.DispatcherProvider
+import io.github.openflocon.domain.device.usecase.RestartAppUseCase
+import io.github.openflocon.domain.device.usecase.TakeScreenshotUseCase
+import io.github.openflocon.domain.feedback.FeedbackDisplayer
import io.github.openflocon.domain.settings.usecase.InitAdbPathUseCase
import io.github.openflocon.domain.settings.usecase.StartAdbForwardUseCase
+import io.github.openflocon.flocondesktop.app.ui.delegates.DevicesDelegate
+import io.github.openflocon.flocondesktop.app.ui.delegates.RecordVideoDelegate
+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.features.analytics.AnalyticsRoutes
+import io.github.openflocon.flocondesktop.features.dashboard.DashboardRoutes
+import io.github.openflocon.flocondesktop.features.database.DatabaseRoutes
+import io.github.openflocon.flocondesktop.features.deeplinks.DeeplinkRoutes
+import io.github.openflocon.flocondesktop.features.files.FilesRoutes
+import io.github.openflocon.flocondesktop.features.images.ImageRoutes
+import io.github.openflocon.flocondesktop.features.network.NetworkRoutes
+import io.github.openflocon.flocondesktop.features.sharedpreferences.SharedPreferencesRoutes
+import io.github.openflocon.flocondesktop.features.table.TableRoutes
import io.github.openflocon.flocondesktop.messages.ui.MessagesServerDelegate
+import io.github.openflocon.navigation.MainFloconNavigationState
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
-class AppViewModel(
+internal class AppViewModel(
messagesServerDelegate: MessagesServerDelegate,
initAdbPathUseCase: InitAdbPathUseCase,
startAdbForwardUseCase: StartAdbForwardUseCase,
+ val navigationState: MainFloconNavigationState,
private val initialSetupStateHolder: InitialSetupStateHolder,
private val dispatcherProvider: DispatcherProvider,
-) : ViewModel(
- messagesServerDelegate,
-) {
+ private val devicesDelegate: DevicesDelegate,
+ private val takeScreenshotUseCase: TakeScreenshotUseCase,
+ private val restartAppUseCase: RestartAppUseCase,
+ private val recordVideoDelegate: RecordVideoDelegate,
+ private val feedbackDisplayer: FeedbackDisplayer,
+) : ViewModel(messagesServerDelegate) {
+
+ private val contentState = MutableStateFlow(
+ ContentUiState(
+ current = SubScreen.Network
+ )
+ )
+ private val menuState = MutableStateFlow(
+ buildMenu(SubScreen.Network)
+ )
+
+ val uiState = combine(
+ contentState,
+ menuState,
+ devicesDelegate.devicesState,
+ devicesDelegate.appsState,
+ recordVideoDelegate.state
+ ) { content, menu, devices, apps, record ->
+ AppUiState(
+ contentState = content,
+ menuState = menu,
+ deviceState = devices,
+ appState = apps,
+ recordState = record
+ )
+ }
+ .stateInWhileSubscribed(
+ AppUiState(
+ contentState = contentState.value,
+ menuState = menuState.value,
+ deviceState = devicesDelegate.devicesState.value,
+ appState = devicesDelegate.appsState.value,
+ recordState = recordVideoDelegate.state.value
+ )
+ )
init {
viewModelScope.launch(dispatcherProvider.viewModel) {
initAdbPathUseCase().alsoFailure {
initialSetupStateHolder.setRequiresInitialSetup()
}
+
+ messagesServerDelegate.initialize()
+
+ launch {
+ while (isActive) {
+ // ensure we have the forward enabled
+ startAdbForwardUseCase()
+ delay(1_500)
+ }
+ }
}
+ }
- messagesServerDelegate.initialize()
+ fun onAction(action: AppAction) {
+ when (action) {
+ is AppAction.SelectMenu -> onSelectMenu(action)
+ is AppAction.DeleteApp -> deleteApp(action)
+ is AppAction.DeleteDevice -> deleteDevice(action)
+ AppAction.Record -> onRecord()
+ AppAction.Restart -> onRestart()
+ AppAction.Screenshoot -> onTakeScreenshot()
+ is AppAction.SelectApp -> onAppSelected(action)
+ is AppAction.SelectDevice -> onDeviceSelected(action)
+ }
+ }
- viewModelScope.launch {
- while (isActive) {
- // ensure we have the forward enabled
- startAdbForwardUseCase()
- delay(1_500)
+ private fun onSelectMenu(action: AppAction.SelectMenu) {
+ contentState.update { it.copy(current = action.menu) }
+ navigationState.menu(
+ when (action.menu) {
+ SubScreen.Analytics -> AnalyticsRoutes.Main
+ SubScreen.Dashboard -> DashboardRoutes.Main
+ SubScreen.Database -> DatabaseRoutes.Main
+ SubScreen.Deeplinks -> DeeplinkRoutes.Main
+ SubScreen.Files -> FilesRoutes.Main
+ SubScreen.Images -> ImageRoutes.Main
+ SubScreen.Network -> NetworkRoutes.Main
+ SubScreen.Settings -> SettingsRoutes.Main
+ SubScreen.SharedPreferences -> SharedPreferencesRoutes.Main
+ SubScreen.Tables -> TableRoutes.Main
}
+ )
+ }
+
+ private fun onDeviceSelected(action: AppAction.SelectDevice) {
+ viewModelScope.launch(dispatcherProvider.viewModel) {
+ devicesDelegate.select(action.device.id)
}
}
+
+ private fun deleteDevice(action: AppAction.DeleteDevice) {
+ viewModelScope.launch(dispatcherProvider.viewModel) {
+ devicesDelegate.delete(action.device.id)
+ }
+ }
+
+ private fun deleteApp(action: AppAction.DeleteApp) {
+ viewModelScope.launch(dispatcherProvider.viewModel) {
+ devicesDelegate.deleteApp(action.app.packageName)
+ }
+ }
+
+ private fun onAppSelected(action: AppAction.SelectApp) {
+ viewModelScope.launch(dispatcherProvider.viewModel) {
+ devicesDelegate.selectApp(action.app.packageName)
+ }
+ }
+
+ private fun onRecord() {
+ viewModelScope.launch(dispatcherProvider.viewModel) {
+ recordVideoDelegate.toggleRecording()
+ }
+ }
+
+ private fun onRestart() {
+ viewModelScope.launch(dispatcherProvider.viewModel) {
+ restartAppUseCase()
+ }
+ }
+
+ private fun onTakeScreenshot() {
+ viewModelScope.launch(dispatcherProvider.viewModel) {
+ takeScreenshotUseCase().fold(
+ doOnFailure = {
+ feedbackDisplayer.displayMessage(it.message ?: "Unknown error")
+ },
+ doOnSuccess = {
+ feedbackDisplayer.displayMessage("Success, file saved at $it")
+ },
+ )
+ }
+ }
+
}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ContentUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ContentUiState.kt
new file mode 100644
index 000000000..f6d17b757
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ContentUiState.kt
@@ -0,0 +1,13 @@
+package io.github.openflocon.flocondesktop.app
+
+import androidx.compose.runtime.Immutable
+import io.github.openflocon.flocondesktop.app.ui.model.SubScreen
+
+@Immutable
+data class ContentUiState(
+ val current: SubScreen
+)
+
+fun previewContentUiState() = ContentUiState(
+ current = SubScreen.Network
+)
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/MenuScene.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/MenuScene.kt
new file mode 100644
index 000000000..d88cce8c7
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/MenuScene.kt
@@ -0,0 +1,141 @@
+package io.github.openflocon.flocondesktop.app
+
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.ChevronRight
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import androidx.navigation3.runtime.NavEntry
+import androidx.navigation3.scene.Scene
+import androidx.navigation3.scene.SceneStrategy
+import androidx.navigation3.scene.SceneStrategyScope
+import io.github.openflocon.flocondesktop.app.ui.view.leftpannel.PanelMaxWidth
+import io.github.openflocon.flocondesktop.app.ui.view.leftpannel.PanelMinWidth
+import io.github.openflocon.library.designsystem.FloconTheme
+import io.github.openflocon.library.designsystem.components.FloconIcon
+import io.github.openflocon.library.designsystem.components.FloconScaffold
+import io.github.openflocon.navigation.FloconRoute
+
+@Immutable
+data class MenuScene(
+ override val entries: List>,
+ override val previousEntries: List>,
+ val entry: NavEntry,
+ val menuContent: @Composable (expanded: Boolean) -> Unit,
+ val topBarContent: @Composable (() -> Unit)?
+) : Scene {
+ override val key: Any
+ get() = Unit
+
+ override val content: @Composable (() -> Unit) = {
+ var expanded by remember { mutableStateOf(true) }
+ val width by animateDpAsState(targetValue = if (expanded) PanelMaxWidth else PanelMinWidth)
+ var windowSize by remember { mutableStateOf(IntSize.Zero) }
+ val position by animateDpAsState(
+ targetValue = if (expanded) PanelMaxWidth else PanelMinWidth,
+ )
+ val rotate by animateFloatAsState(targetValue = if (expanded) 180f else 0f)
+
+ Box {
+ FloconScaffold(
+ topBar = { topBarContent?.invoke() },
+ modifier = Modifier
+ .fillMaxSize()
+ .onGloballyPositioned {
+ windowSize = it.size // TODO Add windowsize lib
+ }
+ ) { padding ->
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(padding)
+ .padding(8.dp)
+ ) {
+ Box(
+ modifier = Modifier
+ .width(width)
+ .fillMaxHeight(),
+ ) {
+ menuContent(expanded)
+ }
+ entry.Content()
+ }
+ }
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = Modifier
+ .width(20.dp)
+ .height(60.dp)
+ .graphicsLayer {
+ translationX = position.toPx() - size.width / 2 - 2.dp.toPx()
+ translationY = (windowSize.height / 4f).times(3f) + (size.height / 2f)
+ }
+ .clip(RoundedCornerShape(topStart = 4.dp, bottomStart = 4.dp))
+ .background(FloconTheme.colorPalette.secondary)
+ .clickable(onClick = { expanded = !expanded }),
+ ) {
+ FloconIcon(
+ imageVector = Icons.Outlined.ChevronRight,
+ tint = Color.LightGray,
+ modifier = Modifier.rotate(rotate),
+ )
+ }
+ }
+ }
+}
+
+class MenuSceneStrategy(
+ private val menuContent: @Composable (expanded: Boolean) -> Unit,
+ private val topBarContent: @Composable (() -> Unit)? = null
+) : SceneStrategy {
+
+ override fun SceneStrategyScope.calculateScene(entries: List>): Scene? {
+ val entry = entries.lastOrNull() ?: return null
+
+ if (entry.metadata.containsKey(MENU_KEY)) {
+ return MenuScene(
+ entries = listOf(entry),
+ previousEntries = entries.dropLast(1),
+ entry = entry,
+ menuContent = menuContent,
+ topBarContent = topBarContent
+ )
+ }
+
+ return null
+ }
+
+ companion object {
+ private const val MENU_KEY = "menu_key"
+
+ fun menu() = mapOf(MENU_KEY to MENU_KEY)
+
+ }
+
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/di/AppUiModule.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/di/AppUiModule.kt
index d07305e38..78498b84c 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/di/AppUiModule.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/di/AppUiModule.kt
@@ -1,12 +1,16 @@
package io.github.openflocon.flocondesktop.app.di
import io.github.openflocon.flocondesktop.app.AppViewModel
+import io.github.openflocon.flocondesktop.app.ui.delegates.DevicesDelegate
+import io.github.openflocon.flocondesktop.app.ui.delegates.RecordVideoDelegate
import io.github.openflocon.flocondesktop.app.version.VersionCheckerViewModel
+import org.koin.core.module.dsl.factoryOf
import org.koin.core.module.dsl.viewModelOf
import org.koin.dsl.module
-val appUiModule =
- module {
- viewModelOf(::AppViewModel)
- viewModelOf(::VersionCheckerViewModel)
- }
+val appUiModule = module {
+ viewModelOf(::AppViewModel)
+ factoryOf(::DevicesDelegate)
+ factoryOf(::RecordVideoDelegate)
+ viewModelOf(::VersionCheckerViewModel)
+}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/delegates/DevicesDelegate.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/delegates/DevicesDelegate.kt
similarity index 93%
rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/delegates/DevicesDelegate.kt
rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/delegates/DevicesDelegate.kt
index 7e4980e28..6f45f703e 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/delegates/DevicesDelegate.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/delegates/DevicesDelegate.kt
@@ -1,6 +1,5 @@
-package io.github.openflocon.flocondesktop.main.ui.delegates
+package io.github.openflocon.flocondesktop.app.ui.delegates
-import io.github.openflocon.domain.device.models.DeviceIdAndPackageNameDomainModel
import io.github.openflocon.domain.device.usecase.DeleteDeviceApplicationUseCase
import io.github.openflocon.domain.device.usecase.DeleteDeviceUseCase
import io.github.openflocon.domain.device.usecase.GetCurrentDeviceIdAndPackageNameUseCase
@@ -12,10 +11,10 @@ import io.github.openflocon.domain.device.usecase.ObserveCurrentDeviceIdUseCase
import io.github.openflocon.domain.device.usecase.ObserveDevicesUseCase
import io.github.openflocon.domain.device.usecase.SelectDeviceAppUseCase
import io.github.openflocon.domain.device.usecase.SelectDeviceUseCase
+import io.github.openflocon.flocondesktop.app.ui.model.AppsStateUiModel
+import io.github.openflocon.flocondesktop.app.ui.model.DevicesStateUiModel
import io.github.openflocon.flocondesktop.common.coroutines.closeable.CloseableDelegate
import io.github.openflocon.flocondesktop.common.coroutines.closeable.CloseableScoped
-import io.github.openflocon.flocondesktop.main.ui.model.AppsStateUiModel
-import io.github.openflocon.flocondesktop.main.ui.model.DevicesStateUiModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@@ -23,7 +22,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
class DevicesDelegate(
private val selectDeviceUseCase: SelectDeviceUseCase,
@@ -88,7 +86,7 @@ class DevicesDelegate(
// do this only if we have 1 unique active device
observeActiveDevicesUseCase().distinctUntilChanged().onEach { activeDevices ->
val currentDeviceId = getCurrentDeviceIdAndPackageNameUseCase()?.deviceId
- if(activeDevices.size == 1 && currentDeviceId !in activeDevices.map { it.deviceId }) {
+ if (activeDevices.size == 1 && currentDeviceId !in activeDevices.map { it.deviceId }) {
val firstActiveDevice = activeDevices.first()
select(firstActiveDevice.deviceId)
}
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/delegates/Mapper.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/delegates/Mapper.kt
similarity index 89%
rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/delegates/Mapper.kt
rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/delegates/Mapper.kt
index ce27c5ede..141406900 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/delegates/Mapper.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/delegates/Mapper.kt
@@ -1,11 +1,11 @@
-package io.github.openflocon.flocondesktop.main.ui.delegates
+package io.github.openflocon.flocondesktop.app.ui.delegates
import io.github.openflocon.domain.device.models.DeviceAppDomainModel
import io.github.openflocon.domain.device.models.DeviceCapabilitiesDomainModel
import io.github.openflocon.domain.device.models.DeviceDomainModel
import io.github.openflocon.domain.device.models.DeviceIdAndPackageNameDomainModel
-import io.github.openflocon.flocondesktop.main.ui.model.DeviceAppUiModel
-import io.github.openflocon.flocondesktop.main.ui.model.DeviceItemUiModel
+import io.github.openflocon.flocondesktop.app.ui.model.DeviceAppUiModel
+import io.github.openflocon.flocondesktop.app.ui.model.DeviceItemUiModel
internal fun mapListToUi(
devices: List,
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/delegates/RecordVideoDelegate.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/delegates/RecordVideoDelegate.kt
similarity index 94%
rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/delegates/RecordVideoDelegate.kt
rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/delegates/RecordVideoDelegate.kt
index efb910dff..107d19b9b 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/delegates/RecordVideoDelegate.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/delegates/RecordVideoDelegate.kt
@@ -1,13 +1,13 @@
-package io.github.openflocon.flocondesktop.main.ui.delegates
+package io.github.openflocon.flocondesktop.app.ui.delegates
import io.github.openflocon.domain.common.DispatcherProvider
import io.github.openflocon.domain.device.models.RecordingDomainModel
import io.github.openflocon.domain.device.usecase.StartRecordingVideoUseCase
import io.github.openflocon.domain.device.usecase.StopRecordingVideoUseCase
import io.github.openflocon.domain.feedback.FeedbackDisplayer
+import io.github.openflocon.flocondesktop.app.ui.model.RecordVideoStateUiModel
import io.github.openflocon.flocondesktop.common.coroutines.closeable.CloseableDelegate
import io.github.openflocon.flocondesktop.common.coroutines.closeable.CloseableScoped
-import io.github.openflocon.flocondesktop.main.ui.model.RecordVideoStateUiModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
import kotlinx.coroutines.flow.StateFlow
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/AppUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/AppUiState.kt
new file mode 100644
index 000000000..260f851e4
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/AppUiState.kt
@@ -0,0 +1,12 @@
+package io.github.openflocon.flocondesktop.app.ui.model
+
+import androidx.compose.runtime.Immutable
+
+@Immutable
+internal data class AppUiState(
+ val test: String
+)
+
+private fun previewAppUiState() = AppUiState(
+ test = ""
+)
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/DeviceAppUiModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/DeviceAppUiModel.kt
similarity index 84%
rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/DeviceAppUiModel.kt
rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/DeviceAppUiModel.kt
index 037c859d4..c1ed82c44 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/DeviceAppUiModel.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/DeviceAppUiModel.kt
@@ -1,4 +1,4 @@
-package io.github.openflocon.flocondesktop.main.ui.model
+package io.github.openflocon.flocondesktop.app.ui.model
import androidx.compose.runtime.Immutable
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/DeviceItemUiModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/DeviceItemUiModel.kt
similarity index 91%
rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/DeviceItemUiModel.kt
rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/DeviceItemUiModel.kt
index be8acdee3..f76be5d0e 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/DeviceItemUiModel.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/DeviceItemUiModel.kt
@@ -1,4 +1,4 @@
-package io.github.openflocon.flocondesktop.main.ui.model
+package io.github.openflocon.flocondesktop.app.ui.model
import androidx.compose.runtime.Immutable
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/DevicesStateUiModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/DevicesStateUiModel.kt
similarity index 84%
rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/DevicesStateUiModel.kt
rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/DevicesStateUiModel.kt
index f7f881fb8..a86e7c08c 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/DevicesStateUiModel.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/DevicesStateUiModel.kt
@@ -1,4 +1,4 @@
-package io.github.openflocon.flocondesktop.main.ui.model
+package io.github.openflocon.flocondesktop.app.ui.model
import androidx.compose.runtime.Immutable
@@ -86,3 +86,18 @@ fun previewDevicesStateUiModel(): DevicesStateUiModel = DevicesStateUiModel.With
canRestart = true,
),
)
+
+fun previewAppsStateUiModel(): AppsStateUiModel = AppsStateUiModel.WithApps(
+ apps = listOf(
+ DeviceAppUiModel(
+ name = "appName1",
+ packageName = "packageName",
+ iconEncoded = null
+ )
+ ),
+ appSelected = DeviceAppUiModel(
+ name = "appName1",
+ packageName = "packageName",
+ iconEncoded = null
+ )
+)
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/RecordVideoStateUiModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/RecordVideoStateUiModel.kt
similarity index 53%
rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/RecordVideoStateUiModel.kt
rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/RecordVideoStateUiModel.kt
index b70b23ef1..716ffbef9 100644
--- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/RecordVideoStateUiModel.kt
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/RecordVideoStateUiModel.kt
@@ -1,4 +1,4 @@
-package io.github.openflocon.flocondesktop.main.ui.model
+package io.github.openflocon.flocondesktop.app.ui.model
enum class RecordVideoStateUiModel {
Idle,
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/SubScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/SubScreen.kt
new file mode 100644
index 000000000..2fa0d345f
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/SubScreen.kt
@@ -0,0 +1,26 @@
+package io.github.openflocon.flocondesktop.app.ui.model
+
+sealed interface SubScreen {
+ data object Dashboard : SubScreen
+
+ // TODO group network, grpc, networkImages
+ data object Network : SubScreen
+ data object Images : SubScreen // network images
+
+ // storage
+ data object Database : SubScreen
+ data object Files : SubScreen // device files (context.cache, context.files)
+ data object SharedPreferences : SubScreen
+
+ data object Analytics : SubScreen
+ data object Tables : SubScreen
+
+ data object Settings : SubScreen
+
+ data object Deeplinks : SubScreen
+}
+
+val SubScreen.id: String
+ get() {
+ return javaClass.simpleName
+ }
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/leftpanel/MenuItem.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/leftpanel/MenuItem.kt
new file mode 100644
index 000000000..ba57fd13a
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/leftpanel/MenuItem.kt
@@ -0,0 +1,13 @@
+package io.github.openflocon.flocondesktop.app.ui.model.leftpanel
+
+import androidx.compose.runtime.Immutable
+import androidx.compose.ui.graphics.vector.ImageVector
+import io.github.openflocon.flocondesktop.app.ui.model.SubScreen
+
+@Immutable
+data class MenuItem(
+ val screen: SubScreen,
+ val icon: ImageVector,
+ val text: String,
+ val isEnabled: Boolean,
+)
diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/leftpanel/MenuSection.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/leftpanel/MenuSection.kt
new file mode 100644
index 000000000..3ae0e0e28
--- /dev/null
+++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/leftpanel/MenuSection.kt
@@ -0,0 +1,6 @@
+package io.github.openflocon.flocondesktop.app.ui.model.leftpanel
+
+data class MenuSection(
+ val title: String,
+ val items: List