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, +) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/leftpanel/MenuUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/leftpanel/MenuUiState.kt new file mode 100644 index 000000000..f4c8d1dd2 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/model/leftpanel/MenuUiState.kt @@ -0,0 +1,141 @@ +package io.github.openflocon.flocondesktop.app.ui.model.leftpanel + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Settings +import androidx.compose.runtime.Immutable +import io.github.openflocon.flocondesktop.app.ui.model.SubScreen +import io.github.openflocon.flocondesktop.app.ui.view.displayName +import io.github.openflocon.flocondesktop.app.ui.view.icon + +@Immutable +data class MenuState( + val current: SubScreen, + val sections: List, + val bottomItems: List, +) + +fun previewMenuState(current: SubScreen) = MenuState( + current = current, + bottomItems = listOf( + MenuItem( + screen = SubScreen.Settings, + icon = Icons.Outlined.Settings, + text = "Settings", + isEnabled = true, + ), + ), + sections = listOf( + MenuSection( + title = "Network", + items = listOf( + MenuItem( + screen = SubScreen.Network, + icon = Icons.Outlined.Settings, + text = "Http", + isEnabled = true, + ), + MenuItem( + screen = SubScreen.Images, + icon = Icons.Outlined.Settings, + text = "Images", + isEnabled = true, + ), + MenuItem( + screen = SubScreen.Network, + icon = Icons.Outlined.Settings, + text = "Grpc", + isEnabled = true, + ), + ), + ), + MenuSection( + title = "Storage", + items = listOf( + MenuItem( + screen = SubScreen.Network, + icon = Icons.Outlined.Settings, + text = "Database", + isEnabled = true, + ), + MenuItem( + screen = SubScreen.SharedPreferences, + icon = Icons.Outlined.Settings, + text = "SharedPreferences", + isEnabled = true, + ), + MenuItem( + screen = SubScreen.Files, + icon = Icons.Outlined.Settings, + text = "Files", + isEnabled = true, + ), + ), + ), + MenuSection( + title = "Data", + items = listOf( + MenuItem( + screen = SubScreen.Dashboard, + icon = Icons.Outlined.Settings, + text = "Dashboard", + isEnabled = true, + ), + MenuItem( + screen = SubScreen.Tables, + icon = Icons.Outlined.Settings, + text = "Tables", + isEnabled = true, + ), + ), + ), + ), +) + +internal fun buildMenu(current: SubScreen) = MenuState( + current = current, + bottomItems = listOf( + item(subScreen = SubScreen.Settings) + ), + sections = listOf( + MenuSection( + title = "Network", + items = listOf( + item(subScreen = SubScreen.Network), + item(subScreen = SubScreen.Images), + ), + ), + MenuSection( + title = "Storage", + items = listOf( + item(SubScreen.Database), + item(SubScreen.SharedPreferences), + item(SubScreen.Files), + ), + ), + MenuSection( + title = "Data", + items = listOf( + item(SubScreen.Dashboard), + item(SubScreen.Analytics), + item(SubScreen.Tables), + ), + ), + MenuSection( + title = "Actions", + items = listOf( + item(SubScreen.Deeplinks) + ), + ), + ), +) + +private fun item( + subScreen: SubScreen +): MenuItem { + return MenuItem( + screen = subScreen, + icon = subScreen.icon(), + text = subScreen.displayName(), + isEnabled = true + ) +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/settings/AboutScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/AboutScreen.kt similarity index 97% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/settings/AboutScreen.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/AboutScreen.kt index 04d06296b..53206a1ca 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/settings/AboutScreen.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/AboutScreen.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.main.ui.settings +package io.github.openflocon.flocondesktop.app.ui.settings import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/DI.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/DI.kt new file mode 100644 index 000000000..60d408410 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/DI.kt @@ -0,0 +1,8 @@ +package io.github.openflocon.flocondesktop.app.ui.settings + +import org.koin.core.module.dsl.viewModelOf +import org.koin.dsl.module + +internal val settingsModule = module { + viewModelOf(::SettingsViewModel) +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/Navigation.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/Navigation.kt new file mode 100644 index 000000000..0c70d65c1 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/Navigation.kt @@ -0,0 +1,21 @@ +package io.github.openflocon.flocondesktop.app.ui.settings + +import androidx.navigation3.runtime.EntryProviderScope +import io.github.openflocon.flocondesktop.app.MenuSceneStrategy +import io.github.openflocon.navigation.FloconRoute +import kotlinx.serialization.Serializable + +sealed interface SettingsRoutes : FloconRoute { + + @Serializable + data object Main : SettingsRoutes + +} + +fun EntryProviderScope.settingsRoutes() { + entry( + metadata = MenuSceneStrategy.menu() + ) { + SettingsScreen() + } +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/settings/SettingsAction.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/SettingsAction.kt similarity index 65% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/settings/SettingsAction.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/SettingsAction.kt index b568f0ba8..a51d05f16 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/settings/SettingsAction.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/SettingsAction.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.main.ui.settings +package io.github.openflocon.flocondesktop.app.ui.settings sealed interface SettingsAction { diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/settings/SettingsScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/SettingsScreen.kt similarity index 99% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/settings/SettingsScreen.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/SettingsScreen.kt index 179d4e676..e18a4dc52 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/settings/SettingsScreen.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/SettingsScreen.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.main.ui.settings +package io.github.openflocon.flocondesktop.app.ui.settings import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/settings/SettingsUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/SettingsUiState.kt similarity index 76% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/settings/SettingsUiState.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/SettingsUiState.kt index dc5c2f0b3..cad85f0b0 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/settings/SettingsUiState.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/SettingsUiState.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.main.ui.settings +package io.github.openflocon.flocondesktop.app.ui.settings import androidx.compose.runtime.Immutable diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/settings/SettingsViewModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/SettingsViewModel.kt similarity index 98% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/settings/SettingsViewModel.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/SettingsViewModel.kt index dd9cf88ae..576a7c5dd 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/settings/SettingsViewModel.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/settings/SettingsViewModel.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.main.ui.settings +package io.github.openflocon.flocondesktop.app.ui.settings import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/SubScreenSelectorItem.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/SubScreenSelectorItem.kt similarity index 93% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/SubScreenSelectorItem.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/SubScreenSelectorItem.kt index 011e4098e..34ae55f1b 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/SubScreenSelectorItem.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/SubScreenSelectorItem.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.main.ui.view +package io.github.openflocon.flocondesktop.app.ui.view import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Link @@ -12,7 +12,7 @@ import androidx.compose.material.icons.outlined.StackedBarChart import androidx.compose.material.icons.outlined.Storage import androidx.compose.material.icons.outlined.TableView import androidx.compose.ui.graphics.vector.ImageVector -import io.github.openflocon.flocondesktop.main.ui.model.SubScreen +import io.github.openflocon.flocondesktop.app.ui.model.SubScreen // Extension function to get the display name for each SubScreen fun SubScreen.displayName(): String = when (this) { diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/leftpannel/LeftPannelDivider.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/leftpannel/LeftPannelDivider.kt similarity index 87% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/leftpannel/LeftPannelDivider.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/leftpannel/LeftPannelDivider.kt index 346e0f27d..94aa59acc 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/leftpannel/LeftPannelDivider.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/leftpannel/LeftPannelDivider.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.main.ui.view.leftpannel +package io.github.openflocon.flocondesktop.app.ui.view.leftpannel import androidx.compose.foundation.layout.padding import androidx.compose.material3.HorizontalDivider diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/leftpannel/LeftPannelView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/leftpannel/LeftPannelView.kt similarity index 64% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/leftpannel/LeftPannelView.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/leftpannel/LeftPannelView.kt index 0292d53a5..07e8246a7 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/leftpannel/LeftPannelView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/leftpannel/LeftPannelView.kt @@ -1,6 +1,6 @@ @file:Suppress("UnusedReceiverParameter") -package io.github.openflocon.flocondesktop.main.ui.view.leftpannel +package io.github.openflocon.flocondesktop.app.ui.view.leftpannel import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -15,16 +15,17 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember 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 androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEachIndexed -import io.github.openflocon.flocondesktop.main.ui.model.leftpanel.LeftPanelItem -import io.github.openflocon.flocondesktop.main.ui.model.leftpanel.LeftPanelState -import io.github.openflocon.flocondesktop.main.ui.model.leftpanel.LeftPannelSection -import io.github.openflocon.flocondesktop.main.ui.model.leftpanel.previewLeftPannelState +import io.github.openflocon.flocondesktop.app.ui.model.SubScreen +import io.github.openflocon.flocondesktop.app.ui.model.leftpanel.MenuItem +import io.github.openflocon.flocondesktop.app.ui.model.leftpanel.MenuState +import io.github.openflocon.flocondesktop.app.ui.model.leftpanel.MenuSection +import io.github.openflocon.flocondesktop.app.ui.model.leftpanel.previewMenuState import io.github.openflocon.library.designsystem.FloconTheme -import org.jetbrains.compose.ui.tooling.preview.Preview val PanelMaxWidth = 275.dp val PanelMinWidth = 64.dp @@ -32,18 +33,19 @@ val PanelContentMinSize = 40.dp @Composable fun LeftPanelView( - state: LeftPanelState, + state: MenuState, expanded: Boolean, - onClickItem: (LeftPanelItem) -> Unit, + onClickItem: (MenuItem) -> Unit, modifier: Modifier = Modifier, ) { Column( modifier = modifier - .background(FloconTheme.colorPalette.surface) - .padding(bottom = 16.dp, top = 8.dp) - .padding(horizontal = 12.dp), + .clip(FloconTheme.shapes.medium) + .background(FloconTheme.colorPalette.primary) + .padding(8.dp) ) { MenuSection( + current = state.current, items = state.sections, expanded = expanded, onClickItem = onClickItem, @@ -51,6 +53,7 @@ fun LeftPanelView( Spacer(modifier = Modifier.height(12.dp)) Spacer(Modifier.weight(1f)) MenuItems( + current = state.current, items = state.bottomItems, expanded = expanded, onClickItem = onClickItem, @@ -60,9 +63,10 @@ fun LeftPanelView( @Composable private fun ColumnScope.MenuSection( - items: List, + current: SubScreen, + items: List, expanded: Boolean, - onClickItem: (LeftPanelItem) -> Unit, + onClickItem: (MenuItem) -> Unit, ) { items.fastForEachIndexed { index, section -> PannelLabel( @@ -70,6 +74,7 @@ private fun ColumnScope.MenuSection( text = section.title, ) MenuItems( + current = current, items = section.items, expanded = expanded, onClickItem = onClickItem, @@ -79,11 +84,12 @@ private fun ColumnScope.MenuSection( @Composable private fun ColumnScope.MenuItems( - items: List, + current: SubScreen, + items: List, expanded: Boolean, - onClickItem: (LeftPanelItem) -> Unit, + onClickItem: (MenuItem) -> Unit, ) { - items.fastForEach { item -> + items.fastForEachIndexed { index, item -> PanelView( modifier = Modifier .height(PanelContentMinSize) @@ -91,25 +97,28 @@ private fun ColumnScope.MenuItems( icon = item.icon, text = item.text, expanded = expanded, - isSelected = item.isSelected, + isSelected = current == item, isEnabled = item.isEnabled, onClick = { onClickItem(item) }, ) + if (index != items.lastIndex) + Spacer(Modifier.height(4.dp)) } } @Composable @Preview private fun LeftPanelViewPreview() { - val selectedItem = remember { mutableStateOf(null) } + val selectedItem = remember { mutableStateOf(SubScreen.Network) } + FloconTheme { Box(modifier = Modifier.background(Color.White)) LeftPanelView( - state = previewLeftPannelState( - selectedId = selectedItem.value, + state = previewMenuState( + current = selectedItem.value, ), onClickItem = { - selectedItem.value = it.id +// selectedItem.value = it.screen }, modifier = Modifier.wrapContentHeight(), expanded = false, diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/leftpannel/PannelLabel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/leftpannel/PannelLabel.kt similarity index 95% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/leftpannel/PannelLabel.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/leftpannel/PannelLabel.kt index e4fd62df2..0575fbb49 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/leftpannel/PannelLabel.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/leftpannel/PannelLabel.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.main.ui.view.leftpannel +package io.github.openflocon.flocondesktop.app.ui.view.leftpannel import androidx.compose.animation.Crossfade import androidx.compose.foundation.layout.Box diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/leftpannel/PannelView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/leftpannel/PannelView.kt similarity index 86% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/leftpannel/PannelView.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/leftpannel/PannelView.kt index 38789d4f3..d6da7f90d 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/leftpannel/PannelView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/leftpannel/PannelView.kt @@ -1,11 +1,10 @@ @file:OptIn(ExperimentalSharedTransitionApi::class) -package io.github.openflocon.flocondesktop.main.ui.view.leftpannel +package io.github.openflocon.flocondesktop.app.ui.view.leftpannel import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn @@ -27,8 +26,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.draw.shadow +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.unit.dp @@ -48,17 +46,9 @@ fun PanelView( val interactionSource = remember { MutableInteractionSource() } val hovered by interactionSource.collectIsHoveredAsState() val shape = FloconTheme.shapes.medium - val shadow by animateDpAsState( - targetValue = when { - isSelected -> 6.dp - hovered -> 2.dp - else -> 0.dp - }, - label = "shadow", - ) val color by animateColorAsState( targetValue = when { - isSelected -> FloconTheme.colorPalette.primary + isSelected -> FloconTheme.colorPalette.accent hovered -> FloconTheme.colorPalette.secondary else -> FloconTheme.colorPalette.surface.copy(alpha = 0f) }, @@ -67,8 +57,8 @@ fun PanelView( val iconColor by animateColorAsState( targetValue = when { isSelected -> FloconTheme.colorPalette.onAccent - hovered -> FloconTheme.colorPalette.onSurface - else -> FloconTheme.colorPalette.onSurface + hovered -> FloconTheme.colorPalette.onSecondary + else -> FloconTheme.colorPalette.onPrimary } ) @@ -81,7 +71,7 @@ fun PanelView( Row( modifier = modifier .height(28.dp) - .shadow(elevation = shadow, shape = shape, clip = true, ambientColor = color, spotColor = color) + .clip(shape) .background(color) .graphicsLayer { alpha = lineAlpha diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/MainScreenTopBar.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/MainScreenTopBar.kt similarity index 75% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/MainScreenTopBar.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/MainScreenTopBar.kt index e517db650..9e367c9a1 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/MainScreenTopBar.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/MainScreenTopBar.kt @@ -1,40 +1,30 @@ -package io.github.openflocon.flocondesktop.main.ui.view.topbar +package io.github.openflocon.flocondesktop.app.ui.view.topbar import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row 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.outlined.CameraAlt -import androidx.compose.material.icons.outlined.Stop -import androidx.compose.material.icons.outlined.Videocam 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.alpha import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import flocondesktop.composeapp.generated.resources.Res import flocondesktop.composeapp.generated.resources.app_icon_small -import io.github.openflocon.flocondesktop.main.ui.model.AppsStateUiModel -import io.github.openflocon.flocondesktop.main.ui.model.DeviceAppUiModel -import io.github.openflocon.flocondesktop.main.ui.model.DeviceItemUiModel -import io.github.openflocon.flocondesktop.main.ui.model.DevicesStateUiModel -import io.github.openflocon.flocondesktop.main.ui.model.RecordVideoStateUiModel -import io.github.openflocon.flocondesktop.main.ui.view.topbar.actions.TopBarActions +import io.github.openflocon.flocondesktop.app.ui.model.AppsStateUiModel +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.DevicesStateUiModel +import io.github.openflocon.flocondesktop.app.ui.model.RecordVideoStateUiModel +import io.github.openflocon.flocondesktop.app.ui.view.topbar.actions.TopBarActions import io.github.openflocon.library.designsystem.FloconTheme import org.jetbrains.compose.resources.painterResource diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/TopBarDeviceAndAppView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/TopBarDeviceAndAppView.kt similarity index 52% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/TopBarDeviceAndAppView.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/TopBarDeviceAndAppView.kt index ce70b3d8a..5bcaa0bf0 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/TopBarDeviceAndAppView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/TopBarDeviceAndAppView.kt @@ -1,39 +1,22 @@ @file:OptIn(ExperimentalMaterial3Api::class) @file:Suppress("UnusedReceiverParameter") -package io.github.openflocon.flocondesktop.main.ui.view.topbar +package io.github.openflocon.flocondesktop.app.ui.view.topbar import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.CameraAlt import androidx.compose.material3.ExperimentalMaterial3Api -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.alpha -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import io.github.openflocon.flocondesktop.main.ui.model.AppsStateUiModel -import io.github.openflocon.flocondesktop.main.ui.model.DeviceAppUiModel -import io.github.openflocon.flocondesktop.main.ui.model.DeviceItemUiModel -import io.github.openflocon.flocondesktop.main.ui.model.DevicesStateUiModel -import io.github.openflocon.flocondesktop.main.ui.view.topbar.app.TopBarAppDropdown -import io.github.openflocon.flocondesktop.main.ui.view.topbar.device.TopBarDeviceDropdown -import io.github.openflocon.library.designsystem.FloconTheme +import io.github.openflocon.flocondesktop.app.ui.model.AppsStateUiModel +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.DevicesStateUiModel +import io.github.openflocon.flocondesktop.app.ui.view.topbar.app.TopBarAppDropdown +import io.github.openflocon.flocondesktop.app.ui.view.topbar.device.TopBarDeviceDropdown @Composable internal fun TopBarDeviceAndAppView( diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/TopBarSelector.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/TopBarSelector.kt similarity index 96% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/TopBarSelector.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/TopBarSelector.kt index f99d9b04e..17d8ddbde 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/TopBarSelector.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/TopBarSelector.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.main.ui.view.topbar +package io.github.openflocon.flocondesktop.app.ui.view.topbar import androidx.compose.foundation.Image diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/actions/TopBarActions.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/actions/TopBarActions.kt similarity index 88% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/actions/TopBarActions.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/actions/TopBarActions.kt index 9967d3059..e91151a6f 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/actions/TopBarActions.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/actions/TopBarActions.kt @@ -1,10 +1,9 @@ -package io.github.openflocon.flocondesktop.main.ui.view.topbar.actions +package io.github.openflocon.flocondesktop.app.ui.view.topbar.actions import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.CameraAlt -import androidx.compose.material.icons.outlined.PlayCircle import androidx.compose.material.icons.outlined.RestartAlt import androidx.compose.material.icons.outlined.StopCircle import androidx.compose.material.icons.outlined.Videocam @@ -12,8 +11,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import io.github.openflocon.flocondesktop.main.ui.model.DevicesStateUiModel -import io.github.openflocon.flocondesktop.main.ui.model.RecordVideoStateUiModel +import io.github.openflocon.flocondesktop.app.ui.model.DevicesStateUiModel +import io.github.openflocon.flocondesktop.app.ui.model.RecordVideoStateUiModel @Composable internal fun TopBarActions( diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/actions/TopBarButton.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/actions/TopBarButton.kt similarity index 92% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/actions/TopBarButton.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/actions/TopBarButton.kt index ef3ab8381..30f1f4080 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/actions/TopBarButton.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/actions/TopBarButton.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.main.ui.view.topbar.actions +package io.github.openflocon.flocondesktop.app.ui.view.topbar.actions import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/app/TopBarAppDropdown.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/app/TopBarAppDropdown.kt similarity index 90% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/app/TopBarAppDropdown.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/app/TopBarAppDropdown.kt index a3113e525..ecbe4f7e1 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/app/TopBarAppDropdown.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/app/TopBarAppDropdown.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalMaterial3Api::class) -package io.github.openflocon.flocondesktop.main.ui.view.topbar.app +package io.github.openflocon.flocondesktop.app.ui.view.topbar.app import androidx.compose.foundation.clickable @@ -14,14 +14,13 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.util.fastForEach -import io.github.openflocon.flocondesktop.main.ui.model.AppsStateUiModel -import io.github.openflocon.flocondesktop.main.ui.model.DeviceAppUiModel -import io.github.openflocon.flocondesktop.main.ui.model.DevicesStateUiModel -import io.github.openflocon.flocondesktop.main.ui.view.topbar.TopBarSelector +import io.github.openflocon.flocondesktop.app.ui.model.AppsStateUiModel +import io.github.openflocon.flocondesktop.app.ui.model.DeviceAppUiModel +import io.github.openflocon.flocondesktop.app.ui.model.DevicesStateUiModel +import io.github.openflocon.flocondesktop.app.ui.view.topbar.TopBarSelector import io.github.openflocon.library.designsystem.components.FloconExposedDropdownMenu import io.github.openflocon.library.designsystem.components.FloconExposedDropdownMenuBox - @Composable internal fun TopBarAppDropdown( devicesState: DevicesStateUiModel, diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/app/TopBarAppView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/app/TopBarAppView.kt similarity index 93% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/app/TopBarAppView.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/app/TopBarAppView.kt index 237bb3a36..ace09ee17 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/app/TopBarAppView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/app/TopBarAppView.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.main.ui.view.topbar.app +package io.github.openflocon.flocondesktop.app.ui.view.topbar.app import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -12,8 +12,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Code -import androidx.compose.material.icons.filled.SmartToy import androidx.compose.material.icons.filled.Terminal import androidx.compose.material.icons.outlined.Close import androidx.compose.material3.Text @@ -31,8 +29,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import flocondesktop.composeapp.generated.resources.Res import flocondesktop.composeapp.generated.resources.smartphone -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 import io.github.openflocon.library.designsystem.FloconTheme import io.github.openflocon.library.designsystem.components.FloconIcon import org.jetbrains.compose.resources.painterResource @@ -118,7 +116,7 @@ private fun AppImage( modifier = modifier, ) } else { - when(platform) { + when (platform) { DeviceItemUiModel.Platform.Desktop -> { Image( imageVector = Icons.Default.Terminal, @@ -127,6 +125,7 @@ private fun AppImage( modifier = modifier, ) } + DeviceItemUiModel.Platform.ios, DeviceItemUiModel.Platform.Android, DeviceItemUiModel.Platform.Unknown -> { diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/device/TopBarDeviceDropdown.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/device/TopBarDeviceDropdown.kt similarity index 89% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/device/TopBarDeviceDropdown.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/device/TopBarDeviceDropdown.kt index 8ad0a26c6..11368a13b 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/device/TopBarDeviceDropdown.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/device/TopBarDeviceDropdown.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalMaterial3Api::class) -package io.github.openflocon.flocondesktop.main.ui.view.topbar.device +package io.github.openflocon.flocondesktop.app.ui.view.topbar.device import androidx.compose.foundation.layout.padding @@ -16,9 +16,9 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import io.github.openflocon.flocondesktop.main.ui.model.DeviceItemUiModel -import io.github.openflocon.flocondesktop.main.ui.model.DevicesStateUiModel -import io.github.openflocon.flocondesktop.main.ui.view.topbar.TopBarSelector +import io.github.openflocon.flocondesktop.app.ui.model.DeviceItemUiModel +import io.github.openflocon.flocondesktop.app.ui.model.DevicesStateUiModel +import io.github.openflocon.flocondesktop.app.ui.view.topbar.TopBarSelector import io.github.openflocon.library.designsystem.FloconTheme import io.github.openflocon.library.designsystem.components.FloconCircularProgressIndicator @@ -41,7 +41,7 @@ internal fun TopBarDeviceDropdown( when (state) { DevicesStateUiModel.Empty -> Empty() DevicesStateUiModel.Loading -> Loading() - is DevicesStateUiModel.WithDevices -> TopBarSelector( + is DevicesStateUiModel.WithDevices -> TopBarSelector( onClick = { expanded = true }, diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/device/TopBarDeviceView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/device/TopBarDeviceView.kt similarity index 96% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/device/TopBarDeviceView.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/device/TopBarDeviceView.kt index 5e2b3d10f..5e8266713 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/view/topbar/device/TopBarDeviceView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/app/ui/view/topbar/device/TopBarDeviceView.kt @@ -1,4 +1,4 @@ -package io.github.openflocon.flocondesktop.main.ui.view.topbar.device +package io.github.openflocon.flocondesktop.app.ui.view.topbar.device import androidx.compose.desktop.ui.tooling.preview.Preview @@ -22,7 +22,6 @@ 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.MobileOff -import androidx.compose.material.icons.outlined.PhoneIphone import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -34,7 +33,7 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import io.github.openflocon.flocondesktop.main.ui.model.DeviceItemUiModel +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.FloconSurface @@ -68,22 +67,25 @@ internal fun TopBarDeviceView( ) { Image( modifier = Modifier.width(20.dp), - imageVector = when(device.platform) { + imageVector = when (device.platform) { DeviceItemUiModel.Platform.Android -> if (device.isActive.not()) { Icons.Filled.MobileOff } else { Icons.Filled.Smartphone } + DeviceItemUiModel.Platform.Desktop -> if (device.isActive.not()) { Icons.Filled.DesktopAccessDisabled } else { Icons.Filled.DesktopWindows } + DeviceItemUiModel.Platform.ios -> if (device.isActive.not()) { Icons.Outlined.MobileOff } else { Icons.Filled.PhoneIphone } + DeviceItemUiModel.Platform.Unknown -> if (device.isActive.not()) { Icons.Filled.MobileOff } else { diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/common/utils/ClosableScopeExt.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/common/utils/ClosableScopeExt.kt new file mode 100644 index 000000000..1fcf5739d --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/common/utils/ClosableScopeExt.kt @@ -0,0 +1,16 @@ +package io.github.openflocon.flocondesktop.common.utils + +import io.github.openflocon.flocondesktop.common.coroutines.closeable.CloseableScoped +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn + +context(closableScope: CloseableScoped) +fun Flow.stateInWhileSubscribed(default: T): StateFlow { + return stateIn( + scope = closableScope.coroutineScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = default + ) +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/common/utils/ViewModelExt.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/common/utils/ViewModelExt.kt new file mode 100644 index 000000000..d3d5ce98b --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/common/utils/ViewModelExt.kt @@ -0,0 +1,18 @@ +package io.github.openflocon.flocondesktop.common.utils + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import io.github.openflocon.flocondesktop.common.coroutines.closeable.CloseableScoped +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn + +context(viewModel: ViewModel) +fun Flow.stateInWhileSubscribed(default: T): StateFlow { + return stateIn( + scope = viewModel.viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = default + ) +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/DI.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/DI.kt new file mode 100644 index 000000000..0a0122555 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/DI.kt @@ -0,0 +1,11 @@ +package io.github.openflocon.flocondesktop.core.data.settings + +import io.github.openflocon.flocondesktop.core.data.settings.usecase.ObserveNetworkSettingsUseCase +import io.github.openflocon.flocondesktop.core.data.settings.usecase.SaveNetworkSettingsUseCase +import org.koin.core.module.dsl.factoryOf +import org.koin.dsl.module + +internal val settingsModule = module { + factoryOf(::ObserveNetworkSettingsUseCase) + factoryOf(::SaveNetworkSettingsUseCase) +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/SettingsRepositoryImpl.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/SettingsRepositoryImpl.kt index e77038fd8..eff8addc0 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/SettingsRepositoryImpl.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/SettingsRepositoryImpl.kt @@ -1,17 +1,27 @@ package io.github.openflocon.flocondesktop.core.data.settings +import io.github.openflocon.domain.models.settings.NetworkSettings import io.github.openflocon.domain.settings.repository.SettingsRepository import io.github.openflocon.flocondesktop.core.data.settings.datasource.local.SettingsDataSource +import io.github.openflocon.flocondesktop.core.data.settings.models.toDomain +import io.github.openflocon.flocondesktop.core.data.settings.models.toLocal import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.mapLatest -class SettingsRepositoryImpl( +internal class SettingsRepositoryImpl( private val localSettingsDataSource: SettingsDataSource, ) : SettingsRepository { override val adbPath: Flow = localSettingsDataSource.adbPath override val fontSizeMultiplier: StateFlow = localSettingsDataSource.fontSizeMultiplier + override var networkSettings: NetworkSettings + get() = localSettingsDataSource.networkSettings.toDomain() + set(value) { localSettingsDataSource.networkSettings = value.toLocal() } + override val networkSettingsFlow: Flow = localSettingsDataSource.networkSettingsFlow + .mapLatest { it.toDomain() } + override fun getAdbPath(): String? = localSettingsDataSource.getAdbPath() override suspend fun setAdbPath(path: String) { diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/datasource/local/SettingsDataSource.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/datasource/local/SettingsDataSource.kt index c165ab00d..8401218b0 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/datasource/local/SettingsDataSource.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/datasource/local/SettingsDataSource.kt @@ -1,9 +1,13 @@ package io.github.openflocon.flocondesktop.core.data.settings.datasource.local +import io.github.openflocon.flocondesktop.core.data.settings.models.NetworkSettingsLocal import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow -interface SettingsDataSource { +internal interface SettingsDataSource { + var networkSettings: NetworkSettingsLocal + val networkSettingsFlow: StateFlow + fun getAdbPath(): String? suspend fun setAdbPath(path: String) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/datasource/local/SettingsDataSourcePrefs.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/datasource/local/SettingsDataSourcePrefs.kt index bb76e31c7..e4b2e26ed 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/datasource/local/SettingsDataSourcePrefs.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/datasource/local/SettingsDataSourcePrefs.kt @@ -1,35 +1,50 @@ -@file:OptIn(ExperimentalSettingsApi::class) +@file:OptIn(ExperimentalSettingsApi::class, ExperimentalSerializationApi::class) package io.github.openflocon.flocondesktop.core.data.settings.datasource.local import com.russhwolf.settings.ExperimentalSettingsApi import com.russhwolf.settings.ObservableSettings import com.russhwolf.settings.coroutines.toFlowSettings +import io.github.openflocon.flocondesktop.core.data.settings.models.NetworkSettingsLocal import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.json.Json +import kotlinx.serialization.serializer +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty expect fun createSettings(): ObservableSettings -class SettingsDataSourcePrefs( - applicationScope: CoroutineScope +internal class SettingsDataSourcePrefs( + private val applicationScope: CoroutineScope ) : SettingsDataSource { + + private val json = Json { + ignoreUnknownKeys = true + } + private val settings = createSettings() private val flowSettings = settings.toFlowSettings() + override var networkSettings: NetworkSettingsLocal by NETWORK_SETTINGS.classDelegateOf(NetworkSettingsLocal()) + override val networkSettingsFlow = flowSettings.getStringOrNullFlow(NETWORK_SETTINGS) + .filterNotNull() + .mapLatest { json.decodeFromString(it) } + .stateIn(networkSettings) + override val adbPath: Flow = flowSettings.getStringOrNullFlow(ADB_PATH) override val fontSizeMultiplier: StateFlow = settings.toFlowSettings() .getFloatOrNullFlow(FONT_SIZE_MULTIPLIER) .filterNotNull() - .stateIn( - scope = applicationScope, - started = SharingStarted.Lazily, - initialValue = 1f - ) + .stateIn(1f) override fun getAdbPath(): String? = settings.getStringOrNull(ADB_PATH) @@ -41,8 +56,44 @@ class SettingsDataSourcePrefs( settings.putFloat(FONT_SIZE_MULTIPLIER, value) } + private fun Flow.stateIn(default: T) = stateIn( + scope = applicationScope, + started = SharingStarted.Lazily, + initialValue = default + ) + + private inner class ClassDelegate( + private val key: String, + private val default: T, + private val serializer: KSerializer + ) : ReadWriteProperty { + + override fun getValue(thisRef: SettingsDataSourcePrefs, property: KProperty<*>): T { + return thisRef.settings.getStringOrNull(key) + ?.let { json.decodeFromString(serializer, it) } + ?: default + } + + override fun setValue(thisRef: SettingsDataSourcePrefs, property: KProperty<*>, value: T?) { + thisRef.settings + .putString(key = key, value = json.encodeToString(serializer, value ?: return)) + } + + } + + private inline fun String.classDelegateOf(default: T): ClassDelegate { + return ClassDelegate( + key = this, + default = default, + serializer = serializer() + ) + } + companion object { private const val ADB_PATH = "adb_path" private const val FONT_SIZE_MULTIPLIER = "font_size_multiplier" + + private const val NETWORK_SETTINGS = "network_settings" } + } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/models/NetworkSettingsLocal.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/models/NetworkSettingsLocal.kt new file mode 100644 index 000000000..632ba204e --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/models/NetworkSettingsLocal.kt @@ -0,0 +1,36 @@ +package io.github.openflocon.flocondesktop.core.data.settings.models + +import io.github.openflocon.domain.models.settings.NetworkSettings +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +internal data class NetworkSettingsLocal( + + @SerialName("pinned_detail") + val pinnedDetails: Boolean = false, + + @SerialName("display_old_sessions") + val displayOldSessions: Boolean = false, + + @SerialName("auto_scroll") + val autoScroll: Boolean = false, + + @SerialName("invert_list") + val invertList: Boolean = false + +) + +internal fun NetworkSettingsLocal.toDomain() = NetworkSettings( + pinnedDetails = pinnedDetails, + displayOldSessions = displayOldSessions, + autoScroll = autoScroll, + invertList = invertList +) + +internal fun NetworkSettings.toLocal() = NetworkSettingsLocal( + pinnedDetails = pinnedDetails, + displayOldSessions = displayOldSessions, + autoScroll = autoScroll, + invertList = invertList +) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/usecase/ObserveNetworkSettingsUseCase.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/usecase/ObserveNetworkSettingsUseCase.kt new file mode 100644 index 000000000..052e577c9 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/usecase/ObserveNetworkSettingsUseCase.kt @@ -0,0 +1,13 @@ +package io.github.openflocon.flocondesktop.core.data.settings.usecase + +import io.github.openflocon.domain.models.settings.NetworkSettings +import io.github.openflocon.domain.settings.repository.SettingsRepository +import kotlinx.coroutines.flow.Flow + +class ObserveNetworkSettingsUseCase( + private val settingsRepository: SettingsRepository +) { + + operator fun invoke(): Flow = settingsRepository.networkSettingsFlow + +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/usecase/SaveNetworkSettingsUseCase.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/usecase/SaveNetworkSettingsUseCase.kt new file mode 100644 index 000000000..c220e9e2c --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/data/settings/usecase/SaveNetworkSettingsUseCase.kt @@ -0,0 +1,15 @@ +package io.github.openflocon.flocondesktop.core.data.settings.usecase + +import io.github.openflocon.domain.models.settings.NetworkSettings +import io.github.openflocon.domain.settings.repository.SettingsRepository + +class SaveNetworkSettingsUseCase( + private val settingsRepository: SettingsRepository +) { + operator fun invoke( + settings: NetworkSettings + ): Result = runCatching { + settingsRepository.networkSettings = settings + } + +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/di/CoreModule.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/di/CoreModule.kt index 215f4f30b..354d945ea 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/di/CoreModule.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/core/di/CoreModule.kt @@ -1,10 +1,13 @@ package io.github.openflocon.flocondesktop.core.di import io.github.openflocon.flocondesktop.core.data.di.coreDataModule +import io.github.openflocon.flocondesktop.core.data.settings.settingsModule import org.koin.dsl.module val coreModule = module { includes( coreDataModule, + + settingsModule ) } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/FeaturesModule.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/FeaturesModule.kt index 525d7bb91..68a8a8d6a 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/FeaturesModule.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/FeaturesModule.kt @@ -1,5 +1,6 @@ package io.github.openflocon.flocondesktop.features +import io.github.openflocon.flocondesktop.app.ui.settings.settingsModule import io.github.openflocon.flocondesktop.features.analytics.analyticsModule import io.github.openflocon.flocondesktop.features.dashboard.dashboardModule import io.github.openflocon.flocondesktop.features.database.databaseModule @@ -24,5 +25,6 @@ val featuresModule = module { dashboardModule, tableModule, deeplinkModule, + settingsModule ) } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/AnalyticsDetailViewModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/AnalyticsDetailViewModel.kt new file mode 100644 index 000000000..5cfa921c0 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/AnalyticsDetailViewModel.kt @@ -0,0 +1,34 @@ +package io.github.openflocon.flocondesktop.features.analytics + +import androidx.lifecycle.ViewModel +import io.github.openflocon.domain.analytics.usecase.ObserveAnalyticsByIdUseCase +import io.github.openflocon.domain.device.usecase.ObserveCurrentDeviceIdAndPackageNameUseCase +import io.github.openflocon.flocondesktop.common.utils.stateInWhileSubscribed +import io.github.openflocon.flocondesktop.features.analytics.mapper.mapToDetailUi +import io.github.openflocon.flocondesktop.features.analytics.model.AnalyticsDetailUiModel +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterNotNull + +class AnalyticsDetailViewModel( + id: String, + observeCurrentDeviceIdAndPackageNameUseCase: ObserveCurrentDeviceIdAndPackageNameUseCase, + observeAnalyticsByIdUseCase: ObserveAnalyticsByIdUseCase +) : ViewModel() { + + val uiState = combine( + observeAnalyticsByIdUseCase(id).filterNotNull(), + observeCurrentDeviceIdAndPackageNameUseCase() + ) { analytics, current -> + analytics.mapToDetailUi(current) + } + .stateInWhileSubscribed( + default = AnalyticsDetailUiModel( + id = id, + dateFormatted = "", + eventName = "", + isFromOldAppInstance = false, + properties = emptyList() + ) + ) + +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/AnalyticsViewModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/AnalyticsViewModel.kt index 17b644ae2..c6cb61774 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/AnalyticsViewModel.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/AnalyticsViewModel.kt @@ -29,6 +29,7 @@ import io.github.openflocon.flocondesktop.features.analytics.model.AnalyticsScre import io.github.openflocon.flocondesktop.features.analytics.model.AnalyticsStateUiModel import io.github.openflocon.flocondesktop.features.analytics.model.DeviceAnalyticsUiModel import io.github.openflocon.library.designsystem.common.asState +import io.github.openflocon.navigation.MainFloconNavigationState import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -38,7 +39,6 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -47,7 +47,7 @@ class AnalyticsViewModel( private val dispatcherProvider: DispatcherProvider, private val feedbackDisplayer: FeedbackDisplayer, private val analyticsSelectorDelegate: AnalyticsSelectorDelegate, - observeCurrentDeviceIdAndPackageNameUseCase: ObserveCurrentDeviceIdAndPackageNameUseCase, + private val observeCurrentDeviceIdAndPackageNameUseCase: ObserveCurrentDeviceIdAndPackageNameUseCase, observeCurrentDeviceAnalyticsContentUseCase: ObserveCurrentDeviceAnalyticsContentUseCase, private val resetCurrentDeviceSelectedAnalyticsUseCase: ResetCurrentDeviceSelectedAnalyticsUseCase, private val removeAnalyticsItemUseCase: RemoveAnalyticsItemUseCase, @@ -55,10 +55,11 @@ class AnalyticsViewModel( private val removeOldSessionsAnalyticsUseCase: RemoveOldSessionsAnalyticsUseCase, private val exportAnalyticsToCsv: ExportAnalyticsToCsvUseCase, private val observeAnalyticsByIdUseCase: ObserveAnalyticsByIdUseCase, + private val navigationState: MainFloconNavigationState ) : ViewModel() { private val _filterText = mutableStateOf("") - val filterText : State = _filterText.asState() + val filterText: State = _filterText.asState() private val _screenState = MutableStateFlow( AnalyticsScreenUiState( @@ -140,14 +141,7 @@ class AnalyticsViewModel( } is AnalyticsAction.OnClick -> { - val newId = action.item.id - _selectedItemId.update { - if (it == newId) { - null - } else { - newId - } - } + navigationState.navigate(AnalyticsRoutes.Detail(action.item.id)) } is AnalyticsAction.Remove -> removeAnalyticsItemUseCase(action.item.id) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/DI.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/DI.kt index 4198bec42..cb76de0f7 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/DI.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/DI.kt @@ -7,5 +7,6 @@ import org.koin.dsl.module val analyticsModule = module { viewModelOf(::AnalyticsViewModel) + viewModelOf(::AnalyticsDetailViewModel) factoryOf(::AnalyticsSelectorDelegate) } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/Navigation.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/Navigation.kt new file mode 100644 index 000000000..5d358f6b7 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/Navigation.kt @@ -0,0 +1,40 @@ +package io.github.openflocon.flocondesktop.features.analytics + +import androidx.navigation3.runtime.EntryProviderScope +import io.github.openflocon.flocondesktop.app.MenuSceneStrategy +import io.github.openflocon.flocondesktop.features.analytics.view.AnalyticsDetailView +import io.github.openflocon.flocondesktop.features.analytics.view.AnalyticsScreen +import io.github.openflocon.navigation.FloconRoute +import io.github.openflocon.navigation.PanelRoute +import io.github.openflocon.navigation.scene.PanelSceneStrategy +import kotlinx.serialization.Serializable + +sealed interface AnalyticsRoutes : FloconRoute { + + @Serializable + data object Main : AnalyticsRoutes + + @Serializable + data class Detail( + val id: String + ) : AnalyticsRoutes, PanelRoute + +} + +fun EntryProviderScope.analyticsRoutes() { + entry( + metadata = MenuSceneStrategy.menu() + ) { + AnalyticsScreen() + } + entry( + metadata = PanelSceneStrategy.panel( + pinnable = false, + closable = true + ) + ) { + AnalyticsDetailView( + id = it.id + ) + } +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsDetailView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsDetailView.kt index f9df2787c..b676915ad 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsDetailView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsDetailView.kt @@ -19,31 +19,47 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Text import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable +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.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import io.github.openflocon.flocondesktop.features.analytics.AnalyticsDetailViewModel import io.github.openflocon.flocondesktop.features.analytics.model.AnalyticsDetailUiModel -import io.github.openflocon.flocondesktop.features.analytics.model.AnalyticsRowUiModel import io.github.openflocon.library.designsystem.FloconTheme import io.github.openflocon.library.designsystem.components.FloconHorizontalDivider import io.github.openflocon.library.designsystem.components.FloconVerticalScrollbar import io.github.openflocon.library.designsystem.components.rememberFloconScrollbarAdapter +import org.koin.compose.viewmodel.koinViewModel +import org.koin.core.parameter.parametersOf @Composable fun AnalyticsDetailView( - state: AnalyticsDetailUiModel, - modifier: Modifier = Modifier + id: String +) { + val viewModel = koinViewModel { + parametersOf(id) + } + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + Content( + uiState = uiState + ) +} + +@Composable +private fun Content( + uiState: AnalyticsDetailUiModel ) { val scrollState = rememberScrollState() val linesLabelWidth: Dp = 130.dp val scrollAdapter = rememberFloconScrollbarAdapter(scrollState) Box( - modifier - .background(FloconTheme.colorPalette.primary) + Modifier.background(FloconTheme.colorPalette.primary) ) { SelectionContainer( modifier = Modifier.fillMaxSize() @@ -56,14 +72,14 @@ fun AnalyticsDetailView( AnalyticsDetailLineTextView( modifier = Modifier.fillMaxWidth(), label = "Name", - value = state.eventName, + value = uiState.eventName, labelWidth = linesLabelWidth, withDivider = false, ) AnalyticsDetailLineTextView( modifier = Modifier.fillMaxWidth(), label = "Time", - value = state.dateFormatted, + value = uiState.dateFormatted, labelWidth = linesLabelWidth, withDivider = false, ) @@ -77,7 +93,7 @@ fun AnalyticsDetailView( shape = FloconTheme.shapes.medium ) ) { - state.properties.forEachIndexed { index, property -> + uiState.properties.forEachIndexed { index, property -> AnalyticsDetailLineTextView( modifier = Modifier.fillMaxWidth(), label = property.name, @@ -85,7 +101,7 @@ fun AnalyticsDetailView( labelWidth = linesLabelWidth, withDivider = true, ) - if (index != state.properties.lastIndex) { + if (index != uiState.properties.lastIndex) { FloconHorizontalDivider( modifier = Modifier.fillMaxWidth(), color = FloconTheme.colorPalette.secondary diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsScreen.kt index 0b2399959..55c9349bb 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsScreen.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/analytics/view/AnalyticsScreen.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons @@ -56,7 +55,6 @@ import io.github.openflocon.library.designsystem.components.FloconFeature import io.github.openflocon.library.designsystem.components.FloconOverflow import io.github.openflocon.library.designsystem.components.FloconPageTopBar import io.github.openflocon.library.designsystem.components.FloconVerticalScrollbar -import io.github.openflocon.library.designsystem.components.panel.FloconPanel import io.github.openflocon.library.designsystem.components.rememberFloconScrollbarAdapter import kotlinx.coroutines.flow.flowOf import org.jetbrains.compose.ui.tooling.preview.Preview @@ -232,26 +230,21 @@ fun AnalyticsScreen( ) } } - FloconPanel( - contentState = selectedItem, - onClose = { onAction(AnalyticsAction.ClosePanel) }, - ) { - AnalyticsDetailView( - modifier = Modifier.fillMaxSize(), - state = it - ) - } } @Composable @Preview private fun AnalyticsScreenPreview() { val rows = remember { - flowOf(PagingData.from(listOf( - previewAnalyticsRowUiModel(), - previewAnalyticsRowUiModel(), - previewAnalyticsRowUiModel(), - ))) + flowOf( + PagingData.from( + listOf( + previewAnalyticsRowUiModel(), + previewAnalyticsRowUiModel(), + previewAnalyticsRowUiModel(), + ) + ) + ) }.collectAsLazyPagingItems() FloconTheme { AnalyticsScreen( diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/DashboardModule.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/DI.kt similarity index 100% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/DashboardModule.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/DI.kt diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/Navigation.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/Navigation.kt new file mode 100644 index 000000000..f911e81af --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/dashboard/Navigation.kt @@ -0,0 +1,22 @@ +package io.github.openflocon.flocondesktop.features.dashboard + +import androidx.navigation3.runtime.EntryProviderScope +import io.github.openflocon.flocondesktop.app.MenuSceneStrategy +import io.github.openflocon.flocondesktop.features.dashboard.view.DashboardScreen +import io.github.openflocon.navigation.FloconRoute +import kotlinx.serialization.Serializable + +sealed interface DashboardRoutes : FloconRoute { + + @Serializable + data object Main : DashboardRoutes + +} + +fun EntryProviderScope.dashboardRoutes() { + entry( + metadata = MenuSceneStrategy.menu() + ) { + DashboardScreen() + } +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/database/Navigation.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/database/Navigation.kt new file mode 100644 index 000000000..40da4ceb7 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/database/Navigation.kt @@ -0,0 +1,22 @@ +package io.github.openflocon.flocondesktop.features.database + +import androidx.navigation3.runtime.EntryProviderScope +import io.github.openflocon.flocondesktop.app.MenuSceneStrategy +import io.github.openflocon.flocondesktop.features.database.view.DatabaseScreen +import io.github.openflocon.navigation.FloconRoute +import kotlinx.serialization.Serializable + +sealed interface DatabaseRoutes : FloconRoute { + + @Serializable + data object Main : DatabaseRoutes + +} + +fun EntryProviderScope.databaseRoutes() { + entry( + metadata = MenuSceneStrategy.menu() + ) { + DatabaseScreen() + } +} 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 e6e903d56..28df12e0f 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 @@ -20,13 +20,7 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.CallMade import androidx.compose.material.icons.automirrored.outlined.DriveFileMove -import androidx.compose.material.icons.filled.FileUpload -import androidx.compose.material.icons.outlined.Download -import androidx.compose.material.icons.outlined.ImportExport -import androidx.compose.material.icons.outlined.Save -import androidx.compose.material.icons.outlined.SaveAs import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -200,7 +194,7 @@ fun DatabaseResultView( } ) { DatabaseRowDetailView( - modifier = Modifier.matchParentSize(), + modifier = Modifier.fillMaxSize(), state = it, columns = result.columns, ) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/deeplinks/Navigation.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/deeplinks/Navigation.kt new file mode 100644 index 000000000..2ecef9cdf --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/deeplinks/Navigation.kt @@ -0,0 +1,22 @@ +package io.github.openflocon.flocondesktop.features.deeplinks + +import androidx.navigation3.runtime.EntryProviderScope +import io.github.openflocon.flocondesktop.app.MenuSceneStrategy +import io.github.openflocon.flocondesktop.features.deeplinks.view.DeeplinkScreen +import io.github.openflocon.navigation.FloconRoute +import kotlinx.serialization.Serializable + +sealed interface DeeplinkRoutes : FloconRoute { + + @Serializable + data object Main : DeeplinkRoutes + +} + +fun EntryProviderScope.deeplinkRoutes() { + entry( + metadata = MenuSceneStrategy.menu() + ) { + DeeplinkScreen() + } +} 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 96c998a02..6b382cc34 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 @@ -17,8 +17,8 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Send import androidx.compose.material.icons.filled.Delete import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuAnchorType.Companion.PrimaryEditable import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.MenuAnchorType.Companion.PrimaryEditable import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/Navigation.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/Navigation.kt new file mode 100644 index 000000000..fa9b628aa --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/files/Navigation.kt @@ -0,0 +1,22 @@ +package io.github.openflocon.flocondesktop.features.files + +import androidx.navigation3.runtime.EntryProviderScope +import io.github.openflocon.flocondesktop.app.MenuSceneStrategy +import io.github.openflocon.flocondesktop.features.files.view.FilesScreen +import io.github.openflocon.navigation.FloconRoute +import kotlinx.serialization.Serializable + +sealed interface FilesRoutes : FloconRoute { + + @Serializable + data object Main : FilesRoutes + +} + +fun EntryProviderScope.filesRoutes() { + entry( + metadata = MenuSceneStrategy.menu() + ) { + FilesScreen() + } +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/images/Navigation.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/images/Navigation.kt new file mode 100644 index 000000000..f5d1a7268 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/images/Navigation.kt @@ -0,0 +1,22 @@ +package io.github.openflocon.flocondesktop.features.images + +import androidx.navigation3.runtime.EntryProviderScope +import io.github.openflocon.flocondesktop.app.MenuSceneStrategy +import io.github.openflocon.flocondesktop.features.images.view.ImagesScreen +import io.github.openflocon.navigation.FloconRoute +import kotlinx.serialization.Serializable + +sealed interface ImageRoutes : FloconRoute { + + @Serializable + data object Main : ImageRoutes + +} + +fun EntryProviderScope.imageRoutes() { + entry( + metadata = MenuSceneStrategy.menu() + ) { + ImagesScreen() + } +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/NetworkUiModule.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/DI.kt similarity index 84% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/NetworkUiModule.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/DI.kt index c4ef7de9e..aa599d8ea 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/NetworkUiModule.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/DI.kt @@ -1,6 +1,8 @@ package io.github.openflocon.flocondesktop.features.network import io.github.openflocon.flocondesktop.features.network.badquality.BadQualityNetworkViewModel +import io.github.openflocon.flocondesktop.features.network.detail.NetworkDetailDelegate +import io.github.openflocon.flocondesktop.features.network.detail.NetworkDetailViewModel import io.github.openflocon.flocondesktop.features.network.list.NetworkViewModel import io.github.openflocon.flocondesktop.features.network.list.delegate.HeaderDelegate import io.github.openflocon.flocondesktop.features.network.list.delegate.OpenBodyDelegate @@ -15,9 +17,12 @@ import org.koin.dsl.module internal val networkModule = module { viewModelOf(::NetworkViewModel) + viewModelOf(::NetworkDetailViewModel) + factoryOf(::MessagesServerDelegate) factoryOf(::HeaderDelegate) factoryOf(::OpenBodyDelegate) + factoryOf(::NetworkDetailDelegate) viewModelOf(::NetworkMocksViewModel) factoryOf(::ExportMocksProcessor) @@ -25,4 +30,5 @@ internal val networkModule = module { viewModelOf(::BadQualityNetworkViewModel) viewModelOf(::NetworkWebsocketMockViewModel) + } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/Navigation.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/Navigation.kt new file mode 100644 index 000000000..14bd458a1 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/Navigation.kt @@ -0,0 +1,55 @@ +package io.github.openflocon.flocondesktop.features.network + +import androidx.navigation3.runtime.EntryProviderScope +import androidx.navigation3.scene.DialogSceneStrategy +import io.github.openflocon.domain.settings.repository.SettingsRepository +import io.github.openflocon.flocondesktop.app.MenuSceneStrategy +import io.github.openflocon.flocondesktop.features.network.detail.view.NetworkDetailScreen +import io.github.openflocon.flocondesktop.features.network.list.view.NetworkScreen +import io.github.openflocon.flocondesktop.features.network.mock.list.view.NetworkMocksWindow +import io.github.openflocon.navigation.FloconRoute +import io.github.openflocon.navigation.PanelRoute +import io.github.openflocon.navigation.scene.PanelSceneStrategy +import kotlinx.serialization.Serializable +import org.koin.mp.KoinPlatform + +internal sealed interface NetworkRoutes : FloconRoute { + + @Serializable + data object Main : NetworkRoutes + + @Serializable + data class Mocks(val id: String?) : NetworkRoutes + + @Serializable + data class Panel(val requestId: String) : NetworkRoutes, PanelRoute + +} + +fun EntryProviderScope.networkRoutes() { + entry( + metadata = MenuSceneStrategy.menu() + ) { + NetworkScreen() + } + entry( + metadata = PanelSceneStrategy.panel( + pinnable = true, + closable = true, + onPin = { + val repository = KoinPlatform.getKoin().get() + + repository.networkSettings = repository.networkSettings.copy(pinnedDetails = true) + } + ) + ) { + NetworkDetailScreen(requestId = it.requestId) + } + entry( + metadata = DialogSceneStrategy.dialog() + ) { + NetworkMocksWindow( + fromNetworkCallId = it.id + ) + } +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/list/view/BadNetworkQualityWindow.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/list/view/BadNetworkQualityWindow.kt index 3d2179510..c716af155 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/list/view/BadNetworkQualityWindow.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/list/view/BadNetworkQualityWindow.kt @@ -2,7 +2,6 @@ package io.github.openflocon.flocondesktop.features.network.badquality.list.view -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/list/view/NetworkBadQualityContent.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/list/view/NetworkBadQualityContent.kt index 37f8ba0b1..4857d65f3 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/list/view/NetworkBadQualityContent.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/list/view/NetworkBadQualityContent.kt @@ -3,7 +3,6 @@ package io.github.openflocon.flocondesktop.features.network.badquality.list.view import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.Text diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/body/model/ContentUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/body/model/ContentUiState.kt index 0f1fcbd68..bfae7ef4b 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/body/model/ContentUiState.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/body/model/ContentUiState.kt @@ -2,27 +2,18 @@ package io.github.openflocon.flocondesktop.features.network.body.model import androidx.compose.runtime.Immutable import io.github.openflocon.flocondesktop.features.network.model.NetworkBodyDetailUi -import java.util.UUID @Immutable data class ContentUiState( val selectedRequestId: String?, val detailJsons: Set, - val mocksDisplayed: MockDisplayed?, val websocketMocksDisplayed: Boolean, - val badNetworkQualityDisplayed: Boolean, -) - -@Immutable -data class MockDisplayed( - val fromNetworkCallId: String?, - val windowInstanceId: String = UUID.randomUUID().toString(), + val badNetworkQualityDisplayed: Boolean ) fun previewContentUiState() = ContentUiState( selectedRequestId = null, detailJsons = emptySet(), - mocksDisplayed = null, badNetworkQualityDisplayed = false, - websocketMocksDisplayed = false, + websocketMocksDisplayed = false ) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/NetworkDetailDelegate.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/NetworkDetailDelegate.kt new file mode 100644 index 000000000..a87ca8158 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/NetworkDetailDelegate.kt @@ -0,0 +1,70 @@ +package io.github.openflocon.flocondesktop.features.network.detail + +import io.github.openflocon.domain.common.DispatcherProvider +import io.github.openflocon.domain.network.models.FloconNetworkCallDomainModel +import io.github.openflocon.domain.network.usecase.ObserveNetworkRequestsByIdUseCase +import io.github.openflocon.flocondesktop.common.coroutines.closeable.CloseableDelegate +import io.github.openflocon.flocondesktop.common.coroutines.closeable.CloseableScoped +import io.github.openflocon.flocondesktop.common.utils.stateInWhileSubscribed +import io.github.openflocon.flocondesktop.features.network.detail.mapper.toDetailUi +import io.github.openflocon.flocondesktop.features.network.detail.model.NetworkDetailViewState +import io.github.openflocon.flocondesktop.features.network.list.model.NetworkAction +import io.github.openflocon.flocondesktop.features.network.list.model.NetworkMethodUi +import io.github.openflocon.flocondesktop.features.network.list.model.NetworkStatusUi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update + +class NetworkDetailDelegate( + private val closeableDelegate: CloseableDelegate, + observeNetworkRequestsByIdUseCase: ObserveNetworkRequestsByIdUseCase, + dispatcherProvider: DispatcherProvider +) : CloseableScoped by closeableDelegate { + + private val _requestId = MutableStateFlow("") + + val uiState: StateFlow = _requestId.flatMapLatest { + observeNetworkRequestsByIdUseCase(it) + } + .distinctUntilChanged() + .filterNotNull() + .map(FloconNetworkCallDomainModel::toDetailUi) + .flowOn(dispatcherProvider.viewModel) + .stateInWhileSubscribed( + NetworkDetailViewState( + callId = "", + fullUrl = "", + requestTimeFormatted = "", + durationFormatted = "", + method = NetworkDetailViewState.Method.Http(NetworkMethodUi.Http.GET), + statusLabel = "", + status = NetworkStatusUi( + text = "", + status = NetworkStatusUi.Status.SUCCESS, + ), + graphQlSection = null, + requestBodyTitle = "", + requestBody = "", + requestSize = "", + requestHeaders = emptyList(), + response = null, + requestBodyIsNotBlank = false, + canOpenRequestBody = false, + imageUrl = null + ) + ) + + fun setRequestId(requestId: String) { + _requestId.update { requestId } + } + + fun onAction(action: NetworkAction) { + + } + +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/NetworkDetailViewModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/NetworkDetailViewModel.kt new file mode 100644 index 000000000..6358d77b3 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/NetworkDetailViewModel.kt @@ -0,0 +1,22 @@ +package io.github.openflocon.flocondesktop.features.network.detail + +import androidx.lifecycle.ViewModel +import io.github.openflocon.flocondesktop.features.network.list.model.NetworkAction +import org.koin.core.component.KoinComponent + +class NetworkDetailViewModel( + requestId: String, + delegate: NetworkDetailDelegate +) : ViewModel(), KoinComponent { + + val uiState = delegate.uiState + + init { + delegate.setRequestId(requestId) + } + + fun onAction(action: NetworkAction) { + + } + +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/mapper/NetworkDetailUiMapper.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/mapper/NetworkDetailUiMapper.kt index 93c27e6c3..4ba5301e7 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/mapper/NetworkDetailUiMapper.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/mapper/NetworkDetailUiMapper.kt @@ -8,7 +8,6 @@ import io.github.openflocon.flocondesktop.features.network.detail.model.NetworkD import io.github.openflocon.flocondesktop.features.network.detail.model.NetworkDetailViewState import io.github.openflocon.flocondesktop.features.network.detail.model.NetworkDetailViewState.Method.Http import io.github.openflocon.flocondesktop.features.network.detail.model.NetworkDetailViewState.Method.MethodName -import io.github.openflocon.flocondesktop.features.network.list.delegate.OpenBodyDelegate import io.github.openflocon.flocondesktop.features.network.list.mapper.getMethodUi import io.github.openflocon.flocondesktop.features.network.list.mapper.loadingStatus import io.github.openflocon.flocondesktop.features.network.list.mapper.toGraphQlNetworkStatusUi @@ -19,46 +18,43 @@ import io.github.openflocon.flocondesktop.features.network.list.model.NetworkMet import io.github.openflocon.flocondesktop.features.network.list.model.NetworkStatusUi import io.github.openflocon.library.designsystem.common.isImageUrl -fun toDetailUi( - request: FloconNetworkCallDomainModel, -): NetworkDetailViewState = - NetworkDetailViewState( - callId = request.callId, - fullUrl = request.request.url, - method = toDetailMethodUi(request), - statusLabel = toDetailStatusLabel(request), - status = toDetailHttpStatusUi(request), - requestTimeFormatted = request.request.startTimeFormatted, - durationFormatted = request.response?.durationFormatted, - imageUrl = request.request.url.takeIf { request.response?.isImage() == true || it.isImageUrl() }, - // request - requestBodyTitle = requestBodyTitle(request), - requestBody = httpBodyToUi(request.request.body), - requestBodyIsNotBlank = request.request.body.isNullOrBlank().not(), - canOpenRequestBody = canOpenExternal(request.request.body), - // headers., - requestHeaders = toNetworkHeadersUi(request.request.headers), - requestSize = request.request.byteSizeFormatted, - // response - response = request.response?.let { - when (it) { - is FloconNetworkCallDomainModel.Response.Failure -> NetworkDetailViewState.Response.Error( - issue = it.issue, - ) +fun FloconNetworkCallDomainModel.toDetailUi(): NetworkDetailViewState = NetworkDetailViewState( + callId = callId, + fullUrl = request.url, + method = toDetailMethodUi(this), + statusLabel = toDetailStatusLabel(this), + status = toDetailHttpStatusUi(this), + requestTimeFormatted = request.startTimeFormatted, + durationFormatted = response?.durationFormatted, + imageUrl = request.url.takeIf { response?.isImage() == true || it.isImageUrl() }, + // request + requestBodyTitle = requestBodyTitle(this), + requestBody = httpBodyToUi(request.body), + requestBodyIsNotBlank = request.body.isNullOrBlank().not(), + canOpenRequestBody = canOpenExternal(request.body), + // headers., + requestHeaders = toNetworkHeadersUi(request.headers), + requestSize = request.byteSizeFormatted, + // response + response = response?.let { + when (it) { + is FloconNetworkCallDomainModel.Response.Failure -> NetworkDetailViewState.Response.Error( + issue = it.issue, + ) - is FloconNetworkCallDomainModel.Response.Success -> NetworkDetailViewState.Response.Success( - body = httpBodyToUi(it.body), - size = it.byteSizeFormatted, - canOpenResponseBody = canOpenExternal(it.body), - responseBodyIsNotBlank = it.body.isNullOrBlank().not(), - headers = toNetworkHeadersUi(it.headers), - ) + is FloconNetworkCallDomainModel.Response.Success -> NetworkDetailViewState.Response.Success( + body = httpBodyToUi(it.body), + size = it.byteSizeFormatted, + canOpenResponseBody = canOpenExternal(it.body), + responseBodyIsNotBlank = it.body.isNullOrBlank().not(), + headers = toNetworkHeadersUi(it.headers), + ) - } + } - }, - graphQlSection = graphQlSection(request), - ) + }, + graphQlSection = graphQlSection(this), +) private fun toDetailStatusLabel(request: FloconNetworkCallDomainModel): String = when (request.request.specificInfos) { @@ -168,6 +164,6 @@ fun toDetailMethodUi(request: FloconNetworkCallDomainModel): NetworkDetailViewSt ) } -private fun canOpenExternal(body: String?) : Boolean { +private fun canOpenExternal(body: String?): Boolean { return body != null && body.isNotBlank() && OpenFile.isSupported() } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/model/NetworkDetailViewState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/model/NetworkDetailViewState.kt index 6ff0339a7..5d0d47274 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/model/NetworkDetailViewState.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/model/NetworkDetailViewState.kt @@ -39,6 +39,7 @@ data class NetworkDetailViewState( val size: String, val headers: List?, ) : Response + @Immutable data class Error( val issue: String, diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/view/NetworkDetailView.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/view/NetworkDetailView.kt index 5fea0c3f0..214d2f69d 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/view/NetworkDetailView.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/detail/view/NetworkDetailView.kt @@ -28,7 +28,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil3.compose.AsyncImage +import io.github.openflocon.flocondesktop.features.network.detail.NetworkDetailViewModel import io.github.openflocon.flocondesktop.features.network.detail.model.NetworkDetailViewState import io.github.openflocon.flocondesktop.features.network.detail.model.previewNetworkDetailHeaderUi import io.github.openflocon.flocondesktop.features.network.detail.view.components.DetailHeadersView @@ -47,29 +49,39 @@ import io.github.openflocon.library.designsystem.components.FloconSection 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 org.koin.core.parameter.parametersOf private const val LARGE_BODY_LENGHT = 30_000 @Composable -fun NetworkDetailView( - state: NetworkDetailViewState, +fun NetworkDetailScreen( + requestId: String +) { + val viewModel = koinViewModel { + parametersOf(requestId) + } + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + NetworkDetailContent( + uiState = uiState, + onAction = viewModel::onAction + ) +} + +@Composable +fun NetworkDetailContent( + uiState: NetworkDetailViewState, onAction: (NetworkAction) -> Unit, - modifier: Modifier = Modifier, + modifier: Modifier = Modifier ) { val scrollState: ScrollState = rememberScrollState() - val scrollAdapter = rememberFloconScrollbarAdapter(scrollState) - val linesLabelWidth: Dp = 130.dp val headersLabelWidth: Dp = 150.dp -// EscapeHandler { -// onAction(NetworkAction.ClosePanel) -// true // consumed -// } - Box( - modifier + modifier = modifier .background(FloconTheme.colorPalette.primary) ) { Column( @@ -80,7 +92,7 @@ fun NetworkDetailView( Request( modifier = Modifier .fillMaxWidth(), - state = state, + state = uiState, onAction = onAction, linesLabelWidth = linesLabelWidth, headersLabelWidth = headersLabelWidth, @@ -94,7 +106,7 @@ fun NetworkDetailView( Response( modifier = Modifier .fillMaxWidth(), - state = state, + state = uiState, onAction = onAction, headersLabelWidth = headersLabelWidth, ) @@ -272,7 +284,7 @@ private fun Request( title = state.requestBodyTitle, initialValue = true, actions = { - if(state.requestBodyIsNotBlank) { + if (state.requestBodyIsNotBlank) { FloconIconButton( tooltip = "View in app", imageVector = Icons.Outlined.OpenInFull, @@ -286,7 +298,7 @@ private fun Request( } ) } - if(state.canOpenRequestBody) { + if (state.canOpenRequestBody) { FloconIconButton( tooltip = "Open in external editor", imageVector = Icons.AutoMirrored.Outlined.OpenInNew, @@ -299,7 +311,7 @@ private fun Request( } ) } - if(state.requestBodyIsNotBlank) { + if (state.requestBodyIsNotBlank) { FloconIconButton( tooltip = "Copy", imageVector = Icons.Outlined.CopyAll, @@ -435,7 +447,7 @@ private fun Response( title = "Response - Body", initialValue = true, actions = { - if(response.responseBodyIsNotBlank) { + if (response.responseBodyIsNotBlank) { FloconIconButton( tooltip = "View body in app", imageVector = Icons.Outlined.OpenInFull, @@ -449,7 +461,7 @@ private fun Response( } ) } - if(response.canOpenResponseBody) { + if (response.canOpenResponseBody) { FloconIconButton( tooltip = "Open in external editor", imageVector = Icons.AutoMirrored.Outlined.OpenInNew, @@ -462,7 +474,7 @@ private fun Response( } ) } - if(response.responseBodyIsNotBlank) { + if (response.responseBodyIsNotBlank) { FloconIconButton( tooltip = "Copy", imageVector = Icons.Outlined.CopyAll, @@ -523,8 +535,8 @@ private fun Response( @Composable private fun NetworkDetailViewPreview() { FloconTheme { - NetworkDetailView( - state = NetworkDetailViewState( + NetworkDetailContent( + uiState = NetworkDetailViewState( callId = "", fullUrl = "http://www.google.com", method = NetworkDetailViewState.Method.Http(NetworkMethodUi.Http.GET), @@ -587,8 +599,7 @@ private fun NetworkDetailViewPreview() { canOpenRequestBody = true, requestBodyIsNotBlank = true, ), - modifier = Modifier.padding(16.dp), // Padding pour la preview - onAction = {}, + onAction = {} ) } } 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 884a7a0be..d618d4c3b 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 @@ -64,6 +64,7 @@ fun DetailHeadersView( label = item.name, value = item.value, labelWidth = labelWidth, + contentColor = FloconTheme.colorPalette.onPrimary, modifier = Modifier.fillMaxWidth() ) } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/NetworkViewModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/NetworkViewModel.kt index 0aac43440..faf76b972 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/NetworkViewModel.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/NetworkViewModel.kt @@ -8,16 +8,15 @@ import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import androidx.paging.cachedIn import androidx.paging.map -import co.touchlab.kermit.Logger import io.github.openflocon.domain.common.DispatcherProvider import io.github.openflocon.domain.common.combines import io.github.openflocon.domain.device.usecase.ObserveCurrentDeviceIdAndPackageNameUseCase import io.github.openflocon.domain.feedback.FeedbackDisplayer +import io.github.openflocon.domain.models.settings.NetworkSettings import io.github.openflocon.domain.network.models.BadQualityConfigDomainModel import io.github.openflocon.domain.network.models.MockNetworkDomainModel import io.github.openflocon.domain.network.models.NetworkFilterDomainModel import io.github.openflocon.domain.network.models.NetworkFilterDomainModel.Filters -import io.github.openflocon.domain.network.models.NetworkSettingsDomainModel import io.github.openflocon.domain.network.models.NetworkTextFilterColumns import io.github.openflocon.domain.network.usecase.DecodeJwtTokenUseCase import io.github.openflocon.domain.network.usecase.ExportNetworkCallsToCsvUseCase @@ -32,13 +31,12 @@ import io.github.openflocon.domain.network.usecase.ResetCurrentDeviceHttpRequest import io.github.openflocon.domain.network.usecase.badquality.ObserveAllNetworkBadQualitiesUseCase import io.github.openflocon.domain.network.usecase.mocks.ObserveNetworkMocksUseCase import io.github.openflocon.domain.network.usecase.mocks.ObserveNetworkWebsocketIdsUseCase -import io.github.openflocon.domain.network.usecase.settings.ObserveNetworkSettingsUseCase -import io.github.openflocon.domain.network.usecase.settings.UpdateNetworkSettingsUseCase -import io.github.openflocon.flocondesktop.common.utils.OpenFile +import io.github.openflocon.flocondesktop.common.utils.stateInWhileSubscribed +import io.github.openflocon.flocondesktop.core.data.settings.usecase.ObserveNetworkSettingsUseCase +import io.github.openflocon.flocondesktop.core.data.settings.usecase.SaveNetworkSettingsUseCase +import io.github.openflocon.flocondesktop.features.network.NetworkRoutes import io.github.openflocon.flocondesktop.features.network.body.model.ContentUiState -import io.github.openflocon.flocondesktop.features.network.body.model.MockDisplayed -import io.github.openflocon.flocondesktop.features.network.detail.mapper.toDetailUi -import io.github.openflocon.flocondesktop.features.network.detail.model.NetworkDetailViewState +import io.github.openflocon.flocondesktop.features.network.detail.NetworkDetailDelegate import io.github.openflocon.flocondesktop.features.network.list.delegate.HeaderDelegate import io.github.openflocon.flocondesktop.features.network.list.delegate.OpenBodyDelegate import io.github.openflocon.flocondesktop.features.network.list.mapper.toDomain @@ -51,6 +49,8 @@ import io.github.openflocon.flocondesktop.features.network.list.model.TopBarUiSt import io.github.openflocon.flocondesktop.features.network.list.model.header.columns.base.filter.TextFilterStateUiModel import io.github.openflocon.flocondesktop.features.network.model.NetworkBodyDetailUi import io.github.openflocon.library.designsystem.common.copyToClipboard +import io.github.openflocon.navigation.MainFloconNavigationState +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -59,13 +59,11 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import java.io.File class NetworkViewModel( observeNetworkRequestsUseCase: ObserveNetworkRequestsUseCase, @@ -84,39 +82,36 @@ class NetworkViewModel( private val exportNetworkCallsToCsv: ExportNetworkCallsToCsvUseCase, private val decodeJwtTokenUseCase: DecodeJwtTokenUseCase, private val removeOldSessionsNetworkRequestUseCase: RemoveOldSessionsNetworkRequestUseCase, + private val navigationState: MainFloconNavigationState, + private val detailDelegate: NetworkDetailDelegate, private val observeNetworkSettingsUseCase: ObserveNetworkSettingsUseCase, - private val updateNetworkSettingsUseCase: UpdateNetworkSettingsUseCase, private val observeNetworkWebsocketIdsUseCase: ObserveNetworkWebsocketIdsUseCase, private val openBodyDelegate: OpenBodyDelegate, + private val saveNetworkSettingsUseCase: SaveNetworkSettingsUseCase ) : ViewModel(headerDelegate) { private val contentState = MutableStateFlow( ContentUiState( selectedRequestId = null, detailJsons = emptySet(), - mocksDisplayed = null, badNetworkQualityDisplayed = false, - websocketMocksDisplayed = false, - ), + websocketMocksDisplayed = false + ) ) private val _filterText = mutableStateOf("") val filterText: State = _filterText - private val defaultNetworkSettings = NetworkSettingsDomainModel( + private val defaultNetworkSettings = NetworkSettings( displayOldSessions = true, autoScroll = false, invertList = false, + pinnedDetails = false ) - private val settings: StateFlow = observeNetworkSettingsUseCase() + private val settings: StateFlow = observeNetworkSettingsUseCase() .flowOn(dispatcherProvider.viewModel) - .map { it ?: defaultNetworkSettings } - .stateIn( - viewModelScope, - SharingStarted.WhileSubscribed(5_000), - initialValue = defaultNetworkSettings - ) + .stateInWhileSubscribed(defaultNetworkSettings) private val filterUiState = combine( mocksUseCase().map { it.any(MockNetworkDomainModel::isEnabled) }.distinctUntilChanged(), @@ -131,29 +126,15 @@ class NetworkViewModel( displayOldSessions = settings.displayOldSessions, hasWebsockets = hasWebsockets, ) - }.stateIn( - viewModelScope, SharingStarted.WhileSubscribed(5_000), - TopBarUiState( - hasBadNetwork = false, - hasMocks = false, - displayOldSessions = false, - hasWebsockets = false, + } + .stateInWhileSubscribed( + TopBarUiState( + hasBadNetwork = false, + hasMocks = false, + displayOldSessions = false, + hasWebsockets = false, + ) ) - ) - - private val detailState: StateFlow = - contentState.map { it.selectedRequestId } - .flatMapLatest { id -> - if (id == null) { - flowOf(null) - } else { - observeNetworkRequestsByIdUseCase(id) - .distinctUntilChanged() - .map { it?.let { toDetailUi(it) } } - } - } - .flowOn(dispatcherProvider.viewModel) - .stateIn(viewModelScope, started = SharingStarted.WhileSubscribed(5_000), null) private val filter = combines( snapshotFlow { _filterText.value }.map { it.takeIf { it.isNotBlank() } } @@ -162,19 +143,21 @@ class NetworkViewModel( headerDelegate.allowedMethods().map { items -> methodsToDomain(items) } .distinctUntilChanged(), settings, - ).map { (textFilters, filterOnAllColumns, methods, settings) -> - NetworkFilterDomainModel( - filterOnAllColumns = textFilters, - textsFilters = filterOnAllColumns, - methodFilter = methods, - displayOldSessions = settings.displayOldSessions, - ) - } + ) + .map { (textFilters, filterOnAllColumns, methods, settings) -> + NetworkFilterDomainModel( + filterOnAllColumns = textFilters, + textsFilters = filterOnAllColumns, + methodFilter = methods, + displayOldSessions = settings.displayOldSessions, + ) + } private val sortAndFilter = combines( headerDelegate.sorted.map { it?.toDomain() }.distinctUntilChanged(), filter, - ).distinctUntilChanged() + ) + .distinctUntilChanged() val items: Flow> = observeCurrentDeviceIdAndPackageNameUseCase() @@ -196,6 +179,15 @@ class NetworkViewModel( .flowOn(dispatcherProvider.viewModel) .cachedIn(viewModelScope) + private val detailState = combine( + detailDelegate.uiState, + contentState, + settings + ) { state, content, settings -> + state.takeIf { settings.pinnedDetails && content.selectedRequestId != null } + } + .stateInWhileSubscribed(null) + val uiState = combine( contentState, detailState, @@ -237,7 +229,6 @@ class NetworkViewModel( is NetworkAction.OpenBadNetworkQuality -> openBadNetworkQuality() is NetworkAction.CloseBadNetworkQuality -> closeBadNetworkQuality() - is NetworkAction.CloseMocks -> closeMocks() is NetworkAction.CopyCUrl -> onCopyCUrl(action) is NetworkAction.CopyUrl -> onCopyUrl(action) is NetworkAction.Remove -> onRemove(action) @@ -259,13 +250,24 @@ class NetworkViewModel( is NetworkAction.InvertList -> toggleInvertList(action) is NetworkAction.ToggleAutoScroll -> toggleAutoScroll(action) NetworkAction.ClearOldSession -> onClearSession() - is NetworkAction.Down -> contentState.update { it.copy(selectedRequestId = action.itemIdToSelect) } - is NetworkAction.Up -> contentState.update { it.copy(selectedRequestId = action.itemIdToSelect) } + is NetworkAction.Down -> selectRequest(action.itemIdToSelect) + is NetworkAction.Up -> selectRequest(action.itemIdToSelect) is NetworkAction.UpdateDisplayOldSessions -> toggleDisplayOldSessions(action) NetworkAction.OpenWebsocketMocks -> openWebsocketMocks() NetworkAction.CloseWebsocketMocks -> contentState.update { it.copy(websocketMocksDisplayed = false) } is NetworkAction.OpenBodyExternally.Request -> openBodyDelegate.openBodyExternally(action.item) is NetworkAction.OpenBodyExternally.Response -> openBodyDelegate.openBodyExternally(action.item) + is NetworkAction.Pinned -> onPinned(action) + } + } + + private fun onPinned(action: NetworkAction.Pinned) { + viewModelScope.launch { + saveNetworkSettingsUseCase( + settings.value.copy( + pinnedDetails = action.value + ) + ) } } @@ -277,7 +279,7 @@ class NetworkViewModel( private fun toggleAutoScroll(action: NetworkAction.ToggleAutoScroll) { viewModelScope.launch(dispatcherProvider.viewModel) { - updateNetworkSettingsUseCase( + saveNetworkSettingsUseCase( settings.value.copy( autoScroll = action.value ) @@ -287,7 +289,7 @@ class NetworkViewModel( private fun toggleDisplayOldSessions(action: NetworkAction.UpdateDisplayOldSessions) { viewModelScope.launch(dispatcherProvider.viewModel) { - updateNetworkSettingsUseCase( + saveNetworkSettingsUseCase( settings.value.copy( displayOldSessions = action.value ) @@ -297,7 +299,7 @@ class NetworkViewModel( private fun toggleInvertList(action: NetworkAction.InvertList) { viewModelScope.launch(dispatcherProvider.viewModel) { - updateNetworkSettingsUseCase( + saveNetworkSettingsUseCase( settings.value.copy( invertList = action.value ) @@ -312,25 +314,26 @@ class NetworkViewModel( } private fun onSelectRequest(action: NetworkAction.SelectRequest) { - contentState.update { state -> - state.copy( - selectedRequestId = if (state.selectedRequestId == action.id) { - null + selectRequest(action.id) + } + + private var selectRequestJob: Job? = null + private fun selectRequest(id: String) { + contentState.update { it.copy(selectedRequestId = id) } + selectRequestJob?.cancel() + selectRequestJob = viewModelScope.launch { + observeNetworkSettingsUseCase().collect { + if (it.pinnedDetails) { + detailDelegate.setRequestId(id) } else { - action.id - }, - ) + navigationState.navigate(NetworkRoutes.Panel(id)) + } + } } } private fun openMocks(callId: String?) { - contentState.update { state -> - state.copy( - mocksDisplayed = MockDisplayed( - fromNetworkCallId = callId, - ), - ) - } + navigationState.navigate(NetworkRoutes.Mocks(callId)) } private fun openWebsocketMocks() { @@ -341,14 +344,6 @@ class NetworkViewModel( } } - private fun closeMocks() { - contentState.update { state -> - state.copy( - mocksDisplayed = null, - ) - } - } - private fun openBadNetworkQuality() { contentState.update { state -> state.copy( diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/mapper/SettingsMapper.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/mapper/SettingsUiMapper.kt similarity index 57% rename from FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/mapper/SettingsMapper.kt rename to FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/mapper/SettingsUiMapper.kt index 1fe0dee08..cb3007fa9 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/mapper/SettingsMapper.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/mapper/SettingsUiMapper.kt @@ -1,10 +1,11 @@ package io.github.openflocon.flocondesktop.features.network.list.mapper -import io.github.openflocon.domain.network.models.NetworkSettingsDomainModel +import io.github.openflocon.domain.models.settings.NetworkSettings import io.github.openflocon.flocondesktop.features.network.list.model.NetworkSettingsUiModel -fun NetworkSettingsDomainModel.toUi() = NetworkSettingsUiModel( +fun NetworkSettings.toUi() = NetworkSettingsUiModel( + pinPanel = pinnedDetails, displayOldSessions = displayOldSessions, autoScroll = autoScroll, - invertList = invertList, + invertList = invertList ) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/NetworkAction.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/NetworkAction.kt index b76b2ece2..32e249666 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/NetworkAction.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/NetworkAction.kt @@ -21,7 +21,6 @@ sealed interface NetworkAction { data class CreateMock(val item: NetworkItemViewState) : NetworkAction data object OpenMocks : NetworkAction - data object CloseMocks : NetworkAction data object OpenWebsocketMocks : NetworkAction @@ -46,6 +45,8 @@ sealed interface NetworkAction { data class InvertList(val value: Boolean) : NetworkAction + data class Pinned(val value: Boolean) : NetworkAction + data class ToggleAutoScroll(val value: Boolean) : NetworkAction data object ClearOldSession : NetworkAction @@ -63,6 +64,7 @@ sealed interface NetworkAction { data class Request(val item: NetworkDetailViewState) : OpenBodyExternally data class Response(val item: NetworkDetailViewState.Response.Success) : OpenBodyExternally } + sealed interface HeaderAction : NetworkAction { data class ClickOnSort( val type: NetworkColumnsTypeUiModel, diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/NetworkSettingsUiModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/NetworkSettingsUiModel.kt index 8f21d505d..47cf5a409 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/NetworkSettingsUiModel.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/NetworkSettingsUiModel.kt @@ -6,11 +6,13 @@ import androidx.compose.runtime.Immutable data class NetworkSettingsUiModel( val displayOldSessions: Boolean, val autoScroll: Boolean, - val invertList: Boolean + val invertList: Boolean, + val pinPanel: Boolean ) fun previewNetworkSettingsUiModel() = NetworkSettingsUiModel( displayOldSessions = true, autoScroll = false, invertList = false, + pinPanel = false ) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/NetworkStatusUi.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/NetworkStatusUi.kt index 4478dfdc1..a8b563e26 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/NetworkStatusUi.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/NetworkStatusUi.kt @@ -1,8 +1,10 @@ package io.github.openflocon.flocondesktop.features.network.list.model import androidx.compose.runtime.Immutable +import kotlinx.serialization.Serializable @Immutable +@Serializable data class NetworkStatusUi( val text: String, val status: Status, diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/NetworkUiState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/NetworkUiState.kt index 6dcf5f9e0..4e0514682 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/NetworkUiState.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/model/NetworkUiState.kt @@ -2,10 +2,10 @@ package io.github.openflocon.flocondesktop.features.network.list.model import androidx.compose.runtime.Immutable import io.github.openflocon.flocondesktop.features.network.body.model.ContentUiState +import io.github.openflocon.flocondesktop.features.network.body.model.previewContentUiState import io.github.openflocon.flocondesktop.features.network.detail.model.NetworkDetailViewState import io.github.openflocon.flocondesktop.features.network.list.model.header.NetworkHeaderUiState import io.github.openflocon.flocondesktop.features.network.list.model.header.previewNetworkHeaderUiState -import io.github.openflocon.flocondesktop.features.network.body.model.previewContentUiState @Immutable data class NetworkUiState( @@ -13,7 +13,7 @@ data class NetworkUiState( val settings: NetworkSettingsUiModel, val detailState: NetworkDetailViewState?, val filterState: TopBarUiState, - val headerState: NetworkHeaderUiState, + val headerState: NetworkHeaderUiState ) fun previewNetworkUiState() = NetworkUiState( diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/NetworkScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/NetworkScreen.kt index 9975c5c7b..f4702af86 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/NetworkScreen.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/list/view/NetworkScreen.kt @@ -1,29 +1,27 @@ package io.github.openflocon.flocondesktop.features.network.list.view -import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.List -import androidx.compose.material.icons.outlined.ChatBubble import androidx.compose.material.icons.outlined.CleaningServices import androidx.compose.material.icons.outlined.Delete -import androidx.compose.material.icons.outlined.History import androidx.compose.material.icons.outlined.ImportExport -import androidx.compose.material.icons.outlined.Outbox import androidx.compose.material.icons.outlined.PlayCircle -import androidx.compose.material.icons.outlined.Podcasts import androidx.compose.material.icons.outlined.SignalWifiStatusbarConnectedNoInternet4 import androidx.compose.material.icons.outlined.WifiTethering +import androidx.compose.material.icons.sharp.PushPin import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State @@ -31,7 +29,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.input.key.Key @@ -39,19 +36,17 @@ import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type -import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.paging.PagingData import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.itemKey -import com.composeunstyled.Text import io.github.openflocon.flocondesktop.common.ui.window.FloconWindowState import io.github.openflocon.flocondesktop.common.ui.window.createFloconWindowState import io.github.openflocon.flocondesktop.features.network.badquality.list.view.BadNetworkQualityWindow -import io.github.openflocon.flocondesktop.features.network.detail.view.NetworkDetailView +import io.github.openflocon.flocondesktop.features.network.detail.view.NetworkDetailContent import io.github.openflocon.flocondesktop.features.network.list.NetworkViewModel import io.github.openflocon.flocondesktop.features.network.list.model.NetworkAction import io.github.openflocon.flocondesktop.features.network.list.model.NetworkItemColumnWidths @@ -62,11 +57,11 @@ import io.github.openflocon.flocondesktop.features.network.list.model.previewNet import io.github.openflocon.flocondesktop.features.network.list.model.previewNetworkUiState import io.github.openflocon.flocondesktop.features.network.list.view.components.FilterBar import io.github.openflocon.flocondesktop.features.network.list.view.header.NetworkItemHeaderView -import io.github.openflocon.flocondesktop.features.network.mock.list.view.NetworkMocksWindow import io.github.openflocon.flocondesktop.features.network.model.NetworkBodyDetailUi import io.github.openflocon.flocondesktop.features.network.view.NetworkBodyWindow import io.github.openflocon.flocondesktop.features.network.websocket.NetworkWebsocketMockWindow import io.github.openflocon.library.designsystem.FloconTheme +import io.github.openflocon.library.designsystem.components.FloconAnimateVisibility import io.github.openflocon.library.designsystem.components.FloconDropdownMenuItem import io.github.openflocon.library.designsystem.components.FloconDropdownSeparator import io.github.openflocon.library.designsystem.components.FloconFeature @@ -77,12 +72,11 @@ import io.github.openflocon.library.designsystem.components.FloconIconToggleButt import io.github.openflocon.library.designsystem.components.FloconOverflow import io.github.openflocon.library.designsystem.components.FloconPageTopBar import io.github.openflocon.library.designsystem.components.FloconVerticalScrollbar -import io.github.openflocon.library.designsystem.components.panel.FloconPanel +import io.github.openflocon.library.designsystem.components.panel.PanelWidth import io.github.openflocon.library.designsystem.components.rememberFloconScrollbarAdapter import kotlinx.coroutines.flow.MutableStateFlow import org.koin.compose.viewmodel.koinViewModel - @Composable fun NetworkScreen( modifier: Modifier = Modifier @@ -111,8 +105,7 @@ fun NetworkScreen( ) { val lazyListState = rememberLazyListState() val scrollAdapter = rememberFloconScrollbarAdapter(lazyListState) - val columnWidths: NetworkItemColumnWidths = - remember { NetworkItemColumnWidths() } // Default widths provided + val columnWidths: NetworkItemColumnWidths = remember { NetworkItemColumnWidths() } // Default widths provided LaunchedEffect(uiState.settings.autoScroll, rows.itemCount) { if (uiState.settings.autoScroll && rows.itemCount != -1) { @@ -120,95 +113,81 @@ fun NetworkScreen( } } - FloconFeature( - modifier = modifier + Row( + modifier = Modifier .fillMaxSize() - .clickable( - interactionSource = null, - indication = null, - enabled = uiState.detailState != null, - onClick = { onAction(NetworkAction.ClosePanel) }, - ) - .onPreviewKeyEvent { event -> - if (event.type != KeyEventType.KeyDown) - return@onPreviewKeyEvent false + ) { + FloconFeature( + contentPadding = PaddingValues(0.dp), + modifier = modifier + .fillMaxHeight() + .weight(1f) + .clickable( + interactionSource = null, + indication = null, + enabled = uiState.detailState != null, + onClick = { onAction(NetworkAction.ClosePanel) }, + ) + .onPreviewKeyEvent { event -> + if (event.type != KeyEventType.KeyDown) + return@onPreviewKeyEvent false - when (event.key) { - Key.DirectionUp -> { - selectPreviousRow(rows, uiState) - ?.let { - onAction(NetworkAction.Up(it.uuid)) - } + when (event.key) { + Key.DirectionUp -> { + selectPreviousRow(rows, uiState) + ?.let { + onAction(NetworkAction.Up(it.uuid)) + } - true - } + true + } - Key.DirectionDown -> { - selectNextRow(rows, uiState) - ?.let { - onAction(NetworkAction.Down(it.uuid)) - } - true - } + Key.DirectionDown -> { + selectNextRow(rows, uiState) + ?.let { + onAction(NetworkAction.Down(it.uuid)) + } + true + } - else -> false + else -> false + } } - } - ) { - FloconPageTopBar( - modifier = Modifier.fillMaxWidth(), - filterBar = { - FilterBar( - filterText = filterText, - placeholderText = "Filter route", - onTextChange = { onAction(NetworkAction.FilterQuery(it)) }, - modifier = Modifier.fillMaxWidth(.7f), - ) - }, - actions = { - if (uiState.filterState.hasWebsockets) { + ) { + FloconPageTopBar( + modifier = Modifier.fillMaxWidth(), + filterBar = { + FilterBar( + filterText = filterText, + placeholderText = "Filter route", + onTextChange = { onAction(NetworkAction.FilterQuery(it)) }, + modifier = Modifier.fillMaxWidth(.7f), + ) + }, + actions = { FloconIconToggleButton( - value = true, - tooltip = "Websocket Mocks", - onValueChange = { onAction(NetworkAction.OpenWebsocketMocks) } + value = uiState.filterState.hasMocks, + tooltip = "Mocks", + onValueChange = { onAction(NetworkAction.OpenMocks) } ) { FloconIcon( - imageVector = Icons.Outlined.Outbox + imageVector = Icons.Outlined.WifiTethering ) } - } - FloconIconToggleButton( - value = uiState.filterState.displayOldSessions, - tooltip = "Display old sessions", - onValueChange = { onAction(NetworkAction.UpdateDisplayOldSessions(it)) } - ) { - FloconIcon( - imageVector = Icons.Outlined.History - ) - } - FloconIconToggleButton( - value = uiState.filterState.hasMocks, - tooltip = "Mocks", - onValueChange = { onAction(NetworkAction.OpenMocks) } - ) { - FloconIcon( - imageVector = Icons.Outlined.WifiTethering - ) - } - FloconIconToggleButton( - value = uiState.filterState.hasBadNetwork, - tooltip = "Bad network", - onValueChange = { onAction(NetworkAction.OpenBadNetworkQuality) } - ) { - FloconIcon( - imageVector = Icons.Outlined.SignalWifiStatusbarConnectedNoInternet4 + FloconIconToggleButton( + value = uiState.filterState.hasBadNetwork, + tooltip = "Bad network", + onValueChange = { onAction(NetworkAction.OpenBadNetworkQuality) } + ) { + FloconIcon( + imageVector = Icons.Outlined.SignalWifiStatusbarConnectedNoInternet4 + ) + } + FloconIconButton( + imageVector = Icons.Outlined.Delete, + onClick = { onAction(NetworkAction.Reset) } ) - } - FloconIconButton( - imageVector = Icons.Outlined.Delete, - onClick = { onAction(NetworkAction.Reset) } - ) - // TODO Later + // TODO Later // FloconDropdownMenu( // expanded = expandedColumn, // anchorContent = { @@ -251,100 +230,112 @@ fun NetworkScreen( // onCheckedChange = {} // ) // } - FloconOverflow { - FloconDropdownMenuItem( - text = "Export CSV", - leadingIcon = Icons.Outlined.ImportExport, - onClick = { onAction(NetworkAction.ExportCsv) } - ) - FloconDropdownMenuItem( - checked = uiState.settings.autoScroll, - text = "Auto scroll", - leadingIcon = Icons.Outlined.PlayCircle, - onCheckedChange = { onAction(NetworkAction.ToggleAutoScroll(it)) } - ) - FloconDropdownMenuItem( - checked = uiState.settings.invertList, - text = "Invert list", - leadingIcon = Icons.AutoMirrored.Outlined.List, - onCheckedChange = { onAction(NetworkAction.InvertList(it)) } - ) - FloconDropdownSeparator() - FloconDropdownMenuItem( - text = "Clear old sessions", - leadingIcon = Icons.Outlined.CleaningServices, - onClick = { onAction(NetworkAction.ClearOldSession) } - ) - } - }, - ) - Column( - modifier = Modifier - .fillMaxSize() - .clip(FloconTheme.shapes.medium) - .background(FloconTheme.colorPalette.primary) - ) { - NetworkItemHeaderView( - columnWidths = columnWidths, - modifier = Modifier - .fillMaxWidth() - .background(FloconTheme.colorPalette.primary), - clickOnSort = { type, sort -> - onAction(NetworkAction.HeaderAction.ClickOnSort(type, sort)) - }, - onFilterAction = { - onAction(NetworkAction.HeaderAction.FilterAction(it)) + FloconOverflow { + FloconDropdownMenuItem( + text = "Export CSV", + leadingIcon = Icons.Outlined.ImportExport, + onClick = { onAction(NetworkAction.ExportCsv) } + ) + FloconDropdownMenuItem( + checked = uiState.settings.autoScroll, + text = "Auto scroll", + leadingIcon = Icons.Outlined.PlayCircle, + onCheckedChange = { checked -> onAction(NetworkAction.ToggleAutoScroll(checked)) } + ) + FloconDropdownMenuItem( + checked = uiState.settings.invertList, + text = "Invert list", + leadingIcon = Icons.AutoMirrored.Outlined.List, + onCheckedChange = { checked -> onAction(NetworkAction.InvertList(checked)) } + ) + FloconDropdownMenuItem( + checked = uiState.settings.pinPanel, + text = "Pin panel", + leadingIcon = Icons.Sharp.PushPin, + onCheckedChange = { checked -> onAction(NetworkAction.Pinned(checked)); it() } + ) + FloconDropdownSeparator() + FloconDropdownMenuItem( + text = "Clear old sessions", + leadingIcon = Icons.Outlined.CleaningServices, + onClick = { onAction(NetworkAction.ClearOldSession) } + ) + } }, - state = uiState.headerState, ) - FloconHorizontalDivider() - Row( - Modifier.fillMaxSize() + Column( + modifier = Modifier + .fillMaxSize() + .clip(FloconTheme.shapes.medium) + .background(FloconTheme.colorPalette.primary) ) { - LazyColumn( - state = lazyListState, - reverseLayout = uiState.settings.invertList, + NetworkItemHeaderView( + columnWidths = columnWidths, modifier = Modifier - .fillMaxHeight() - .weight(1f) + .fillMaxWidth() + .background(FloconTheme.colorPalette.primary), + clickOnSort = { type, sort -> + onAction(NetworkAction.HeaderAction.ClickOnSort(type, sort)) + }, + onFilterAction = { + onAction(NetworkAction.HeaderAction.FilterAction(it)) + }, + state = uiState.headerState, + ) + FloconHorizontalDivider() + Row( + Modifier.fillMaxSize() ) { - items( - count = rows.itemCount, - key = rows.itemKey { it.uuid }, - ) { index -> - val item = rows[index] - if (item != null) { - NetworkItemView( - state = item, - selected = item.uuid == uiState.contentState.selectedRequestId, - columnWidths = columnWidths, - onAction = onAction, - modifier = Modifier - .fillMaxWidth() - .animateItem(), - ) - } else { - Box(Modifier) // display nothing during load + LazyColumn( + state = lazyListState, + reverseLayout = uiState.settings.invertList, + modifier = Modifier + .fillMaxHeight() + .weight(1f) + ) { + items( + count = rows.itemCount, + key = rows.itemKey { it.uuid }, + ) { index -> + val item = rows[index] + if (item != null) { + NetworkItemView( + state = item, + selected = item.uuid == uiState.contentState.selectedRequestId, + columnWidths = columnWidths, + onAction = onAction, + modifier = Modifier + .fillMaxWidth() + .animateItem(), + ) + } else { + Box(Modifier) // display nothing during load + } } } + FloconVerticalScrollbar( + adapter = scrollAdapter, + modifier = Modifier.fillMaxHeight(), + ) } - FloconVerticalScrollbar( - adapter = scrollAdapter, - modifier = Modifier.fillMaxHeight(), + } + } + FloconAnimateVisibility( + state = uiState.detailState, + modifier = Modifier.fillMaxHeight() + ) { + Row { + Spacer(Modifier.width(8.dp)) + NetworkDetailContent( + uiState = it, + onAction = onAction, + modifier = Modifier + .width(PanelWidth) + .clip(FloconTheme.shapes.medium) ) } } } - FloconPanel( - contentState = uiState.detailState, - onClose = { onAction(NetworkAction.ClosePanel) } - ) { - NetworkDetailView( - state = it, - onAction = onAction, - modifier = Modifier.matchParentSize() - ) - } val states = remember { mutableStateMapOf() } @@ -356,9 +347,7 @@ fun NetworkScreen( deletedJson.forEach { states.remove(it) } addedJson.forEach { - states.put( - it, createFloconWindowState(), - ) + states[it] = createFloconWindowState() } } @@ -378,16 +367,6 @@ fun NetworkScreen( } } - uiState.contentState.mocksDisplayed?.let { - NetworkMocksWindow( - instanceId = it.windowInstanceId, - fromNetworkCallId = it.fromNetworkCallId, - onCloseRequest = { - onAction(NetworkAction.CloseMocks) - }, - ) - } - if (uiState.contentState.badNetworkQualityDisplayed) { BadNetworkQualityWindow( onCloseRequest = { 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 28971d459..0b8f8d98c 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 @@ -13,7 +13,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.key import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -23,7 +22,6 @@ import io.github.openflocon.flocondesktop.features.network.mock.list.model.MockN import io.github.openflocon.flocondesktop.features.network.mock.list.model.previewMockNetworkLineUiModel import io.github.openflocon.library.designsystem.FloconTheme import io.github.openflocon.library.designsystem.components.FloconButton -import io.github.openflocon.library.designsystem.components.FloconDialog import io.github.openflocon.library.designsystem.components.FloconDialogHeader import io.github.openflocon.library.designsystem.components.FloconDropdownMenuItem import io.github.openflocon.library.designsystem.components.FloconOverflow @@ -33,9 +31,7 @@ import org.koin.compose.viewmodel.koinViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable fun NetworkMocksWindow( - instanceId: String, - fromNetworkCallId: String?, - onCloseRequest: () -> Unit, + fromNetworkCallId: String? ) { val viewModel: NetworkMocksViewModel = koinViewModel() LaunchedEffect(viewModel, fromNetworkCallId) { @@ -43,26 +39,21 @@ fun NetworkMocksWindow( } val mocks by viewModel.items.collectAsStateWithLifecycle() val editionWindow by viewModel.editionWindow.collectAsStateWithLifecycle() - key(instanceId) { - FloconDialog( - onDismissRequest = onCloseRequest, - ) { - NetworkMocksContent( - mocks = mocks, - modifier = Modifier.fillMaxWidth(), - onAction = viewModel::onAction, - ) - } - editionWindow?.let { - NetworkEditionWindow( - instanceId = it.windowInstanceId, - state = it.selectedMockUiModel, - onCloseRequest = viewModel::cancelMockCreation, - onCancel = viewModel::cancelMockCreation, - onSave = viewModel::addMock, - ) - } + NetworkMocksContent( + mocks = mocks, + modifier = Modifier.fillMaxWidth(), + onAction = viewModel::onAction, + ) + + editionWindow?.let { + NetworkEditionWindow( + instanceId = it.windowInstanceId, + state = it.selectedMockUiModel, + onCloseRequest = viewModel::cancelMockCreation, + onCancel = viewModel::cancelMockCreation, + onSave = viewModel::addMock, + ) } } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/Navigation.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/Navigation.kt new file mode 100644 index 000000000..928bc5110 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/sharedpreferences/Navigation.kt @@ -0,0 +1,22 @@ +package io.github.openflocon.flocondesktop.features.sharedpreferences + +import androidx.navigation3.runtime.EntryProviderScope +import io.github.openflocon.flocondesktop.app.MenuSceneStrategy +import io.github.openflocon.flocondesktop.features.sharedpreferences.view.SharedPreferencesScreen +import io.github.openflocon.navigation.FloconRoute +import kotlinx.serialization.Serializable + +sealed interface SharedPreferencesRoutes : FloconRoute { + + @Serializable + data object Main : SharedPreferencesRoutes + +} + +fun EntryProviderScope.sharedPreferencesRoutes() { + entry( + metadata = MenuSceneStrategy.menu() + ) { + SharedPreferencesScreen() + } +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/table/Navigation.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/table/Navigation.kt new file mode 100644 index 000000000..a4298f737 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/table/Navigation.kt @@ -0,0 +1,22 @@ +package io.github.openflocon.flocondesktop.features.table + +import androidx.navigation3.runtime.EntryProviderScope +import io.github.openflocon.flocondesktop.app.MenuSceneStrategy +import io.github.openflocon.flocondesktop.features.table.view.TableScreen +import io.github.openflocon.navigation.FloconRoute +import kotlinx.serialization.Serializable + +sealed interface TableRoutes : FloconRoute { + + @Serializable + data object Main : TableRoutes + +} + +fun EntryProviderScope.tableRoutes() { + entry( + metadata = MenuSceneStrategy.menu() + ) { + TableScreen() + } +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/di/MainModule.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/di/MainModule.kt deleted file mode 100644 index f280cb15a..000000000 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/di/MainModule.kt +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.openflocon.flocondesktop.main.di - -import io.github.openflocon.flocondesktop.main.ui.di.mainUiModule -import org.koin.dsl.module - -val mainModule = - module { - includes( - mainUiModule, - ) - } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/MainScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/MainScreen.kt deleted file mode 100644 index 0d915dd43..000000000 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/MainScreen.kt +++ /dev/null @@ -1,248 +0,0 @@ -@file:OptIn(ExperimentalMaterial3Api::class) - -package io.github.openflocon.flocondesktop.main.ui - -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.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -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.material3.ExperimentalMaterial3Api -import androidx.compose.runtime.Composable -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.compose.ui.zIndex -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import io.github.openflocon.flocondesktop.features.analytics.view.AnalyticsScreen -import io.github.openflocon.flocondesktop.features.dashboard.view.DashboardScreen -import io.github.openflocon.flocondesktop.features.database.view.DatabaseScreen -import io.github.openflocon.flocondesktop.features.deeplinks.view.DeeplinkScreen -import io.github.openflocon.flocondesktop.features.files.view.FilesScreen -import io.github.openflocon.flocondesktop.features.images.view.ImagesScreen -import io.github.openflocon.flocondesktop.features.network.list.view.NetworkScreen -import io.github.openflocon.flocondesktop.features.sharedpreferences.view.SharedPreferencesScreen -import io.github.openflocon.flocondesktop.features.table.view.TableScreen -import io.github.openflocon.flocondesktop.main.ui.model.AppsStateUiModel -import io.github.openflocon.flocondesktop.main.ui.model.DeviceAppUiModel -import io.github.openflocon.flocondesktop.main.ui.model.DeviceItemUiModel -import io.github.openflocon.flocondesktop.main.ui.model.DevicesStateUiModel -import io.github.openflocon.flocondesktop.main.ui.model.RecordVideoStateUiModel -import io.github.openflocon.flocondesktop.main.ui.model.SubScreen -import io.github.openflocon.flocondesktop.main.ui.model.leftpanel.LeftPanelItem -import io.github.openflocon.flocondesktop.main.ui.model.leftpanel.LeftPanelState -import io.github.openflocon.flocondesktop.main.ui.settings.SettingsScreen -import io.github.openflocon.flocondesktop.main.ui.view.leftpannel.LeftPanelView -import io.github.openflocon.flocondesktop.main.ui.view.leftpannel.PanelMaxWidth -import io.github.openflocon.flocondesktop.main.ui.view.leftpannel.PanelMinWidth -import io.github.openflocon.flocondesktop.main.ui.view.topbar.MainScreenTopBar -import io.github.openflocon.library.designsystem.FloconTheme -import io.github.openflocon.library.designsystem.components.FloconIcon -import org.koin.compose.viewmodel.koinViewModel - -@Composable -fun MainScreen( - modifier: Modifier = Modifier, -) { - val viewModel: MainViewModel = koinViewModel() - val leftPanelState by viewModel.leftPanelState.collectAsStateWithLifecycle() - val subScreen by viewModel.subScreen.collectAsStateWithLifecycle() - val devicesState by viewModel.devicesState.collectAsStateWithLifecycle() - val appsState by viewModel.appsState.collectAsStateWithLifecycle() - val recordState by viewModel.recordState.collectAsStateWithLifecycle() - - Box(modifier = modifier) { - MainScreen( - subScreen = subScreen, - modifier = Modifier.fillMaxSize(), - devicesState = devicesState, - appsState = appsState, - recordState = recordState, - onDeviceSelected = viewModel::onDeviceSelected, - deleteDevice = viewModel::deleteDevice, - deleteApp = viewModel::deleteApp, - onAppSelected = viewModel::onAppSelected, - leftPanelState = leftPanelState, - onClickLeftPanelItem = viewModel::onClickLeftPanelItem, - onTakeScreenshotClicked = viewModel::onTakeScreenshotClicked, - onRecordClicked = viewModel::onRecordClicked, - onRestartClicked = viewModel::onRestartClicked, - ) - } -} - -@Composable -private fun MainScreen( - subScreen: SubScreen, - leftPanelState: LeftPanelState, - onClickLeftPanelItem: (LeftPanelItem) -> Unit, - devicesState: DevicesStateUiModel, - appsState: AppsStateUiModel, - onDeviceSelected: (DeviceItemUiModel) -> Unit, - deleteDevice: (DeviceItemUiModel) -> Unit, - onAppSelected: (DeviceAppUiModel) -> Unit, - deleteApp: (DeviceAppUiModel) -> Unit, - onTakeScreenshotClicked: () -> Unit, - recordState: RecordVideoStateUiModel, - onRecordClicked: () -> Unit, - onRestartClicked: () -> Unit, - modifier: Modifier = Modifier, -) { - 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) - - Column( - modifier = modifier - ) { - MainScreenTopBar( - modifier = Modifier - .fillMaxWidth() - .zIndex(10f), - devicesState = devicesState, - appsState = appsState, - onDeviceSelected = onDeviceSelected, - deleteDevice = deleteDevice, - onAppSelected = onAppSelected, - deleteApp = deleteApp, - onTakeScreenshotClicked = onTakeScreenshotClicked, - recordState = recordState, - onRecordClicked = onRecordClicked, - onRestartClicked = onRestartClicked, - ) - Box( - modifier = Modifier - .fillMaxSize() - .onGloballyPositioned { - windowSize = it.size // TODO Add windowsize lib - }, - ) { - Row( - modifier = Modifier - .fillMaxSize(), - ) { - LeftPanelView( - modifier = Modifier - .width(width) - .fillMaxHeight(), - expanded = expanded, - onClickItem = onClickLeftPanelItem, - state = leftPanelState, - ) - Box( - modifier = Modifier - .fillMaxSize() - ) { - when (subScreen) { - SubScreen.Network -> - NetworkScreen( - modifier = Modifier - .fillMaxSize(), - ) - - SubScreen.Database -> - DatabaseScreen( - modifier = Modifier - .fillMaxSize(), - ) - - SubScreen.Images -> - ImagesScreen( - modifier = Modifier - .fillMaxSize(), - ) - - SubScreen.Files -> - FilesScreen( - modifier = Modifier - .fillMaxSize(), - ) - - SubScreen.Tables -> - TableScreen( - modifier = Modifier - .fillMaxSize(), - ) - - SubScreen.SharedPreferences -> - SharedPreferencesScreen( - modifier = Modifier - .fillMaxSize(), - ) - - SubScreen.Dashboard -> - DashboardScreen( - modifier = Modifier - .fillMaxSize(), - ) - - SubScreen.Settings -> { - SettingsScreen( - modifier = Modifier.fillMaxSize(), - ) - } - - SubScreen.Deeplinks -> { - DeeplinkScreen( - modifier = - Modifier - .fillMaxSize(), - ) - } - - SubScreen.Analytics -> { - AnalyticsScreen( - modifier = - Modifier - .fillMaxSize(), - ) - } - } - } - } - Box( - contentAlignment = Alignment.Center, - modifier = Modifier - .width(20.dp) - .height(60.dp) - .graphicsLayer { - translationX = position.toPx() - size.width / 2 - 8.dp.toPx() - translationY = (windowSize.height / 2) - (size.height / 2) - } - .clip(RoundedCornerShape(4.dp)) - .background(FloconTheme.colorPalette.primary) - .clickable(onClick = { expanded = !expanded }), - ) { - FloconIcon( - imageVector = Icons.Outlined.ChevronRight, - tint = Color.LightGray, - modifier = Modifier.rotate(rotate), - ) - } - } - } -} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/MainViewModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/MainViewModel.kt deleted file mode 100644 index 48a48c920..000000000 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/MainViewModel.kt +++ /dev/null @@ -1,193 +0,0 @@ -package io.github.openflocon.flocondesktop.main.ui - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import io.github.openflocon.domain.common.DispatcherProvider -import io.github.openflocon.domain.common.combines -import io.github.openflocon.domain.device.models.DeviceCapabilitiesDomainModel -import io.github.openflocon.domain.device.usecase.ObserveCurrentDeviceCapabilitiesUseCase -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.flocondesktop.app.InitialSetupStateHolder -import io.github.openflocon.flocondesktop.main.ui.delegates.DevicesDelegate -import io.github.openflocon.flocondesktop.main.ui.delegates.RecordVideoDelegate -import io.github.openflocon.flocondesktop.main.ui.model.AppsStateUiModel -import io.github.openflocon.flocondesktop.main.ui.model.DeviceAppUiModel -import io.github.openflocon.flocondesktop.main.ui.model.DeviceItemUiModel -import io.github.openflocon.flocondesktop.main.ui.model.DevicesStateUiModel -import io.github.openflocon.flocondesktop.main.ui.model.RecordVideoStateUiModel -import io.github.openflocon.flocondesktop.main.ui.model.SubScreen -import io.github.openflocon.flocondesktop.main.ui.model.id -import io.github.openflocon.flocondesktop.main.ui.model.leftpanel.LeftPanelItem -import io.github.openflocon.flocondesktop.main.ui.model.leftpanel.LeftPanelState -import io.github.openflocon.flocondesktop.main.ui.model.leftpanel.LeftPannelSection -import io.github.openflocon.flocondesktop.main.ui.view.displayName -import io.github.openflocon.flocondesktop.main.ui.view.icon -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch - -class MainViewModel( - private val devicesDelegate: DevicesDelegate, - private val dispatcherProvider: DispatcherProvider, - private val initialSetupStateHolder: InitialSetupStateHolder, - private val takeScreenshotUseCase : TakeScreenshotUseCase, - private val restartAppUseCase : RestartAppUseCase, - private val recordVideoDelegate: RecordVideoDelegate, - private val feedbackDisplayer: FeedbackDisplayer, - private val observeCurrentDeviceCapabilitiesUseCase: ObserveCurrentDeviceCapabilitiesUseCase, -) : ViewModel( - devicesDelegate, - recordVideoDelegate, -) { - val subScreen = MutableStateFlow(SubScreen.Network) - - val recordState: StateFlow = recordVideoDelegate.state - - init { - viewModelScope.launch(dispatcherProvider.viewModel) { - initialSetupStateHolder.needsAdbSetup.collect { - if (it) { - subScreen.update { SubScreen.Settings } - } - } - } - } - - val leftPanelState = combines( - subScreen, - observeCurrentDeviceCapabilitiesUseCase(), - ).map { (subScreen, capabilities) -> - buildLeftPanelState( - selectedId = subScreen.id, - currentDeviceCapabilities = capabilities, - ) - }.flowOn(dispatcherProvider.ui) - .stateIn( - scope = viewModelScope, - started = SharingStarted.Eagerly, - initialValue = buildLeftPanelState( - selectedId = subScreen.value.id, - currentDeviceCapabilities = null - ), - ) - - val devicesState: StateFlow = devicesDelegate.devicesState - val appsState: StateFlow = devicesDelegate.appsState - - fun onDeviceSelected(device: DeviceItemUiModel) { - viewModelScope.launch(dispatcherProvider.viewModel) { - devicesDelegate.select(device.id) - } - } - - fun deleteDevice(device: DeviceItemUiModel) { - viewModelScope.launch(dispatcherProvider.viewModel) { - devicesDelegate.delete(device.id) - } - } - - fun deleteApp(app: DeviceAppUiModel) { - viewModelScope.launch(dispatcherProvider.viewModel) { - devicesDelegate.deleteApp(app.packageName) - } - } - - fun onAppSelected(app: DeviceAppUiModel) { - viewModelScope.launch(dispatcherProvider.viewModel) { - devicesDelegate.selectApp(app.packageName) - } - } - - fun onClickLeftPanelItem(leftPanelItem: LeftPanelItem) { - this.subScreen.update { SubScreen.fromId(leftPanelItem.id) } - } - - fun onRecordClicked() { - viewModelScope.launch(dispatcherProvider.viewModel) { - recordVideoDelegate.toggleRecording() - } - } - - fun onRestartClicked() { - viewModelScope.launch(dispatcherProvider.viewModel) { - restartAppUseCase() - } - } - - fun onTakeScreenshotClicked() { - viewModelScope.launch(dispatcherProvider.viewModel) { - takeScreenshotUseCase().fold( - doOnFailure = { - feedbackDisplayer.displayMessage(it.message ?: "Unknown error") - }, - doOnSuccess = { - feedbackDisplayer.displayMessage("Success, file saved at $it") - }, - ) - } - } -} - -internal fun buildLeftPanelState(selectedId: String?, currentDeviceCapabilities: DeviceCapabilitiesDomainModel?) = LeftPanelState( - bottomItems = listOf( - item( - subScreen = SubScreen.Settings, - selectedId = selectedId, - isEnabled = true, - ), - ), - sections = listOf( - LeftPannelSection( - title = "Network", - items = listOf( - item(subScreen = SubScreen.Network, selectedId = selectedId, isEnabled = true,), - item(subScreen = SubScreen.Images, selectedId = selectedId, isEnabled = true,), - ), - ), - LeftPannelSection( - title = "Storage", - items = listOf( - item(SubScreen.Database, selectedId = selectedId, isEnabled = true), - item(SubScreen.SharedPreferences, selectedId = selectedId, isEnabled = currentDeviceCapabilities?.sharedPreferences ?: true), - item(SubScreen.Files, selectedId = selectedId, isEnabled = currentDeviceCapabilities?.files ?: true), - ), - ), - LeftPannelSection( - title = "Data", - items = listOf( - item(SubScreen.Dashboard, selectedId = selectedId, isEnabled = true), - item(SubScreen.Analytics, selectedId = selectedId, isEnabled = true), - item(SubScreen.Tables, selectedId = selectedId, isEnabled = true), - ), - ), - LeftPannelSection( - title = "Actions", - items = listOf( - item(SubScreen.Deeplinks, selectedId = selectedId, isEnabled = currentDeviceCapabilities?.deeplinks ?: true), - ), - ), - ), -) - -private fun item( - subScreen: SubScreen, - selectedId: String?, - isEnabled: Boolean, -): LeftPanelItem { - val id = subScreen.id - return LeftPanelItem( - id = id, - icon = subScreen.icon(), - text = subScreen.displayName(), - isSelected = selectedId == id, - isEnabled = isEnabled, - ) -} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/di/MainUiModule.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/di/MainUiModule.kt deleted file mode 100644 index 69483969a..000000000 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/di/MainUiModule.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.github.openflocon.flocondesktop.main.ui.di - -import io.github.openflocon.flocondesktop.main.ui.MainViewModel -import io.github.openflocon.flocondesktop.main.ui.delegates.DevicesDelegate -import io.github.openflocon.flocondesktop.main.ui.delegates.RecordVideoDelegate -import io.github.openflocon.flocondesktop.main.ui.settings.SettingsViewModel -import org.koin.core.module.dsl.factoryOf -import org.koin.core.module.dsl.viewModelOf -import org.koin.dsl.module - -val mainUiModule = - module { - viewModelOf(::MainViewModel) - factoryOf(::DevicesDelegate) - factoryOf(::RecordVideoDelegate) - viewModelOf(::SettingsViewModel) - } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/SubScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/SubScreen.kt deleted file mode 100644 index d0fb1dfa6..000000000 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/SubScreen.kt +++ /dev/null @@ -1,32 +0,0 @@ -package io.github.openflocon.flocondesktop.main.ui.model - -enum class SubScreen { - Dashboard, - - // TODO group network, grpc, networkImages - Network, - Images, // network images - - // storage - Database, - Files, // device files (context.cache, context.files) - SharedPreferences, - - Analytics, - Tables, - - Settings, - - Deeplinks, - - ; - - companion object { - fun fromId(id: String): SubScreen = SubScreen.valueOf(id) - } -} - -val SubScreen.id: String - get() { - return this.name - } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/leftpanel/LeftPanelItem.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/leftpanel/LeftPanelItem.kt deleted file mode 100644 index 7a1117a72..000000000 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/leftpanel/LeftPanelItem.kt +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.openflocon.flocondesktop.main.ui.model.leftpanel - -import androidx.compose.ui.graphics.vector.ImageVector - -data class LeftPanelItem( - val id: String, - val icon: ImageVector, - val text: String, - val isSelected: Boolean, - val isEnabled: Boolean, -) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/leftpanel/LeftPanelState.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/leftpanel/LeftPanelState.kt deleted file mode 100644 index 07a613e0e..000000000 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/leftpanel/LeftPanelState.kt +++ /dev/null @@ -1,94 +0,0 @@ -package io.github.openflocon.flocondesktop.main.ui.model.leftpanel - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Settings - -data class LeftPanelState( - val sections: List, - val bottomItems: List, -) - -fun previewLeftPannelState(selectedId: String?) = LeftPanelState( - bottomItems = listOf( - LeftPanelItem( - id = "Settings", - icon = Icons.Outlined.Settings, - text = "Settings", - isSelected = selectedId == "Settings", - isEnabled = true, - ), - ), - sections = listOf( - LeftPannelSection( - title = "Network", - items = listOf( - LeftPanelItem( - id = "Http", - icon = Icons.Outlined.Settings, - text = "Http", - isSelected = selectedId == "Http", - isEnabled = true, - ), - LeftPanelItem( - id = "Images", - icon = Icons.Outlined.Settings, - text = "Images", - isSelected = selectedId == "Images", - isEnabled = true, - ), - LeftPanelItem( - id = "Grpc", - icon = Icons.Outlined.Settings, - text = "Grpc", - isSelected = selectedId == "Grpc", - isEnabled = true, - ), - ), - ), - LeftPannelSection( - title = "Storage", - items = listOf( - LeftPanelItem( - id = "Database", - icon = Icons.Outlined.Settings, - text = "Database", - isSelected = selectedId == "Database", - isEnabled = true, - ), - LeftPanelItem( - id = "SharedPreferences", - icon = Icons.Outlined.Settings, - text = "SharedPreferences", - isSelected = selectedId == "SharedPreferences", - isEnabled = true, - ), - LeftPanelItem( - id = "Files", - icon = Icons.Outlined.Settings, - text = "Files", - isSelected = selectedId == "Files", - isEnabled = true, - ), - ), - ), - LeftPannelSection( - title = "Data", - items = listOf( - LeftPanelItem( - id = "Dashboard", - icon = Icons.Outlined.Settings, - text = "Dashboard", - isSelected = selectedId == "Dashboard", - isEnabled = true, - ), - LeftPanelItem( - id = "Tables", - icon = Icons.Outlined.Settings, - text = "Tables", - isSelected = selectedId == "Tables", - isEnabled = true, - ), - ), - ), - ), -) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/leftpanel/LeftPannelSection.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/leftpanel/LeftPannelSection.kt deleted file mode 100644 index fe17ab05c..000000000 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/main/ui/model/leftpanel/LeftPannelSection.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.github.openflocon.flocondesktop.main.ui.model.leftpanel - -data class LeftPannelSection( - val title: String, - val items: List, -) diff --git a/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/Main.kt b/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/Main.kt index 7f2fd4887..5f0ae0d17 100644 --- a/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/Main.kt +++ b/FloconDesktop/composeApp/src/desktopMain/kotlin/io/github/openflocon/flocondesktop/Main.kt @@ -145,7 +145,7 @@ private fun FrameWindowScope.FloconMenu() { alwaysOnTop = true, onCloseRequest = { openLicenses = false }, ) { - io.github.openflocon.flocondesktop.main.ui.settings.AboutScreen( + io.github.openflocon.flocondesktop.app.ui.settings.AboutScreen( modifier = Modifier .fillMaxSize() .background(FloconTheme.colorPalette.primary), diff --git a/FloconDesktop/data/core/src/commonMain/kotlin/io/github/openflocon/data/core/network/DI.kt b/FloconDesktop/data/core/src/commonMain/kotlin/io/github/openflocon/data/core/network/DI.kt index 92fe3ddbb..287d8b4d6 100644 --- a/FloconDesktop/data/core/src/commonMain/kotlin/io/github/openflocon/data/core/network/DI.kt +++ b/FloconDesktop/data/core/src/commonMain/kotlin/io/github/openflocon/data/core/network/DI.kt @@ -7,7 +7,6 @@ import io.github.openflocon.domain.network.repository.NetworkBadQualityRepositor import io.github.openflocon.domain.network.repository.NetworkFilterRepository import io.github.openflocon.domain.network.repository.NetworkMocksRepository import io.github.openflocon.domain.network.repository.NetworkRepository -import io.github.openflocon.domain.network.repository.NetworkSettingsRepository import org.koin.core.module.dsl.bind import org.koin.core.module.dsl.singleOf import org.koin.dsl.bind @@ -20,6 +19,5 @@ internal val networkModule = module { bind() bind() bind() - bind() } } diff --git a/FloconDesktop/data/core/src/commonMain/kotlin/io/github/openflocon/data/core/network/datasource/NetworkSettingsLocalDataSource.kt b/FloconDesktop/data/core/src/commonMain/kotlin/io/github/openflocon/data/core/network/datasource/NetworkSettingsLocalDataSource.kt deleted file mode 100644 index a32c0a493..000000000 --- a/FloconDesktop/data/core/src/commonMain/kotlin/io/github/openflocon/data/core/network/datasource/NetworkSettingsLocalDataSource.kt +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.openflocon.data.core.network.datasource - -import io.github.openflocon.domain.device.models.DeviceIdAndPackageNameDomainModel -import io.github.openflocon.domain.network.models.NetworkSettingsDomainModel -import kotlinx.coroutines.flow.Flow - -interface NetworkSettingsLocalDataSource { - suspend fun getNetworkSettings( - deviceAndApp: DeviceIdAndPackageNameDomainModel, - ): NetworkSettingsDomainModel? - - fun observeNetworkSettings(deviceAndApp: DeviceIdAndPackageNameDomainModel): Flow - - suspend fun updateNetworkSettings( - deviceAndApp: DeviceIdAndPackageNameDomainModel, - newValue: NetworkSettingsDomainModel, - ) -} diff --git a/FloconDesktop/data/core/src/commonMain/kotlin/io/github/openflocon/data/core/network/repository/NetworkRepositoryImpl.kt b/FloconDesktop/data/core/src/commonMain/kotlin/io/github/openflocon/data/core/network/repository/NetworkRepositoryImpl.kt index 45e9e7f2d..07d74465b 100644 --- a/FloconDesktop/data/core/src/commonMain/kotlin/io/github/openflocon/data/core/network/repository/NetworkRepositoryImpl.kt +++ b/FloconDesktop/data/core/src/commonMain/kotlin/io/github/openflocon/data/core/network/repository/NetworkRepositoryImpl.kt @@ -6,7 +6,6 @@ import io.github.openflocon.data.core.network.datasource.NetworkLocalWebsocketDa import io.github.openflocon.data.core.network.datasource.NetworkMocksLocalDataSource import io.github.openflocon.data.core.network.datasource.NetworkQualityLocalDataSource import io.github.openflocon.data.core.network.datasource.NetworkRemoteDataSource -import io.github.openflocon.data.core.network.datasource.NetworkSettingsLocalDataSource import io.github.openflocon.domain.Protocol import io.github.openflocon.domain.common.DispatcherProvider import io.github.openflocon.domain.device.models.DeviceIdAndPackageNameDomainModel @@ -18,7 +17,6 @@ import io.github.openflocon.domain.network.models.FloconNetworkCallDomainModel import io.github.openflocon.domain.network.models.FloconNetworkResponseOnlyDomainModel import io.github.openflocon.domain.network.models.MockNetworkDomainModel import io.github.openflocon.domain.network.models.NetworkFilterDomainModel -import io.github.openflocon.domain.network.models.NetworkSettingsDomainModel import io.github.openflocon.domain.network.models.NetworkSortDomainModel import io.github.openflocon.domain.network.models.NetworkWebsocketId import io.github.openflocon.domain.network.models.isImage @@ -26,7 +24,6 @@ import io.github.openflocon.domain.network.repository.NetworkBadQualityRepositor import io.github.openflocon.domain.network.repository.NetworkImageRepository import io.github.openflocon.domain.network.repository.NetworkMocksRepository import io.github.openflocon.domain.network.repository.NetworkRepository -import io.github.openflocon.domain.network.repository.NetworkSettingsRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.withContext @@ -37,13 +34,11 @@ class NetworkRepositoryImpl( private val networkLocalWebsocketDataSource: NetworkLocalWebsocketDataSource, private val networkMocksLocalDataSource: NetworkMocksLocalDataSource, private val networkQualityLocalDataSource: NetworkQualityLocalDataSource, - private val networkSettingsLocalDataSource: NetworkSettingsLocalDataSource, private val networkImageRepository: NetworkImageRepository, private val networkRemoteDataSource: NetworkRemoteDataSource, ) : NetworkRepository, NetworkMocksRepository, MessagesReceiverRepository, - NetworkSettingsRepository, NetworkBadQualityRepository { override val pluginName = listOf(Protocol.FromDevice.Network.Plugin) @@ -57,7 +52,7 @@ class NetworkRepositoryImpl( deviceIdAndPackageName = deviceIdAndPackageName, mocks = getAllEnabledMocks(deviceIdAndPackageName = deviceIdAndPackageName), ) - if(isNewDevice) { + if (isNewDevice) { networkQualityLocalDataSource.prepopulate( deviceIdAndPackageName = deviceIdAndPackageName, ) @@ -122,6 +117,7 @@ class NetworkRepositoryImpl( ids = networkRemoteDataSource.getWebsocketClientsIds(message), ) } + Protocol.FromDevice.Network.Method.LogWebSocketEvent -> { networkRemoteDataSource.getWebSocketData(message) ?.let { wenSocketEvent -> @@ -218,7 +214,7 @@ class NetworkRepositoryImpl( ): FloconNetworkCallDomainModel? { val isRequestGraphQl = request.request.specificInfos is FloconNetworkCallDomainModel.Request.SpecificInfos.GraphQl return try { - val response = if(isRequestGraphQl) { + val response = if (isRequestGraphQl) { when (val r = receivedResponse.response) { is FloconNetworkCallDomainModel.Response.Success -> { // specific case : map to graphQl if needed @@ -231,6 +227,7 @@ class NetworkRepositoryImpl( ) ) } + else -> r } } @@ -418,32 +415,6 @@ class NetworkRepositoryImpl( } } - override suspend fun getNetworkSettings(deviceAndApp: DeviceIdAndPackageNameDomainModel): NetworkSettingsDomainModel? { - return withContext(dispatcherProvider.data) { - networkSettingsLocalDataSource.getNetworkSettings( - deviceAndApp = deviceAndApp, - ) - } - } - - override fun observeNetworkSettings(deviceAndApp: DeviceIdAndPackageNameDomainModel): Flow { - return networkSettingsLocalDataSource.observeNetworkSettings( - deviceAndApp = deviceAndApp, - ).flowOn(dispatcherProvider.data) - } - - override suspend fun updateNetworkSettings( - deviceAndApp: DeviceIdAndPackageNameDomainModel, - newValue: NetworkSettingsDomainModel - ) { - withContext(dispatcherProvider.data) { - networkSettingsLocalDataSource.updateNetworkSettings( - deviceAndApp = deviceAndApp, - newValue = newValue, - ) - } - } - override suspend fun observeWebsocketClientsIds(deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel): Flow> { return networkLocalWebsocketDataSource.observeWebsocketClients( deviceIdAndPackageName = deviceIdAndPackageName, diff --git a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/DI.kt b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/DI.kt index 33491fd53..fd3cd5d5b 100644 --- a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/DI.kt +++ b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/DI.kt @@ -5,13 +5,11 @@ import io.github.openflocon.data.core.network.datasource.NetworkLocalDataSource import io.github.openflocon.data.core.network.datasource.NetworkLocalWebsocketDataSource import io.github.openflocon.data.core.network.datasource.NetworkMocksLocalDataSource import io.github.openflocon.data.core.network.datasource.NetworkQualityLocalDataSource -import io.github.openflocon.data.core.network.datasource.NetworkSettingsLocalDataSource import io.github.openflocon.data.local.network.datasource.BadQualityConfigLocalDataSourceImpl import io.github.openflocon.data.local.network.datasource.NetworkFilterLocalDataSourceRoom import io.github.openflocon.data.local.network.datasource.NetworkLocalDataSourceRoom import io.github.openflocon.data.local.network.datasource.NetworkLocalWebsocketDataSourceRam import io.github.openflocon.data.local.network.datasource.NetworkMocksLocalDataSourceImpl -import io.github.openflocon.data.local.network.datasource.NetworkSettingsLocalDataSourceRoom import org.koin.core.module.dsl.singleOf import org.koin.dsl.bind import org.koin.dsl.module @@ -21,6 +19,5 @@ internal val networkModule = module { singleOf(::NetworkFilterLocalDataSourceRoom) bind NetworkFilterLocalDataSource::class singleOf(::NetworkMocksLocalDataSourceImpl) bind NetworkMocksLocalDataSource::class singleOf(::BadQualityConfigLocalDataSourceImpl) bind NetworkQualityLocalDataSource::class - singleOf(::NetworkSettingsLocalDataSourceRoom) bind NetworkSettingsLocalDataSource::class singleOf(::NetworkLocalWebsocketDataSourceRam) bind NetworkLocalWebsocketDataSource::class } diff --git a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/datasource/NetworkSettingsLocalDataSourceRoom.kt b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/datasource/NetworkSettingsLocalDataSourceRoom.kt deleted file mode 100644 index a02b1e620..000000000 --- a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/datasource/NetworkSettingsLocalDataSourceRoom.kt +++ /dev/null @@ -1,103 +0,0 @@ -package io.github.openflocon.data.local.network.datasource - -import io.github.openflocon.data.core.network.datasource.NetworkSettingsLocalDataSource -import io.github.openflocon.data.local.network.dao.NetworkSettingsDao -import io.github.openflocon.data.local.network.models.NetworkSettingsEntity -import io.github.openflocon.domain.device.models.DeviceIdAndPackageNameDomainModel -import io.github.openflocon.domain.network.models.NetworkSettingsDomainModel -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json - -class NetworkSettingsLocalDataSourceRoom( - private val networkSettingsDao: NetworkSettingsDao, - private val json: Json, -) : NetworkSettingsLocalDataSource { - - override suspend fun getNetworkSettings( - deviceAndApp: DeviceIdAndPackageNameDomainModel, - ): NetworkSettingsDomainModel? { - return networkSettingsDao.get( - deviceId = deviceAndApp.deviceId, - packageName = deviceAndApp.packageName - ).let { - it?.toDomain( - json = json, - ) - } - } - - override fun observeNetworkSettings(deviceAndApp: DeviceIdAndPackageNameDomainModel): Flow { - return networkSettingsDao.observe( - deviceId = deviceAndApp.deviceId, - packageName = deviceAndApp.packageName - ).map { - it?.toDomain( - json = json, - ) - } - } - - override suspend fun updateNetworkSettings( - deviceAndApp: DeviceIdAndPackageNameDomainModel, - newValue: NetworkSettingsDomainModel - ) { - networkSettingsDao.insertOrUpdate( - newValue.toEntity( - json = json, - deviceAndApp = deviceAndApp, - ) - ) - } -} - -private fun NetworkSettingsDomainModel.toEntity( - deviceAndApp: DeviceIdAndPackageNameDomainModel, - json: Json, -): NetworkSettingsEntity { - val saved = try { - json.encodeToString(this.toSaved()) - } catch (t: Throwable) { - t.printStackTrace() - "" - } - return NetworkSettingsEntity( - deviceId = deviceAndApp.deviceId, - packageName = deviceAndApp.packageName, - valueAsJson = saved, - ) -} - -@Serializable -internal data class NetworkSettingsSavedModel( - val displayOldSessions: Boolean, - val autoScroll: Boolean, - val invertList: Boolean, -) - -private fun NetworkSettingsDomainModel.toSaved() = NetworkSettingsSavedModel( - displayOldSessions = displayOldSessions, - autoScroll = autoScroll, - invertList = invertList, -) - -private fun NetworkSettingsEntity.toDomain( - json: Json, -): NetworkSettingsDomainModel { - val saved = try { - json.decodeFromString(this.valueAsJson) - } catch (t: Throwable) { - t.printStackTrace() - NetworkSettingsSavedModel( - displayOldSessions = false, - autoScroll = false, - invertList = false, - ) - } - return NetworkSettingsDomainModel( - displayOldSessions = saved.displayOldSessions, - autoScroll = saved.autoScroll, - invertList = saved.invertList, - ) -} diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/models/settings/NetworkSettings.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/models/settings/NetworkSettings.kt new file mode 100644 index 000000000..156164cae --- /dev/null +++ b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/models/settings/NetworkSettings.kt @@ -0,0 +1,8 @@ +package io.github.openflocon.domain.models.settings + +data class NetworkSettings( + val pinnedDetails: Boolean, + val displayOldSessions: Boolean, + val autoScroll: Boolean, + val invertList: Boolean +) diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/DI.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/DI.kt index b78268705..6e4d3aba3 100644 --- a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/DI.kt +++ b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/DI.kt @@ -5,11 +5,11 @@ import io.github.openflocon.domain.network.usecase.ExportNetworkCallsToCsvUseCas import io.github.openflocon.domain.network.usecase.GenerateCurlCommandUseCase import io.github.openflocon.domain.network.usecase.GetNetworkFilterUseCase import io.github.openflocon.domain.network.usecase.GetNetworkRequestsUseCase +import io.github.openflocon.domain.network.usecase.ObserveNetworkFilterUseCase import io.github.openflocon.domain.network.usecase.ObserveNetworkRequestsByIdUseCase import io.github.openflocon.domain.network.usecase.ObserveNetworkRequestsUseCase -import io.github.openflocon.domain.network.usecase.ObserveNetworkFilterUseCase -import io.github.openflocon.domain.network.usecase.RemoveNetworkRequestUseCase import io.github.openflocon.domain.network.usecase.RemoveHttpRequestsBeforeUseCase +import io.github.openflocon.domain.network.usecase.RemoveNetworkRequestUseCase import io.github.openflocon.domain.network.usecase.RemoveOldSessionsNetworkRequestUseCase import io.github.openflocon.domain.network.usecase.ResetCurrentDeviceHttpRequestsUseCase import io.github.openflocon.domain.network.usecase.UpdateNetworkFilterUseCase @@ -17,8 +17,8 @@ import io.github.openflocon.domain.network.usecase.badquality.DeleteBadQualityUs import io.github.openflocon.domain.network.usecase.badquality.ObserveAllNetworkBadQualitiesUseCase import io.github.openflocon.domain.network.usecase.badquality.ObserveNetworkBadQualityUseCase import io.github.openflocon.domain.network.usecase.badquality.SaveNetworkBadQualityUseCase -import io.github.openflocon.domain.network.usecase.badquality.SetupNetworkBadQualityUseCase import io.github.openflocon.domain.network.usecase.badquality.SetNetworkBadQualityEnabledConfigUseCase +import io.github.openflocon.domain.network.usecase.badquality.SetupNetworkBadQualityUseCase import io.github.openflocon.domain.network.usecase.mocks.AddNetworkMocksUseCase import io.github.openflocon.domain.network.usecase.mocks.DeleteNetworkMocksUseCase import io.github.openflocon.domain.network.usecase.mocks.GenerateNetworkMockFromNetworkCallUseCase @@ -29,9 +29,6 @@ import io.github.openflocon.domain.network.usecase.mocks.SendNetworkWebsocketMoc import io.github.openflocon.domain.network.usecase.mocks.SetupNetworkMocksUseCase import io.github.openflocon.domain.network.usecase.mocks.UpdateNetworkMockIsEnabledUseCase import io.github.openflocon.domain.network.usecase.mocks.UpdateNetworkMocksDeviceUseCase -import io.github.openflocon.domain.network.usecase.settings.GetNetworkSettingsUseCase -import io.github.openflocon.domain.network.usecase.settings.ObserveNetworkSettingsUseCase -import io.github.openflocon.domain.network.usecase.settings.UpdateNetworkSettingsUseCase import org.koin.core.module.dsl.factoryOf import org.koin.dsl.module @@ -68,8 +65,4 @@ internal val networkModule = module { factoryOf(::SetupNetworkBadQualityUseCase) factoryOf(::SetNetworkBadQualityEnabledConfigUseCase) factoryOf(::ObserveAllNetworkBadQualitiesUseCase) - // settings - factoryOf(::GetNetworkSettingsUseCase) - factoryOf(::UpdateNetworkSettingsUseCase) - factoryOf(::ObserveNetworkSettingsUseCase) } diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/models/NetworkSettingsDomainModel.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/models/NetworkSettingsDomainModel.kt deleted file mode 100644 index 826a4790c..000000000 --- a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/models/NetworkSettingsDomainModel.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.github.openflocon.domain.network.models - -data class NetworkSettingsDomainModel( - val displayOldSessions: Boolean, - val autoScroll: Boolean, - val invertList: Boolean, -) diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/repository/NetworkSettingsRepository.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/repository/NetworkSettingsRepository.kt deleted file mode 100644 index 9c76f6aa0..000000000 --- a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/repository/NetworkSettingsRepository.kt +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.openflocon.domain.network.repository - -import io.github.openflocon.domain.device.models.DeviceIdAndPackageNameDomainModel -import io.github.openflocon.domain.network.models.NetworkSettingsDomainModel -import kotlinx.coroutines.flow.Flow - -interface NetworkSettingsRepository { - suspend fun getNetworkSettings( - deviceAndApp: DeviceIdAndPackageNameDomainModel, - ): NetworkSettingsDomainModel? - - fun observeNetworkSettings(deviceAndApp: DeviceIdAndPackageNameDomainModel): Flow - - suspend fun updateNetworkSettings( - deviceAndApp: DeviceIdAndPackageNameDomainModel, - newValue: NetworkSettingsDomainModel, - ) -} diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/usecase/settings/GetNetworkSettingsUseCase.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/usecase/settings/GetNetworkSettingsUseCase.kt deleted file mode 100644 index 5eb922ea6..000000000 --- a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/usecase/settings/GetNetworkSettingsUseCase.kt +++ /dev/null @@ -1,21 +0,0 @@ -package io.github.openflocon.domain.network.usecase.settings - -import io.github.openflocon.domain.device.usecase.GetCurrentDeviceIdAndPackageNameUseCase -import io.github.openflocon.domain.network.models.MockNetworkDomainModel -import io.github.openflocon.domain.network.models.NetworkSettingsDomainModel -import io.github.openflocon.domain.network.repository.NetworkMocksRepository -import io.github.openflocon.domain.network.repository.NetworkSettingsRepository -import io.github.openflocon.domain.network.usecase.mocks.SetupNetworkMocksUseCase - -class GetNetworkSettingsUseCase( - private val getCurrentDeviceIdAndPackageNameUseCase: GetCurrentDeviceIdAndPackageNameUseCase, - private val networkSettingsRepository: NetworkSettingsRepository, -) { - suspend operator fun invoke() : NetworkSettingsDomainModel? { - return getCurrentDeviceIdAndPackageNameUseCase()?.let { deviceIdAndPackageName -> - networkSettingsRepository.getNetworkSettings( - deviceAndApp = deviceIdAndPackageName, - ) - } - } -} diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/usecase/settings/ObserveNetworkSettingsUseCase.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/usecase/settings/ObserveNetworkSettingsUseCase.kt deleted file mode 100644 index 20bfa6977..000000000 --- a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/usecase/settings/ObserveNetworkSettingsUseCase.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.openflocon.domain.network.usecase.settings - -import io.github.openflocon.domain.device.usecase.ObserveCurrentDeviceIdAndPackageNameUseCase -import io.github.openflocon.domain.device.usecase.ObserveCurrentDeviceIdUseCase -import io.github.openflocon.domain.models.TextFilterStateDomainModel -import io.github.openflocon.domain.network.models.NetworkSettingsDomainModel -import io.github.openflocon.domain.network.models.NetworkTextFilterColumns -import io.github.openflocon.domain.network.repository.NetworkFilterRepository -import io.github.openflocon.domain.network.repository.NetworkSettingsRepository -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf - -class ObserveNetworkSettingsUseCase( - private val observeCurrentDeviceIdAndPackageNameUseCase: ObserveCurrentDeviceIdAndPackageNameUseCase, - private val networkSettingsRepository: NetworkSettingsRepository, -) { - operator fun invoke(): Flow = - observeCurrentDeviceIdAndPackageNameUseCase().flatMapLatest { current -> - if (current == null) flowOf(null) - else networkSettingsRepository.observeNetworkSettings(deviceAndApp = current) - }.distinctUntilChanged() -} diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/usecase/settings/UpdateNetworkSettingsUseCase.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/usecase/settings/UpdateNetworkSettingsUseCase.kt deleted file mode 100644 index 619eb2e28..000000000 --- a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/usecase/settings/UpdateNetworkSettingsUseCase.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.openflocon.domain.network.usecase.settings - -import io.github.openflocon.domain.device.usecase.GetCurrentDeviceIdAndPackageNameUseCase -import io.github.openflocon.domain.network.models.MockNetworkDomainModel -import io.github.openflocon.domain.network.models.NetworkSettingsDomainModel -import io.github.openflocon.domain.network.repository.NetworkMocksRepository -import io.github.openflocon.domain.network.repository.NetworkSettingsRepository -import io.github.openflocon.domain.network.usecase.mocks.SetupNetworkMocksUseCase - -class UpdateNetworkSettingsUseCase( - private val getCurrentDeviceIdAndPackageNameUseCase: GetCurrentDeviceIdAndPackageNameUseCase, - private val networkSettingsRepository: NetworkSettingsRepository, -) { - suspend operator fun invoke( - settings: NetworkSettingsDomainModel, - ) { - getCurrentDeviceIdAndPackageNameUseCase()?.let { deviceIdAndPackageName -> - networkSettingsRepository.updateNetworkSettings( - deviceAndApp = deviceIdAndPackageName, - newValue = settings, - ) - } - } -} diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/settings/repository/SettingsRepository.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/settings/repository/SettingsRepository.kt index 2c59a6c1f..f20aa2a3d 100644 --- a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/settings/repository/SettingsRepository.kt +++ b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/settings/repository/SettingsRepository.kt @@ -1,9 +1,13 @@ package io.github.openflocon.domain.settings.repository +import io.github.openflocon.domain.models.settings.NetworkSettings import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow interface SettingsRepository { + var networkSettings: NetworkSettings + val networkSettingsFlow: Flow + fun getAdbPath(): String? suspend fun setAdbPath(path: String) diff --git a/FloconDesktop/gradle/libs.versions.toml b/FloconDesktop/gradle/libs.versions.toml index ff661dd00..f63b1399b 100644 --- a/FloconDesktop/gradle/libs.versions.toml +++ b/FloconDesktop/gradle/libs.versions.toml @@ -8,11 +8,11 @@ androidx-appcompat = "1.7.1" androidx-constraintlayout = "2.2.1" androidx-core = "1.17.0" androidx-espresso = "3.7.0" -androidx-lifecycle = "2.9.2" +androidx-lifecycle = "2.10.0-alpha03" androidx-testExt = "1.3.0" coil = "3.3.0" composeHotReload = "1.0.0-beta05" -composeMultiplatform = "1.9.0" +composeMultiplatform = "1.10.0-alpha03" junit = "4.13.2" kermit = "2.0.8" koin = "4.1.0" @@ -36,6 +36,11 @@ uiToolingPreviewDesktop = "1.8.2" buildconfig = "5.6.8" paging = "3.3.2" +# TODO Sort +nav3Core = "1.0.0-alpha04+dev3147"#"1.0.0-alpha03" +material3-adaptive = "1.0.0-alpha03" +kotlinxSerializationCore = "1.8.1" + [libraries] kermit = { module = "co.touchlab:kermit", version.ref = "kermit" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } @@ -49,6 +54,7 @@ androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayo androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } androidx-lifecycle-viewmodel = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel", version.ref = "androidx-lifecycle" } androidx-lifecycle-runtimeCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } +androidx-lifecycle-nav3 = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "androidx-lifecycle" } coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } kotlinx-coroutinesCore = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } kotlinx-coroutinesSwing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } @@ -91,6 +97,15 @@ androidx-core = { group = "androidx.test", name = "core", version.ref = "core" } other-jsontree = { module = "com.sebastianneubauer.jsontree:jsontree", version.ref = "other-jsontree"} ui-tooling-preview-desktop = { module = "org.jetbrains.compose.ui:ui-tooling-preview-desktop", version.ref = "uiToolingPreviewDesktop" } +# TODO Sort +#compose-navigation3-runtime = { module = "org.jetbrains.androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" } +compose-navigation3-ui = { module = "org.jetbrains.androidx.navigation3:navigation3-ui", version.ref = "nav3Core" } + +material3-adaptive = { module = "org.jetbrains.compose.material3.adaptive:adaptive-navigation3", version.ref = "material3-adaptive" } + +# Optional add-on libraries +kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerializationCore" } + androidx-paging-common = { module = "androidx.paging:paging-common", version.ref = "paging" } androidx-paging-compose = { module = "androidx.paging:paging-compose", version = "3.4.0-alpha04" } diff --git a/FloconDesktop/library/designsystem/build.gradle.kts b/FloconDesktop/library/designsystem/build.gradle.kts index 98a1843aa..7b13aa189 100644 --- a/FloconDesktop/library/designsystem/build.gradle.kts +++ b/FloconDesktop/library/designsystem/build.gradle.kts @@ -31,6 +31,13 @@ kotlin { api(compose.components.resources) api(compose.components.uiToolingPreview) api(libs.other.jsontree) + + api(libs.compose.navigation3.ui) +// api(libs.compose.navigation3.runtime) + + // Not KMP yet +// api(libs.androidx.lifecycle.viewmodel.navigation3) + api(libs.kotlinx.serialization.core) api("com.composables:core:1.43.1") } } diff --git a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconAnimateVisibility.kt b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconAnimateVisibility.kt new file mode 100644 index 000000000..26a42fc96 --- /dev/null +++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconAnimateVisibility.kt @@ -0,0 +1,40 @@ +package io.github.openflocon.library.designsystem.components + +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedContentScope +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.ContentTransform +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun FloconAnimateVisibility( + state: T?, + modifier: Modifier = Modifier, + transitionSpec: AnimatedContentTransitionScope.() -> ContentTransform = { + (fadeIn(animationSpec = tween(220, delayMillis = 90)) + slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Start, + animationSpec = tween(220, delayMillis = 90) + )) + .togetherWith(fadeOut(animationSpec = tween(90))) + }, + content: @Composable AnimatedContentScope.(T & Any) -> Unit +) { + AnimatedContent( + targetState = state, + transitionSpec = transitionSpec, + contentKey = { it != null }, + modifier = modifier + ) { + if (it != null) { + content(it) + } else { + Box(Modifier) + } + } +} diff --git a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconDialog.kt b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconDialog.kt index 0abcb678c..adb2f4c85 100644 --- a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconDialog.kt +++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconDialog.kt @@ -3,13 +3,10 @@ package io.github.openflocon.library.designsystem.components 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.RowScope import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape @@ -18,7 +15,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties @@ -29,11 +25,12 @@ import io.github.openflocon.library.designsystem.components.escape.EscapeHandler fun FloconDialog( onDismissRequest: () -> Unit, modifier: Modifier = Modifier, + properties: DialogProperties = DialogProperties(), content: @Composable () -> Unit ) { Dialog( onDismissRequest = onDismissRequest, - properties = DialogProperties(), + properties = properties, ) { EscapeHandler { onDismissRequest() diff --git a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconFeature.kt b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconFeature.kt index d1ae5aa63..ea7508ccc 100644 --- a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconFeature.kt +++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconFeature.kt @@ -3,6 +3,7 @@ package io.github.openflocon.library.designsystem.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -11,11 +12,12 @@ import androidx.compose.ui.unit.dp @Composable fun FloconFeature( modifier: Modifier = Modifier, + contentPadding: PaddingValues = PaddingValues(8.dp), // TODO Remove content: @Composable ColumnScope.() -> Unit ) { Column( verticalArrangement = Arrangement.spacedBy(8.dp), - modifier = modifier.padding(8.dp), + modifier = modifier.padding(contentPadding), content = content ) } diff --git a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconIconButton.kt b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconIconButton.kt index 3b4ddcb5a..943b02e7b 100644 --- a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconIconButton.kt +++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconIconButton.kt @@ -16,6 +16,8 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.selection.toggleable +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Close import androidx.compose.material3.LocalContentColor import androidx.compose.material3.Text import androidx.compose.runtime.Composable diff --git a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconOverflow.kt b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconOverflow.kt index 4f970d478..ba13595fc 100644 --- a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconOverflow.kt +++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconOverflow.kt @@ -4,7 +4,9 @@ import androidx.compose.foundation.layout.ColumnScope import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -12,6 +14,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import com.composeunstyled.DropdownPanelAnchor +import io.github.openflocon.library.designsystem.FloconTheme @Immutable data class FloconOverflowItem( @@ -35,7 +38,7 @@ data class FloconOverflowItem( @Composable fun FloconOverflow( modifier: Modifier = Modifier, - content: @Composable ColumnScope.() -> Unit + content: @Composable ColumnScope.(close: () -> Unit) -> Unit ) { var expanded by remember { mutableStateOf(false) } @@ -56,6 +59,8 @@ fun FloconOverflow( onDismissRequest = { expanded = false }, modifier = modifier ) { - content() + CompositionLocalProvider(LocalContentColor provides FloconTheme.colorPalette.onPrimary) { + content { expanded = false } + } } } diff --git a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconScaffold.kt b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconScaffold.kt index b210a3218..ce91a5874 100644 --- a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconScaffold.kt +++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/FloconScaffold.kt @@ -1,6 +1,9 @@ package io.github.openflocon.library.designsystem.components import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.foundation.layout.systemBars import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -25,6 +28,7 @@ fun FloconScaffold( snackbarHost = snackbarHost, containerColor = containerColor, contentColor = contentColor, + contentWindowInsets = WindowInsets.systemBars, content = content ) } diff --git a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/panel/FloconPanel.kt b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/panel/FloconPanel.kt index 45321550e..13f2ce61e 100644 --- a/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/panel/FloconPanel.kt +++ b/FloconDesktop/library/designsystem/src/commonMain/kotlin/io/github/openflocon/library/designsystem/components/panel/FloconPanel.kt @@ -6,8 +6,9 @@ import androidx.compose.animation.core.VectorConverter import androidx.compose.animation.core.animateTo import androidx.compose.animation.core.tween import androidx.compose.foundation.border +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.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding @@ -15,11 +16,12 @@ import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Close import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer @@ -29,89 +31,154 @@ import io.github.openflocon.library.designsystem.FloconTheme import io.github.openflocon.library.designsystem.components.FloconIcon import io.github.openflocon.library.designsystem.components.FloconIconTonalButton import io.github.openflocon.library.designsystem.components.escape.EscapeHandler -import io.github.openflocon.library.designsystem.components.panel.LocalFloconPanelController +import kotlinx.coroutines.launch private const val AnimDuration = 500 -private val PanelWidth = 500.dp +val PanelWidth = 500.dp + +@Stable +class FloconPanelState internal constructor(initialValue: Boolean) { + + internal var expanded by mutableStateOf(initialValue) + + val translationX = AnimationState(typeConverter = Dp.VectorConverter, PanelWidth) + + suspend fun show() { + expanded = true + translationX.animateTo(0.dp, animationSpec = tween(AnimDuration, easing = EaseOutExpo)) + } + + suspend fun hide() { + translationX.animateTo(PanelWidth, animationSpec = tween(AnimDuration, easing = EaseOutExpo)) + expanded = false + } + +} + +@Composable +fun rememberFloconPanelState(initialValue: Boolean = false): FloconPanelState { + return remember { + println("rememberFloconPanelState") + FloconPanelState(initialValue) + } +} + + +interface FloconPanelScope { + val state: FloconPanelState + + fun Modifier.animatePanelAction(): Modifier + +} + +private class FloconPanelScopeImpl( + override val state: FloconPanelState +) : FloconPanelScope { + + @Stable + override fun Modifier.animatePanelAction(): Modifier = graphicsLayer { + this.alpha = 1f - state.translationX.value.div(PanelWidth) + } + +} + @Composable fun FloconPanel( - expanded: Boolean, - onClose: (() -> Unit)? = null, - content: @Composable BoxScope.() -> Unit + state: FloconPanelState, + onDismissRequest: () -> Unit, + actions: @Composable FloconPanelScope.() -> Unit = {}, + content: @Composable FloconPanelScope.() -> Unit ) { - var innerExpanded by remember { mutableStateOf(expanded) } - val translationX = remember { AnimationState(typeConverter = Dp.VectorConverter, PanelWidth) } + val scope = remember(state) { FloconPanelScopeImpl(state) } + val coroutineScope = rememberCoroutineScope() - suspend fun hide() { - translationX.animateTo(PanelWidth, animationSpec = tween(AnimDuration, easing = EaseOutExpo)) - innerExpanded = false + LaunchedEffect(Unit) { + if (state.expanded) { + state.show() + } } - LaunchedEffect(expanded) { - if (expanded) { - innerExpanded = true - translationX.animateTo(0.dp, animationSpec = tween(AnimDuration, easing = EaseOutExpo)) - } else { - hide() + LaunchedEffect(state.expanded) { + if (!state.expanded) { + onDismissRequest() } } - val floconPanelController = LocalFloconPanelController.current + EscapeHandler { + coroutineScope.launch { + state.hide() + onDismissRequest() + } + true + } - if (innerExpanded) { - DisposableEffect(Unit) { - floconPanelController.display { - if (onClose != null) { - EscapeHandler { - onClose() - true - } + Row( + modifier = Modifier + .fillMaxHeight() + ) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.padding(8.dp), + content = { scope.actions() } + ) + Box( + modifier = Modifier + .width(PanelWidth) + .fillMaxHeight() + .graphicsLayer { + this.translationX = state.translationX.value.toPx() } + .border(width = 1.dp, color = FloconTheme.colorPalette.surface) + ) { + scope.content() + } + } +} - Row( - modifier = Modifier - .fillMaxHeight() - ) { - if (onClose != null) { - Box( - modifier = Modifier.padding(8.dp) - ) { - FloconIconTonalButton( - onClick = onClose, - modifier = Modifier - .graphicsLayer { - this.alpha = 1f - translationX.value.div(PanelWidth) - } - ) { - FloconIcon( - Icons.Outlined.Close - ) - } - } - } - Box( - modifier = Modifier - .width(PanelWidth) - .fillMaxHeight() - .graphicsLayer { - this.translationX = translationX.value.toPx() - } - .border(width = 1.dp, color = FloconTheme.colorPalette.surface), - content = content - ) - } - } - onDispose { floconPanelController.hide() } +// TODO Rework +@Composable +fun FloconPanel( + expanded: Boolean, + onDismissRequest: () -> Unit, + actions: @Composable FloconPanelScope.() -> Unit = {}, + content: @Composable FloconPanelScope.() -> Unit +) { + var innerExpand by remember { mutableStateOf(expanded) } + val state = rememberFloconPanelState(expanded) + val scope = rememberCoroutineScope() + + LaunchedEffect(expanded) { + if (expanded) { + innerExpand = true + state.show() + } else { + state.hide() + innerExpand = false } } + + if (innerExpand) { + FloconPanel( + state = state, + onDismissRequest = { + scope.launch { + state.hide() + innerExpand = false + onDismissRequest() + } + }, + actions = actions, + content = content + ) + } } @Composable fun FloconPanel( contentState: T, onClose: (() -> Unit)? = null, - content: @Composable BoxScope.(T & Any) -> Unit + content: @Composable FloconPanelScope.(T & Any) -> Unit ) { var rememberTarget by remember { mutableStateOf(contentState) } @@ -123,7 +190,19 @@ fun FloconPanel( FloconPanel( expanded = contentState != null, - onClose = onClose, + onDismissRequest = { onClose?.invoke() }, + actions = { + if (onClose != null) { + FloconIconTonalButton( + onClick = onClose, + modifier = Modifier + ) { + FloconIcon( + Icons.Outlined.Close + ) + } + } + }, ) { rememberTarget?.let { content(this, it) } } diff --git a/FloconDesktop/navigation/.gitignore b/FloconDesktop/navigation/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/FloconDesktop/navigation/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/FloconDesktop/navigation/build.gradle.kts b/FloconDesktop/navigation/build.gradle.kts new file mode 100644 index 000000000..b5e0dda84 --- /dev/null +++ b/FloconDesktop/navigation/build.gradle.kts @@ -0,0 +1,50 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.KotlinVersion + +plugins { + alias(libs.plugins.kotlinMultiplatform) + + alias(libs.plugins.composeCompiler) + alias(libs.plugins.composeMultiplatform) + + alias(libs.plugins.kotlinx.serialization) +} + +kotlin { + jvmToolchain(21) + + compilerOptions { + // Pour Kotlin 1.9+ + freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") + freeCompilerArgs.add("-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi") + freeCompilerArgs.add("-Xcontext-parameters") + freeCompilerArgs.add("-Xcontext-sensitive-resolution") + } + + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + languageVersion.set(KotlinVersion.KOTLIN_2_2) + } + + jvm("desktop") + + // Source set declarations. + // Declaring a target automatically creates a source set with the same name. By default, the + // Kotlin Gradle Plugin creates additional source sets that depend on each other, since it is + // common to share sources between related targets. + // See: https://kotlinlang.org/docs/multiplatform-hierarchy.html + sourceSets { + commonMain.dependencies { + implementation(projects.library.designsystem) + + implementation(project.dependencies.platform(libs.koin.bom)) + implementation(libs.koin.core) + implementation(libs.koin.compose.viewmodel) + + implementation(libs.androidx.lifecycle.nav3) + + api(libs.kotlinx.serialization.core) + } + } + +} diff --git a/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/FloconNavigation.kt b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/FloconNavigation.kt new file mode 100644 index 000000000..4d1e4aef6 --- /dev/null +++ b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/FloconNavigation.kt @@ -0,0 +1,39 @@ +package io.github.openflocon.navigation + +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator +import androidx.navigation3.runtime.EntryProviderScope +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator +import androidx.navigation3.scene.SceneStrategy +import androidx.navigation3.scene.SinglePaneSceneStrategy +import androidx.navigation3.ui.NavDisplay + +@Composable +fun FloconNavigation( + navigationState: FloconNavigationState, + modifier: Modifier = Modifier, + sceneStrategy: SceneStrategy = SinglePaneSceneStrategy(), + builder: EntryProviderScope.() -> Unit +) { + NavDisplay( + backStack = navigationState.stack, + transitionSpec = { fadeIn() togetherWith fadeOut() }, + popTransitionSpec = { fadeIn() togetherWith fadeOut() }, + predictivePopTransitionSpec = { fadeIn() togetherWith fadeOut() }, + entryDecorators = listOf( + rememberSaveableStateHolderNavEntryDecorator(), + rememberViewModelStoreNavEntryDecorator() + ), + sceneStrategy = sceneStrategy, + onBack = { navigationState.back(1) }, // TODO + entryProvider = entryProvider { + builder() + }, + modifier = modifier + ) +} diff --git a/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/FloconNavigationState.kt b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/FloconNavigationState.kt new file mode 100644 index 000000000..4429a8cfa --- /dev/null +++ b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/FloconNavigationState.kt @@ -0,0 +1,53 @@ +package io.github.openflocon.navigation + +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.snapshots.SnapshotStateList + +@Immutable +interface FloconNavigationState { + val stack: SnapshotStateList + + fun navigate(route: T) + + // TODO Remove, since it's multiple window handling + // Maybe not if we instansiate one per window + fun back(count: Int = 1) + + fun remove(route: FloconRoute) + +} + +@Immutable +class MainFloconNavigationState(initialScreen: FloconRoute = LoadingRoute) : FloconNavigationState { + + private val _stack = mutableStateListOf(initialScreen) + override val stack: SnapshotStateList = _stack + + override fun navigate(route: FloconRoute) { + if (route is PanelRoute) { + val index = _stack.indexOfFirst { it is PanelRoute } + + if (index != -1) { + _stack[index] = route + } else { + _stack.add(route) + } + } else { + _stack.add(route) + } + } + + override fun back(count: Int) { + repeat(count) { _stack.removeLast() } + } + + override fun remove(route: FloconRoute) { + _stack.remove(route) + } + + fun menu(route: FloconRoute) { + _stack[0] = route + } + +} diff --git a/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/FloconRoute.kt b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/FloconRoute.kt new file mode 100644 index 000000000..650530222 --- /dev/null +++ b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/FloconRoute.kt @@ -0,0 +1,7 @@ +package io.github.openflocon.navigation + +import androidx.navigation3.runtime.NavKey + +interface FloconRoute : NavKey + +interface PanelRoute : NavKey diff --git a/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/LoadingRoute.kt b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/LoadingRoute.kt new file mode 100644 index 000000000..0b14b0cf9 --- /dev/null +++ b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/LoadingRoute.kt @@ -0,0 +1,6 @@ +package io.github.openflocon.navigation + +import kotlinx.serialization.Serializable + +@Serializable +object LoadingRoute : FloconRoute diff --git a/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/DialogScene.kt b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/DialogScene.kt new file mode 100644 index 000000000..4228108ab --- /dev/null +++ b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/DialogScene.kt @@ -0,0 +1,100 @@ +package io.github.openflocon.navigation.scene + +import androidx.compose.runtime.Composable +import androidx.compose.ui.window.DialogProperties +import androidx.navigation3.runtime.NavEntry +import androidx.navigation3.scene.OverlayScene +import androidx.navigation3.scene.Scene +import androidx.navigation3.scene.SceneStrategy +import androidx.navigation3.scene.SceneStrategyScope +import io.github.openflocon.library.designsystem.components.FloconDialog +import io.github.openflocon.navigation.scene.DialogSceneStrategy.Companion.dialog + +/** + * Copy pasta from default scene strategy, to impl ours + */ +internal class DialogScene( + override val key: Any, + private val entry: NavEntry, + override val previousEntries: List>, + override val overlaidEntries: List>, + private val dialogProperties: DialogProperties, + private val onBack: () -> Unit, +) : OverlayScene { + + override val entries: List> = listOf(entry) + + override val content: @Composable (() -> Unit) = { + FloconDialog( + onDismissRequest = onBack, + properties = dialogProperties + ) { + entry.Content() + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as DialogScene<*> + + return key == other.key && + previousEntries == other.previousEntries && + overlaidEntries == other.overlaidEntries && + entry == other.entry && + dialogProperties == other.dialogProperties + } + + override fun hashCode(): Int { + return key.hashCode() * 31 + + previousEntries.hashCode() * 31 + + overlaidEntries.hashCode() * 31 + + entry.hashCode() * 31 + + dialogProperties.hashCode() * 31 + } + + override fun toString(): String { + return "DialogScene(key=$key, entry=$entry, previousEntries=$previousEntries, overlaidEntries=$overlaidEntries, dialogProperties=$dialogProperties)" + } +} + +/** + * A [SceneStrategy] that displays entries that have added [dialog] to their [NavEntry.metadata] + * within a [Dialog] instance. + * + * This strategy should always be added before any non-overlay scene strategies. + */ +public class DialogSceneStrategy() : SceneStrategy { + + public override fun SceneStrategyScope.calculateScene( + entries: List> + ): Scene? { + val lastEntry = entries.lastOrNull() + val dialogProperties = lastEntry?.metadata?.get(DIALOG_KEY) as? DialogProperties + return dialogProperties?.let { properties -> + DialogScene( + key = lastEntry.contentKey, + entry = lastEntry, + previousEntries = entries.dropLast(1), + overlaidEntries = entries.dropLast(1), + dialogProperties = properties, + onBack = onBack, + ) + } + } + + public companion object { + /** + * Function to be called on the [NavEntry.metadata] to mark this entry as something that + * should be displayed within a [Dialog]. + * + * @param dialogProperties properties that should be passed to the containing [Dialog]. + */ + public fun dialog( + dialogProperties: DialogProperties = DialogProperties() + ): Map = mapOf(DIALOG_KEY to dialogProperties) + + internal const val DIALOG_KEY = "dialog" + } +} diff --git a/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/InnerWindowScene.kt b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/InnerWindowScene.kt new file mode 100644 index 000000000..0de24f05d --- /dev/null +++ b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/InnerWindowScene.kt @@ -0,0 +1,98 @@ +package io.github.openflocon.navigation.scene + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.sharp.Close +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import androidx.navigation3.runtime.NavEntry +import androidx.navigation3.scene.OverlayScene +import androidx.navigation3.scene.Scene +import androidx.navigation3.scene.SceneStrategy +import androidx.navigation3.scene.SceneStrategyScope +import io.github.openflocon.library.designsystem.FloconTheme +import io.github.openflocon.library.designsystem.components.FloconIcon +import io.github.openflocon.library.designsystem.components.FloconIconTonalButton +import io.github.openflocon.library.designsystem.components.FloconSurface + +@Immutable +internal data class InnerWindowScene( + override val key: Any, + private val entry: NavEntry, + override val previousEntries: List>, + override val overlaidEntries: List>, + private val onBack: () -> Unit, +) : OverlayScene { + + override val entries: List> = listOf(entry) + + override val content: @Composable (() -> Unit) = { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxSize() + .background(Color.Black.copy(alpha = 0.5f)) + .clickable(onClick = onBack, indication = null, interactionSource = remember { MutableInteractionSource() }) + ) { + FloconSurface( + shape = FloconTheme.shapes.small, + modifier = Modifier.fillMaxSize(0.9f) + ) { + entry.Content() + } + FloconIconTonalButton( + onClick = onBack, + modifier = Modifier + .align(Alignment.TopEnd) + .offset { + IntOffset( + x = -16.dp.roundToPx(), + y = 16.dp.roundToPx() + ) + } + ) { + FloconIcon( + imageVector = Icons.Sharp.Close + ) + } + } + } +} + +public class InnerWindowSceneStrategy() : SceneStrategy { + + public override fun SceneStrategyScope.calculateScene( + entries: List> + ): Scene? { + val lastEntry = entries.lastOrNull() ?: return null + + if (lastEntry.metadata[INNER_WINDOW_KEY] == true) { + return InnerWindowScene( + key = lastEntry.contentKey, + entry = lastEntry, + previousEntries = entries.dropLast(1), + overlaidEntries = entries.dropLast(1), + onBack = onBack, + ) + } + + return null + } + + public companion object { + private val INNER_WINDOW_KEY = InnerWindowScene::class.qualifiedName!! + + public fun innerWindow(): Map = mapOf(INNER_WINDOW_KEY to true) + } +} diff --git a/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/PanelScene.kt b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/PanelScene.kt new file mode 100644 index 000000000..99e97f883 --- /dev/null +++ b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/PanelScene.kt @@ -0,0 +1,148 @@ +package io.github.openflocon.navigation.scene + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Close +import androidx.compose.material.icons.sharp.PushPin +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.navigation3.runtime.NavEntry +import androidx.navigation3.scene.OverlayScene +import androidx.navigation3.scene.Scene +import androidx.navigation3.scene.SceneStrategy +import androidx.navigation3.scene.SceneStrategyScope +import io.github.openflocon.library.designsystem.components.FloconIcon +import io.github.openflocon.library.designsystem.components.FloconIconTonalButton +import io.github.openflocon.library.designsystem.components.panel.FloconPanel +import io.github.openflocon.library.designsystem.components.panel.rememberFloconPanelState +import io.github.openflocon.navigation.FloconRoute +import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent + +@Immutable +data class PanelScene( + override val overlaidEntries: List>, + override val previousEntries: List>, + private val entry: NavEntry, + private val properties: PaneProperties, + private val onPin: OnPin?, + private val onBack: () -> Unit, +) : OverlayScene, KoinComponent { + override val key: Any + get() = PanelScene::class.qualifiedName!! + + override val entries: List> = listOf(entry) + + override val content: @Composable (() -> Unit) = { + val state = rememberFloconPanelState(initialValue = true) + val scope = rememberCoroutineScope() + + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.TopEnd + ) { + FloconPanel( + state = state, + onDismissRequest = onBack, + actions = { + FloconIconTonalButton( + onClick = { + scope.launch { + state.hide() + onBack() + } + }, + modifier = Modifier + .animatePanelAction() + ) { + FloconIcon( + Icons.Outlined.Close + ) + } + if (onPin != null && properties.pinnable) { // TODO Use only one + FloconIconTonalButton( + onClick = { + scope.launch { + onPin.onPin() + state.hide() + onBack() + } + }, + modifier = Modifier + .animatePanelAction() + ) { + FloconIcon( + Icons.Sharp.PushPin + ) + } + } + } + ) { + entry.Content() + } + } + } + +} + +class PanelSceneStrategy : SceneStrategy { + + override fun SceneStrategyScope.calculateScene( + entries: List> + ): Scene? { + val lastEntry = entries.lastOrNull() ?: return null + val properties = lastEntry.metadata[PANEL_KEY] ?: return null + + if (properties is PaneProperties) { + return PanelScene( + previousEntries = entries.dropLast(1), + overlaidEntries = entries.dropLast(1), + entry = lastEntry, + properties = properties, + onPin = lastEntry.metadata[ON_PIN] as? OnPin, + onBack = onBack + ) + } + + return null + } + + companion object { + private val PANEL_KEY = PanelSceneStrategy::class.qualifiedName!! + + private const val ON_PIN = "on_pin" + + fun panel( + pinnable: Boolean, + closable: Boolean, + onPin: OnPin = OnPin.Empty + ): Map = mapOf( + PANEL_KEY to PaneProperties( + pinnable = pinnable, + closable = closable + ), + ON_PIN to onPin + ) + + } + +} + +fun interface OnPin { + + fun onPin() + + companion object { + val Empty = OnPin {} + } + +} + +data class PaneProperties( + val pinnable: Boolean = false, + val closable: Boolean = false +) 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 new file mode 100644 index 000000000..7a7588069 --- /dev/null +++ b/FloconDesktop/navigation/src/commonMain/kotlin/io/github/openflocon/navigation/scene/WindowScene.kt @@ -0,0 +1,56 @@ +package io.github.openflocon.navigation.scene + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.ui.window.Window +import androidx.navigation3.runtime.NavEntry +import androidx.navigation3.scene.OverlayScene +import androidx.navigation3.scene.Scene +import androidx.navigation3.scene.SceneStrategy +import androidx.navigation3.scene.SceneStrategyScope +import io.github.openflocon.navigation.FloconRoute + +@Immutable +data class WindowScene( + private val entry: NavEntry, + override val previousEntries: List>, + private val onBack: () -> Unit +) : OverlayScene { + + override val key: Any = entry.contentKey + override val overlaidEntries: List> = previousEntries + override val entries: List> = listOf(entry) + + override val content: @Composable (() -> Unit) = { + Window( + onCloseRequest = onBack + ) { + entry.Content() + } + } + +} + +class WindowSceneStrategy : SceneStrategy { + + override fun SceneStrategyScope.calculateScene(entries: List>): Scene? { + val entry = entries.last() + + if (entry.metadata[IS_WINDOW] == true) { + return WindowScene( + entry = entry, + previousEntries = entries.dropLast(1), + onBack = onBack + ) + } + + return null + } + + companion object { + private const val IS_WINDOW = "is_window" + + fun window() = mapOf(IS_WINDOW to true) + } + +} diff --git a/FloconDesktop/settings.gradle.kts b/FloconDesktop/settings.gradle.kts index 2601754eb..6d20c3563 100644 --- a/FloconDesktop/settings.gradle.kts +++ b/FloconDesktop/settings.gradle.kts @@ -11,6 +11,7 @@ pluginManagement { } } mavenCentral() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") gradlePluginPortal() } } @@ -24,6 +25,7 @@ dependencyResolutionManagement { includeGroupAndSubgroups("com.google") } } + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") mavenCentral() } } @@ -38,3 +40,4 @@ include(":data:remote") include(":domain") include(":data:core") include(":data:local") +include(":navigation")