From b317f03d61fe5fea5a70da09b9a5f2646d2b7fb6 Mon Sep 17 00:00:00 2001 From: dantrap Date: Thu, 22 May 2025 17:58:56 +0300 Subject: [PATCH 1/2] Create MultiPaneMenuUi for improved tablet support --- features/build.gradle.kts | 1 + features/module_graph/modules.dot | 21 + features/module_graph/modules.svg | 508 +++++++++++------- .../features/chat/presentation/ChatUi.kt | 18 +- .../specific/CollapsingToolbarSpecificUi.kt | 16 +- .../features/image/presentation/ImageUi.kt | 48 +- .../samples/features/multipane_menu/DI.kt | 28 + .../presentation/MultiPaneMenuComponent.kt | 22 + .../presentation/MultiPaneMenuUi.kt | 117 ++++ .../RealMultiPaneMenuComponent.kt | 75 +++ .../details/RealSampleDetailsComponent.kt | 226 ++++++++ .../details/SampleDetailsComponent.kt | 52 ++ .../presentation/details/SampleDetailsUi.kt | 59 ++ .../list/RealSampleListComponent.kt | 18 + .../presentation/list/SampleListComponent.kt | 14 + .../presentation/list/SampleListUi.kt | 76 +++ .../presentation/camera/PhotoCameraUi.kt | 18 +- .../presentation/preview/PhotoPreviewUi.kt | 25 +- .../root/presentation/RealRootComponent.kt | 17 +- .../root/presentation/RootComponent.kt | 2 + .../features/root/presentation/RootUi.kt | 2 + .../uploader/presentation/UploaderUi.kt | 10 +- .../presentation/player/VideoPlayerUi.kt | 18 +- .../presentation/recorder/VideoRecorderUi.kt | 18 +- .../src/main/res/values-ru/menu_strings.xml | 1 + features/src/main/res/values/menu_strings.xml | 1 + gradle/libs.versions.toml | 4 +- 27 files changed, 1157 insertions(+), 258 deletions(-) create mode 100644 features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/DI.kt create mode 100644 features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneMenuComponent.kt create mode 100644 features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneMenuUi.kt create mode 100644 features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/RealMultiPaneMenuComponent.kt create mode 100644 features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/RealSampleDetailsComponent.kt create mode 100644 features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/SampleDetailsComponent.kt create mode 100644 features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/SampleDetailsUi.kt create mode 100644 features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/RealSampleListComponent.kt create mode 100644 features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/SampleListComponent.kt create mode 100644 features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/SampleListUi.kt diff --git a/features/build.gradle.kts b/features/build.gradle.kts index e68cd294..56ad568f 100644 --- a/features/build.gradle.kts +++ b/features/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { implementation("androidx.compose.material3:material3:1.4.0-alpha10") implementation(libs.accompanist.swiperefresh) implementation(libs.bundles.coil) + implementation(libs.compose.material.adaptive) // DI implementation(libs.koin) diff --git a/features/module_graph/modules.dot b/features/module_graph/modules.dot index af6c8665..006e580c 100644 --- a/features/module_graph/modules.dot +++ b/features/module_graph/modules.dot @@ -8,6 +8,7 @@ form image map menu +multipane_menu navigation otp photo @@ -20,6 +21,25 @@ tutorial uploader video work_manager +multipane_menu -> calendar +multipane_menu -> charts +multipane_menu -> chat +multipane_menu -> collapsing_toolbar +multipane_menu -> document +multipane_menu -> form +multipane_menu -> image +multipane_menu -> map +multipane_menu -> menu +multipane_menu -> navigation +multipane_menu -> otp +multipane_menu -> photo +multipane_menu -> pin_code +multipane_menu -> qr_code +multipane_menu -> shared_element_transitions +multipane_menu -> tutorial +multipane_menu -> uploader +multipane_menu -> video +multipane_menu -> work_manager photo -> image photo -> video root -> calendar @@ -31,6 +51,7 @@ root -> form root -> image root -> map root -> menu +root -> multipane_menu root -> navigation root -> otp root -> photo diff --git a/features/module_graph/modules.svg b/features/module_graph/modules.svg index 53fee31e..78184019 100644 --- a/features/module_graph/modules.svg +++ b/features/module_graph/modules.svg @@ -1,264 +1,390 @@ - - - - + + + + -photo - -photo +multipane_menu + +multipane_menu - + -image - -image +calendar + +calendar - + -photo->image - - +multipane_menu->calendar + + - + -video - -video +charts + +charts - + -photo->video - - +multipane_menu->charts + + - + -root - -root - - - -root->photo - - - - - -root->image - - +chat + +chat - - -root->video - - + + +multipane_menu->chat + + - + -calendar - -calendar +collapsing_toolbar + +collapsing_toolbar - - -root->calendar - - + + +multipane_menu->collapsing_toolbar + + - + -charts - -charts +document + +document - - -root->charts - - + + +multipane_menu->document + + - + -chat - -chat +form + +form - - -root->chat - - + + +multipane_menu->form + + - + -collapsing_toolbar - -collapsing_toolbar +image + +image - - -root->collapsing_toolbar - - + + +multipane_menu->image + + - + -document - -document +map + +map - - -root->document - - + + +multipane_menu->map + + - + -form - -form +menu + +menu - - -root->form - - + + +multipane_menu->menu + + - + -map - -map +navigation + +navigation - + -root->map - - +multipane_menu->navigation + + - + -menu - -menu +otp + +otp - + -root->menu - - +multipane_menu->otp + + - + -navigation - -navigation +photo + +photo - + -root->navigation - - +multipane_menu->photo + + - + -otp - -otp +pin_code + +pin_code - + -root->otp - - +multipane_menu->pin_code + + - + -pin_code - -pin_code +qr_code + +qr_code - - -root->pin_code - - + + +multipane_menu->qr_code + + - + -qr_code - -qr_code +shared_element_transitions + +shared_element_transitions - - -root->qr_code - - + + +multipane_menu->shared_element_transitions + + - + -settings - -settings +tutorial + +tutorial - - -root->settings - - + + +multipane_menu->tutorial + + - + -shared_element_transitions - -shared_element_transitions +uploader + +uploader - - -root->shared_element_transitions - - + + +multipane_menu->uploader + + - + -tutorial - -tutorial +video + +video - - -root->tutorial - - + + +multipane_menu->video + + - + -uploader - -uploader +work_manager + +work_manager - + + +multipane_menu->work_manager + + + + -root->uploader - - +photo->image + + - + + +photo->video + + + + -work_manager - -work_manager +root + +root - + + +root->multipane_menu + + + + +root->calendar + + + + + +root->charts + + + + + +root->chat + + + + + +root->collapsing_toolbar + + + + + +root->document + + + + + +root->form + + + + + +root->image + + + + + +root->map + + + + + +root->menu + + + + + +root->navigation + + + + + +root->otp + + + + + +root->photo + + + + + +root->pin_code + + + + + +root->qr_code + + + + + +root->shared_element_transitions + + + + + +root->tutorial + + + + + +root->uploader + + + + + +root->video + + + + + root->work_manager - - + + + + + +settings + +settings + + + +root->settings + + diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/chat/presentation/ChatUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/chat/presentation/ChatUi.kt index 29cfb1e9..8833396c 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/chat/presentation/ChatUi.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/chat/presentation/ChatUi.kt @@ -32,6 +32,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -48,6 +49,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.window.core.layout.WindowWidthSizeClass import ru.mobileup.kmm_form_validation.control.InputControl import ru.mobileup.samples.core.dialog.standard.StandardDialog import ru.mobileup.samples.core.theme.AppTheme @@ -78,12 +80,16 @@ fun ChatUi( component: ChatComponent, modifier: Modifier = Modifier, ) { - SystemBars( - statusBarColor = CustomTheme.colors.chat.primary, - navigationBarColor = CustomTheme.colors.chat.primary, - statusBarIconsColor = SystemBarIconsColor.Light, - navigationBarIconsColor = SystemBarIconsColor.Light - ) + val changeSystemBarColor = currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT + + if (changeSystemBarColor) { + SystemBars( + statusBarColor = CustomTheme.colors.chat.primary, + navigationBarColor = CustomTheme.colors.chat.primary, + statusBarIconsColor = SystemBarIconsColor.Light, + navigationBarIconsColor = SystemBarIconsColor.Light + ) + } Scaffold( modifier = modifier, diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/collapsing_toolbar/presentation/specific/CollapsingToolbarSpecificUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/collapsing_toolbar/presentation/specific/CollapsingToolbarSpecificUi.kt index 69f71d27..70330366 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/collapsing_toolbar/presentation/specific/CollapsingToolbarSpecificUi.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/collapsing_toolbar/presentation/specific/CollapsingToolbarSpecificUi.kt @@ -23,6 +23,7 @@ import androidx.compose.material3.Slider import androidx.compose.material3.Surface import androidx.compose.material3.Switch import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -41,10 +42,11 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Popup +import androidx.window.core.layout.WindowWidthSizeClass import ru.mobileup.samples.core.theme.custom.CustomTheme -import ru.mobileup.samples.core.utils.clickableNoRipple import ru.mobileup.samples.core.utils.SystemBarIconsColor import ru.mobileup.samples.core.utils.SystemBars +import ru.mobileup.samples.core.utils.clickableNoRipple import ru.mobileup.samples.features.collapsing_toolbar.presentation.common.widget.CustomToolbarDefaults import ru.mobileup.samples.features.collapsing_toolbar.presentation.specific.widget.SpecificToolbar @@ -65,10 +67,14 @@ fun CollapsingToolbarSpecificUi( convergenceCoefficient = { convergenceCoefficient } ) - SystemBars( - statusBarColor = Color.Transparent, - statusBarIconsColor = SystemBarIconsColor.Light, - ) + val changeSystemBarColor = currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT + + if (changeSystemBarColor) { + SystemBars( + statusBarColor = Color.Transparent, + statusBarIconsColor = SystemBarIconsColor.Light, + ) + } // Don't copy ⚠️ For demonstration purposes, the ScrollState and ScrollBehavior positions are reset. LaunchedEffect(canCollapse, convergenceCoefficient, itemsCount) { diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/image/presentation/ImageUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/image/presentation/ImageUi.kt index 84522e69..7a4edba0 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/image/presentation/ImageUi.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/image/presentation/ImageUi.kt @@ -18,6 +18,7 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.Icon import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -26,6 +27,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.window.core.layout.WindowWidthSizeClass import dev.icerock.moko.resources.compose.localized import ru.mobileup.samples.core.theme.AppTheme import ru.mobileup.samples.core.theme.custom.CustomTheme @@ -46,7 +48,13 @@ fun ImageUi( Box(modifier = modifier) { when (mode) { ImageCarouselMode.Embedded -> { - Column(Modifier.statusBarsPadding()) { + Column( + Modifier + .statusBarsPadding() + .verticalScroll( + rememberScrollState() + ) + ) { EmbeddedImageCarouselUi(component.imageCarouselComponent) CatsTextContent( title = title.localized(), @@ -63,32 +71,37 @@ fun ImageUi( } } - ImageToolbar() + ImageToolbar(mode) } } @Composable -private fun ImageToolbar(modifier: Modifier = Modifier) { +private fun ImageToolbar(mode: ImageCarouselMode, modifier: Modifier = Modifier) { val context = LocalContext.current + val isCompact = + currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT + Box( modifier = modifier .fillMaxHeight() .statusBarsPadding() ) { - Surface( - modifier = Modifier - .padding(16.dp) - .clip(RoundedCornerShape(12.dp)) - .size(40.dp) - .clickable { dispatchOnBackPressed(context) }, - shape = RoundedCornerShape(12.dp), - color = CustomTheme.colors.background.screen - ) { - Icon( - imageVector = Icons.AutoMirrored.Default.ArrowBack, - contentDescription = null, - modifier = Modifier.requiredSize(24.dp) - ) + if (!isCompact && mode != ImageCarouselMode.Embedded) { + Surface( + modifier = Modifier + .padding(16.dp) + .clip(RoundedCornerShape(12.dp)) + .size(40.dp) + .clickable { dispatchOnBackPressed(context) }, + shape = RoundedCornerShape(12.dp), + color = CustomTheme.colors.background.screen + ) { + Icon( + imageVector = Icons.AutoMirrored.Default.ArrowBack, + contentDescription = null, + modifier = Modifier.requiredSize(24.dp) + ) + } } } } @@ -101,7 +114,6 @@ private fun CatsTextContent( ) { Column( modifier = modifier - .verticalScroll(rememberScrollState()) .navigationBarsPadding() ) { Text( diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/DI.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/DI.kt new file mode 100644 index 00000000..a123515e --- /dev/null +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/DI.kt @@ -0,0 +1,28 @@ +package ru.mobileup.samples.features.multipane_menu + +import com.arkivanov.decompose.ComponentContext +import org.koin.core.component.get +import ru.mobileup.samples.core.ComponentFactory +import ru.mobileup.samples.features.menu.domain.Sample +import ru.mobileup.samples.features.multipane_menu.presentation.MultiPaneMenuComponent +import ru.mobileup.samples.features.multipane_menu.presentation.RealMultiPaneMenuComponent +import ru.mobileup.samples.features.multipane_menu.presentation.details.RealSampleDetailsComponent +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent +import ru.mobileup.samples.features.multipane_menu.presentation.list.RealSampleListComponent +import ru.mobileup.samples.features.multipane_menu.presentation.list.SampleListComponent + +fun ComponentFactory.createMultiPaneComponent( + componentContext: ComponentContext, + onOutput: (MultiPaneMenuComponent.Output) -> Unit, +): MultiPaneMenuComponent = RealMultiPaneMenuComponent(componentContext, onOutput, get()) + +fun ComponentFactory.createSampleListComponent( + componentContext: ComponentContext, + onOutput: (SampleListComponent.Output) -> Unit, +): SampleListComponent = RealSampleListComponent(componentContext, onOutput) + +fun ComponentFactory.createSampleDetailsComponent( + componentContext: ComponentContext, + sample: Sample, + onOutput: (SampleDetailsComponent.Output) -> Unit, +): SampleDetailsComponent = RealSampleDetailsComponent(componentContext, sample, onOutput, get()) diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneMenuComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneMenuComponent.kt new file mode 100644 index 00000000..bf0d95d4 --- /dev/null +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneMenuComponent.kt @@ -0,0 +1,22 @@ +package ru.mobileup.samples.features.multipane_menu.presentation + +import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.decompose.router.panels.ChildPanels +import com.arkivanov.decompose.router.panels.ChildPanelsMode +import kotlinx.coroutines.flow.StateFlow +import ru.mobileup.samples.core.utils.PredictiveBackComponent +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent +import ru.mobileup.samples.features.multipane_menu.presentation.list.SampleListComponent + +@OptIn(ExperimentalDecomposeApi::class) +interface MultiPaneMenuComponent : PredictiveBackComponent { + + val panels: StateFlow> + + fun setMode(mode: ChildPanelsMode) + fun onSettingsClick() + + sealed interface Output { + object SettingsRequested : Output + } +} diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneMenuUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneMenuUi.kt new file mode 100644 index 00000000..278b3136 --- /dev/null +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneMenuUi.kt @@ -0,0 +1,117 @@ +package ru.mobileup.samples.features.multipane_menu.presentation + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.window.core.layout.WindowWidthSizeClass +import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.decompose.extensions.compose.experimental.panels.ChildPanels +import com.arkivanov.decompose.extensions.compose.experimental.panels.ChildPanelsAnimators +import com.arkivanov.decompose.extensions.compose.experimental.panels.HorizontalChildPanelsLayout +import com.arkivanov.decompose.extensions.compose.experimental.stack.animation.PredictiveBackParams +import com.arkivanov.decompose.extensions.compose.experimental.stack.animation.fade +import com.arkivanov.decompose.extensions.compose.experimental.stack.animation.slide +import com.arkivanov.decompose.extensions.compose.stack.animation.predictiveback.materialPredictiveBackAnimatable +import com.arkivanov.decompose.router.panels.ChildPanelsMode +import ru.mobileup.samples.core.theme.custom.CustomTheme +import ru.mobileup.samples.features.R +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsUi +import ru.mobileup.samples.features.multipane_menu.presentation.list.SampleListUi + +@OptIn(ExperimentalDecomposeApi::class, ExperimentalMaterial3Api::class) +@Composable +fun MultiPaneMenuUi( + component: MultiPaneMenuComponent, + modifier: Modifier = Modifier, +) { + val panels by component.panels.collectAsState() + + val isCompact = + currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT + + val mode = if (isCompact) ChildPanelsMode.SINGLE else ChildPanelsMode.DUAL + + LaunchedEffect(mode) { + component.setMode(mode) + } + + Column( + modifier = modifier.run { + if (mode == ChildPanelsMode.DUAL) statusBarsPadding() else this + } + ) { + if (mode == ChildPanelsMode.DUAL) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(56.dp) + ) { + IconButton( + modifier = Modifier.align(Alignment.CenterEnd), + onClick = component::onSettingsClick + ) { + Icon( + imageVector = Icons.Default.Settings, + contentDescription = null, + tint = CustomTheme.colors.icon.primary + ) + } + } + } + + ChildPanels( + panels = panels, + mainChild = { + SampleListUi( + modifier = Modifier.background(CustomTheme.colors.background.screen), + component = it.instance, + mode = mode + ) + }, + detailsChild = { + SampleDetailsUi( + modifier = Modifier.background(CustomTheme.colors.background.screen), + component = it.instance + ) + }, + layout = HorizontalChildPanelsLayout( + dualWeights = Pair(first = 0.4F, second = 0.6F), + ), + secondPanelPlaceholder = { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text(text = stringResource(R.string.menu_item_details_placeholder)) + } + }, + animators = ChildPanelsAnimators(single = slide(), dual = fade() to fade()), + predictiveBackParams = { + PredictiveBackParams( + backHandler = component.backHandler, + onBack = component::onBackClick, + animatable = ::materialPredictiveBackAnimatable, + ) + }, + ) + } +} diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/RealMultiPaneMenuComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/RealMultiPaneMenuComponent.kt new file mode 100644 index 00000000..4f3c5541 --- /dev/null +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/RealMultiPaneMenuComponent.kt @@ -0,0 +1,75 @@ +package ru.mobileup.samples.features.multipane_menu.presentation + +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.decompose.router.panels.ChildPanels +import com.arkivanov.decompose.router.panels.ChildPanelsMode +import com.arkivanov.decompose.router.panels.Panels +import com.arkivanov.decompose.router.panels.PanelsNavigation +import com.arkivanov.decompose.router.panels.activateDetails +import com.arkivanov.decompose.router.panels.childPanels +import com.arkivanov.decompose.router.panels.pop +import com.arkivanov.decompose.router.panels.setMode +import kotlinx.coroutines.flow.StateFlow +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.serializer +import ru.mobileup.samples.core.ComponentFactory +import ru.mobileup.samples.core.utils.toStateFlow +import ru.mobileup.samples.features.menu.domain.Sample +import ru.mobileup.samples.features.multipane_menu.createSampleDetailsComponent +import ru.mobileup.samples.features.multipane_menu.createSampleListComponent +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent +import ru.mobileup.samples.features.multipane_menu.presentation.list.SampleListComponent + +@OptIn(ExperimentalDecomposeApi::class) +class RealMultiPaneMenuComponent( + componentContext: ComponentContext, + private val onOutput: (MultiPaneMenuComponent.Output) -> Unit, + private val componentFactory: ComponentFactory, +) : ComponentContext by componentContext, MultiPaneMenuComponent { + + private val navigation = PanelsNavigation() + + @OptIn(ExperimentalSerializationApi::class) + override val panels: StateFlow> = + childPanels( + source = navigation, + serializers = Unit.serializer() to DetailsConfig.serializer(), + initialPanels = { Panels(main = Unit) }, + handleBackButton = true, + mainFactory = { _, ctx -> + componentFactory.createSampleListComponent(ctx, ::onSampleListOutput) + }, + detailsFactory = { cfg, ctx -> + componentFactory.createSampleDetailsComponent( + ctx, + cfg.sample, + ::onSampleDetailsOutput + ) + }, + ).toStateFlow(lifecycle) + + private fun onSampleListOutput(output: SampleListComponent.Output) = when (output) { + is SampleListComponent.Output.SampleChosen -> navigation.activateDetails( + DetailsConfig(output.sample) + ) + + SampleListComponent.Output.SettingsRequested -> onOutput( + MultiPaneMenuComponent.Output.SettingsRequested + ) + } + + private fun onSampleDetailsOutput(output: SampleDetailsComponent.Output) = when (output) { + SampleDetailsComponent.Output.OtpSuccessfullyVerified -> navigation.pop() + } + + override fun onBackClick() = navigation.pop() + + override fun setMode(mode: ChildPanelsMode) = navigation.setMode(mode) + + override fun onSettingsClick() = onOutput(MultiPaneMenuComponent.Output.SettingsRequested) + + @Serializable + private data class DetailsConfig(val sample: Sample) +} diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/RealSampleDetailsComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/RealSampleDetailsComponent.kt new file mode 100644 index 00000000..973902ed --- /dev/null +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/RealSampleDetailsComponent.kt @@ -0,0 +1,226 @@ +package ru.mobileup.samples.features.multipane_menu.presentation.details + +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.router.stack.ChildStack +import com.arkivanov.decompose.router.stack.StackNavigation +import com.arkivanov.decompose.router.stack.childStack +import kotlinx.coroutines.flow.StateFlow +import kotlinx.serialization.Serializable +import ru.mobileup.samples.core.ComponentFactory +import ru.mobileup.samples.core.utils.toStateFlow +import ru.mobileup.samples.features.calendar.createCalendarComponent +import ru.mobileup.samples.features.charts.createChartComponent +import ru.mobileup.samples.features.chat.createChatComponent +import ru.mobileup.samples.features.collapsing_toolbar.createCollapsingToolbarComponent +import ru.mobileup.samples.features.document.createDocumentComponent +import ru.mobileup.samples.features.form.createFormComponent +import ru.mobileup.samples.features.image.createImageComponent +import ru.mobileup.samples.features.map.createMapMainComponent +import ru.mobileup.samples.features.menu.domain.Sample +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent.Child.Calendar +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent.Child.Chart +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent.Child.Chat +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent.Child.CollapsingToolbar +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent.Child.Document +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent.Child.Form +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent.Child.Image +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent.Child.Map +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent.Child.Navigation +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent.Child.Otp +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent.Child.Photo +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent.Child.PinCodeSettings +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent.Child.QrCode +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent.Child.SharedElements +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent.Child.Tutorial +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent.Child.Uploader +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent.Child.Video +import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent.Child.WorkManager +import ru.mobileup.samples.features.navigation.createNavigationComponent +import ru.mobileup.samples.features.otp.createOtpComponent +import ru.mobileup.samples.features.otp.presentation.OtpComponent +import ru.mobileup.samples.features.photo.createPhotoComponent +import ru.mobileup.samples.features.pin_code.createPinCodeSettingsComponent +import ru.mobileup.samples.features.qr_code.createQrCodeComponent +import ru.mobileup.samples.features.shared_element_transitions.createSharedElementsComponent +import ru.mobileup.samples.features.tutorial.createTutorialSampleComponent +import ru.mobileup.samples.features.uploader.createUploaderComponent +import ru.mobileup.samples.features.video.createVideoComponent +import ru.mobileup.samples.features.work_manager.createWorkManagerComponent + +class RealSampleDetailsComponent( + componentContext: ComponentContext, + sample: Sample, + private val onOutput: (SampleDetailsComponent.Output) -> Unit, + private val componentFactory: ComponentFactory, +) : ComponentContext by componentContext, SampleDetailsComponent { + + private val navigation = StackNavigation() + + override val childStack: StateFlow> = childStack( + source = navigation, + initialConfiguration = when (sample) { + Sample.Form -> ChildConfig.Form + Sample.Otp -> ChildConfig.Otp + Sample.Photo -> ChildConfig.Photo + Sample.Video -> ChildConfig.Video + Sample.Document -> ChildConfig.Document + Sample.Uploader -> ChildConfig.Uploader + Sample.Calendar -> ChildConfig.Calendar + Sample.QrCode -> ChildConfig.QrCode + Sample.Chart -> ChildConfig.Chart + Sample.Navigation -> ChildConfig.Navigation + Sample.CollapsingToolbar -> ChildConfig.CollapsingToolbar + Sample.Image -> ChildConfig.Image + Sample.Tutorial -> ChildConfig.Tutorial + Sample.SharedTransitions -> ChildConfig.SharedElements + Sample.PinCodeSettings -> ChildConfig.PinCodeSettings + Sample.Map -> ChildConfig.Map + Sample.Chat -> ChildConfig.Chat + Sample.WorkManager -> ChildConfig.WorkManager + }, + serializer = ChildConfig.serializer(), + handleBackButton = true, + childFactory = ::createChild + ).toStateFlow(lifecycle) + + private fun createChild( + config: ChildConfig, + componentContext: ComponentContext, + ): SampleDetailsComponent.Child = when (config) { + ChildConfig.Form -> Form( + componentFactory.createFormComponent(componentContext) + ) + + ChildConfig.Otp -> Otp( + componentFactory.createOtpComponent(componentContext, ::onOtpOutput) + ) + + ChildConfig.Photo -> Photo( + componentFactory.createPhotoComponent(componentContext) + ) + + ChildConfig.Video -> Video( + componentFactory.createVideoComponent(componentContext) + ) + + ChildConfig.Document -> Document( + componentFactory.createDocumentComponent(componentContext) + ) + + ChildConfig.Uploader -> Uploader( + componentFactory.createUploaderComponent(componentContext) + ) + + ChildConfig.Calendar -> Calendar( + componentFactory.createCalendarComponent(componentContext) + ) + + ChildConfig.QrCode -> QrCode( + componentFactory.createQrCodeComponent(componentContext) + ) + + ChildConfig.Chart -> Chart( + componentFactory.createChartComponent(componentContext) + ) + + ChildConfig.Navigation -> Navigation( + componentFactory.createNavigationComponent(componentContext) + ) + + ChildConfig.CollapsingToolbar -> CollapsingToolbar( + componentFactory.createCollapsingToolbarComponent(componentContext) + ) + + ChildConfig.Image -> Image( + componentFactory.createImageComponent(componentContext) + ) + + ChildConfig.Tutorial -> Tutorial( + componentFactory.createTutorialSampleComponent(componentContext) + ) + + ChildConfig.SharedElements -> SharedElements( + componentFactory.createSharedElementsComponent(componentContext) + ) + + ChildConfig.PinCodeSettings -> PinCodeSettings( + componentFactory.createPinCodeSettingsComponent(componentContext) + ) + + ChildConfig.Map -> Map( + componentFactory.createMapMainComponent(componentContext) + ) + + ChildConfig.Chat -> Chat( + componentFactory.createChatComponent(componentContext) + ) + + ChildConfig.WorkManager -> WorkManager( + componentFactory.createWorkManagerComponent(componentContext) + ) + } + + private fun onOtpOutput(output: OtpComponent.Output) = when (output) { + OtpComponent.Output.OtpSuccessfullyVerified -> onOutput( + SampleDetailsComponent.Output.OtpSuccessfullyVerified + ) + } + + @Serializable + sealed interface ChildConfig { + + @Serializable + data object Form : ChildConfig + + @Serializable + data object Otp : ChildConfig + + @Serializable + data object Photo : ChildConfig + + @Serializable + data object Video : ChildConfig + + @Serializable + data object Document : ChildConfig + + @Serializable + data object Uploader : ChildConfig + + @Serializable + data object Calendar : ChildConfig + + @Serializable + data object QrCode : ChildConfig + + @Serializable + data object Chart : ChildConfig + + @Serializable + data object Navigation : ChildConfig + + @Serializable + data object CollapsingToolbar : ChildConfig + + @Serializable + data object Image : ChildConfig + + @Serializable + data object Tutorial : ChildConfig + + @Serializable + data object SharedElements : ChildConfig + + @Serializable + data object PinCodeSettings : ChildConfig + + @Serializable + data object Map : ChildConfig + + @Serializable + data object Chat : ChildConfig + + @Serializable + data object WorkManager : ChildConfig + } +} diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/SampleDetailsComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/SampleDetailsComponent.kt new file mode 100644 index 00000000..10ae5152 --- /dev/null +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/SampleDetailsComponent.kt @@ -0,0 +1,52 @@ +package ru.mobileup.samples.features.multipane_menu.presentation.details + +import com.arkivanov.decompose.router.stack.ChildStack +import kotlinx.coroutines.flow.StateFlow +import ru.mobileup.samples.features.calendar.presentation.CalendarComponent +import ru.mobileup.samples.features.charts.presentation.ChartComponent +import ru.mobileup.samples.features.chat.presentation.ChatComponent +import ru.mobileup.samples.features.collapsing_toolbar.presentation.CollapsingToolbarComponent +import ru.mobileup.samples.features.document.presentation.DocumentComponent +import ru.mobileup.samples.features.form.presentation.FormComponent +import ru.mobileup.samples.features.image.presentation.ImageComponent +import ru.mobileup.samples.features.map.presentation.MapComponent +import ru.mobileup.samples.features.navigation.NavigationComponent +import ru.mobileup.samples.features.otp.presentation.OtpComponent +import ru.mobileup.samples.features.photo.presentation.PhotoComponent +import ru.mobileup.samples.features.pin_code.presentation.settings.PinCodeSettingsComponent +import ru.mobileup.samples.features.qr_code.presentation.QrCodeComponent +import ru.mobileup.samples.features.shared_element_transitions.presentation.SharedElementsComponent +import ru.mobileup.samples.features.tutorial.presentation.TutorialSampleComponent +import ru.mobileup.samples.features.uploader.presentation.UploaderComponent +import ru.mobileup.samples.features.video.presentation.VideoComponent +import ru.mobileup.samples.features.work_manager.presentation.WorkManagerComponent + +interface SampleDetailsComponent { + + val childStack: StateFlow> + + sealed interface Child { + class Form(val component: FormComponent) : Child + class Otp(val component: OtpComponent) : Child + class Photo(val component: PhotoComponent) : Child + class Video(val component: VideoComponent) : Child + class Document(val component: DocumentComponent) : Child + class Uploader(val component: UploaderComponent) : Child + class Calendar(val component: CalendarComponent) : Child + class QrCode(val component: QrCodeComponent) : Child + class Chart(val component: ChartComponent) : Child + class Navigation(val component: NavigationComponent) : Child + class CollapsingToolbar(val component: CollapsingToolbarComponent) : Child + class Image(val component: ImageComponent) : Child + class Tutorial(val component: TutorialSampleComponent) : Child + class SharedElements(val component: SharedElementsComponent) : Child + class PinCodeSettings(val component: PinCodeSettingsComponent) : Child + class Map(val component: MapComponent) : Child + class Chat(val component: ChatComponent) : Child + class WorkManager(val component: WorkManagerComponent) : Child + } + + sealed interface Output { + data object OtpSuccessfullyVerified : Output + } +} diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/SampleDetailsUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/SampleDetailsUi.kt new file mode 100644 index 00000000..b28c938d --- /dev/null +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/SampleDetailsUi.kt @@ -0,0 +1,59 @@ +package ru.mobileup.samples.features.multipane_menu.presentation.details + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import com.arkivanov.decompose.extensions.compose.stack.Children +import ru.mobileup.samples.features.calendar.presentation.CalendarUi +import ru.mobileup.samples.features.charts.presentation.ChartUi +import ru.mobileup.samples.features.chat.presentation.ChatUi +import ru.mobileup.samples.features.collapsing_toolbar.presentation.CollapsingToolbarUi +import ru.mobileup.samples.features.document.presentation.DocumentUi +import ru.mobileup.samples.features.form.presentation.FormUi +import ru.mobileup.samples.features.image.presentation.ImageUi +import ru.mobileup.samples.features.map.presentation.MapUi +import ru.mobileup.samples.features.navigation.NavigationUi +import ru.mobileup.samples.features.otp.presentation.OtpUi +import ru.mobileup.samples.features.photo.presentation.PhotoUi +import ru.mobileup.samples.features.pin_code.presentation.settings.PinCodeSettingsUi +import ru.mobileup.samples.features.qr_code.presentation.QrCodeUi +import ru.mobileup.samples.features.shared_element_transitions.presentation.SharedElementsUi +import ru.mobileup.samples.features.tutorial.presentation.TutorialSampleUi +import ru.mobileup.samples.features.uploader.presentation.UploaderUi +import ru.mobileup.samples.features.video.presentation.VideoUi +import ru.mobileup.samples.features.work_manager.presentation.WorkManagerUi + +@Composable +fun SampleDetailsUi( + component: SampleDetailsComponent, + modifier: Modifier = Modifier, +) { + val stack by component.childStack.collectAsState() + + Children( + modifier = modifier, + stack = stack, + ) { child -> + when (val instance = child.instance) { + is SampleDetailsComponent.Child.Form -> FormUi(instance.component) + is SampleDetailsComponent.Child.Otp -> OtpUi(instance.component) + is SampleDetailsComponent.Child.Photo -> PhotoUi(instance.component) + is SampleDetailsComponent.Child.Video -> VideoUi(instance.component) + is SampleDetailsComponent.Child.Document -> DocumentUi(instance.component) + is SampleDetailsComponent.Child.Uploader -> UploaderUi(instance.component) + is SampleDetailsComponent.Child.Calendar -> CalendarUi(instance.component) + is SampleDetailsComponent.Child.QrCode -> QrCodeUi(instance.component) + is SampleDetailsComponent.Child.Chart -> ChartUi(instance.component) + is SampleDetailsComponent.Child.Navigation -> NavigationUi(instance.component) + is SampleDetailsComponent.Child.CollapsingToolbar -> CollapsingToolbarUi(instance.component) + is SampleDetailsComponent.Child.Image -> ImageUi(instance.component) + is SampleDetailsComponent.Child.Tutorial -> TutorialSampleUi(instance.component) + is SampleDetailsComponent.Child.SharedElements -> SharedElementsUi(instance.component) + is SampleDetailsComponent.Child.PinCodeSettings -> PinCodeSettingsUi(instance.component) + is SampleDetailsComponent.Child.Map -> MapUi(instance.component) + is SampleDetailsComponent.Child.Chat -> ChatUi(instance.component) + is SampleDetailsComponent.Child.WorkManager -> WorkManagerUi(instance.component) + } + } +} diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/RealSampleListComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/RealSampleListComponent.kt new file mode 100644 index 00000000..04f90d91 --- /dev/null +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/RealSampleListComponent.kt @@ -0,0 +1,18 @@ +package ru.mobileup.samples.features.multipane_menu.presentation.list + +import com.arkivanov.decompose.ComponentContext +import ru.mobileup.samples.features.menu.domain.Sample + +class RealSampleListComponent( + componentContext: ComponentContext, + private val onOutput: (SampleListComponent.Output) -> Unit, +) : ComponentContext by componentContext, SampleListComponent { + + override fun onButtonClick(sample: Sample) { + onOutput(SampleListComponent.Output.SampleChosen(sample)) + } + + override fun onSettingsClick() { + onOutput(SampleListComponent.Output.SettingsRequested) + } +} diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/SampleListComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/SampleListComponent.kt new file mode 100644 index 00000000..57253ab7 --- /dev/null +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/SampleListComponent.kt @@ -0,0 +1,14 @@ +package ru.mobileup.samples.features.multipane_menu.presentation.list + +import ru.mobileup.samples.features.menu.domain.Sample + +interface SampleListComponent { + + fun onButtonClick(sample: Sample) + fun onSettingsClick() + + sealed interface Output { + data class SampleChosen(val sample: Sample) : Output + data object SettingsRequested : Output + } +} diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/SampleListUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/SampleListUi.kt new file mode 100644 index 00000000..537d9663 --- /dev/null +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/SampleListUi.kt @@ -0,0 +1,76 @@ +package ru.mobileup.samples.features.multipane_menu.presentation.list + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.decompose.router.panels.ChildPanelsMode +import dev.icerock.moko.resources.compose.localized +import ru.mobileup.samples.core.message.presentation.noOverlapByMessage +import ru.mobileup.samples.core.theme.custom.CustomTheme +import ru.mobileup.samples.core.widget.button.AppButton +import ru.mobileup.samples.core.widget.button.ButtonType +import ru.mobileup.samples.features.menu.domain.Sample + +@OptIn(ExperimentalDecomposeApi::class) +@Composable +fun SampleListUi( + component: SampleListComponent, + mode: ChildPanelsMode, + modifier: Modifier = Modifier, +) { + Scaffold( + modifier = modifier, + floatingActionButton = { + if (mode == ChildPanelsMode.SINGLE) { + FloatingActionButton( + modifier = Modifier.noOverlapByMessage(), + onClick = component::onSettingsClick + ) { + Icon( + imageVector = Icons.Default.Settings, + contentDescription = null, + tint = CustomTheme.colors.icon.primary + ) + } + } + } + ) { padding -> + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .systemBarsPadding() + .padding(32.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Sample.entries.forEach { sample -> + AppButton( + modifier = Modifier.fillMaxWidth(), + buttonType = ButtonType.Secondary, + text = sample.displayName.localized(), + onClick = { component.onButtonClick(sample) } + ) + } + + /* Manually add bottom spacing equal to FAB height because the padding + provided by Scaffold is not always correct on all Android versions. */ + if (mode == ChildPanelsMode.SINGLE) Spacer(Modifier.height(56.dp)) + } + } +} diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/photo/presentation/camera/PhotoCameraUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/photo/presentation/camera/PhotoCameraUi.kt index 76c4f88f..f9a98037 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/photo/presentation/camera/PhotoCameraUi.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/photo/presentation/camera/PhotoCameraUi.kt @@ -32,6 +32,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Icon import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -59,6 +60,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.times import androidx.compose.ui.viewinterop.AndroidView +import androidx.window.core.layout.WindowWidthSizeClass import coil3.compose.rememberAsyncImagePainter import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -125,12 +127,16 @@ fun PhotoCameraUi( ) } - SystemBars( - statusBarColor = Color.Transparent, - navigationBarColor = Color.Transparent, - statusBarIconsColor = SystemBarIconsColor.Light, - navigationBarIconsColor = SystemBarIconsColor.Light - ) + val changeSystemBarColor = currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT + + if (changeSystemBarColor) { + SystemBars( + statusBarColor = Color.Transparent, + navigationBarColor = Color.Transparent, + statusBarIconsColor = SystemBarIconsColor.Light, + navigationBarIconsColor = SystemBarIconsColor.Light + ) + } LaunchedEffect(cameraState) { photoCameraController.cameraState = cameraState diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/photo/presentation/preview/PhotoPreviewUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/photo/presentation/preview/PhotoPreviewUi.kt index 0d54fa5f..40528f0a 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/photo/presentation/preview/PhotoPreviewUi.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/photo/presentation/preview/PhotoPreviewUi.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -29,6 +30,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.media3.common.util.UnstableApi +import androidx.window.core.layout.WindowWidthSizeClass import ru.mobileup.samples.core.dialog.standard.StandardDialog import ru.mobileup.samples.core.theme.AppTheme import ru.mobileup.samples.core.theme.custom.CustomTheme @@ -40,14 +42,19 @@ import ru.mobileup.samples.features.image.presentation.carousel.FullScreenImageC @Composable fun PhotoPreviewUi( component: PhotoPreviewComponent, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { - SystemBars( - statusBarColor = Color.Transparent, - navigationBarColor = Color.Transparent, - statusBarIconsColor = SystemBarIconsColor.Light, - navigationBarIconsColor = SystemBarIconsColor.Light - ) + val changeSystemBarColor = + currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT + + if (changeSystemBarColor) { + SystemBars( + statusBarColor = Color.Transparent, + navigationBarColor = Color.Transparent, + statusBarIconsColor = SystemBarIconsColor.Light, + navigationBarIconsColor = SystemBarIconsColor.Light + ) + } StandardDialog(component.saveDialog) @@ -60,7 +67,7 @@ fun PhotoPreviewUi( @Composable private fun PhotoPreviewContent( component: PhotoPreviewComponent, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Scaffold( modifier = modifier, @@ -83,7 +90,7 @@ private fun PhotoPreviewContent( @Composable private fun PreviewTopBar( onSaveClick: () -> Unit, - onShareClick: () -> Unit + onShareClick: () -> Unit, ) { val configuration = LocalConfiguration.current var orientation by remember { mutableIntStateOf(Configuration.ORIENTATION_PORTRAIT) } diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RealRootComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RealRootComponent.kt index d82778e5..fe8212df 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RealRootComponent.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RealRootComponent.kt @@ -23,6 +23,8 @@ import ru.mobileup.samples.features.map.createMapMainComponent import ru.mobileup.samples.features.menu.createMenuComponent import ru.mobileup.samples.features.menu.domain.Sample import ru.mobileup.samples.features.menu.presentation.MenuComponent +import ru.mobileup.samples.features.multipane_menu.createMultiPaneComponent +import ru.mobileup.samples.features.multipane_menu.presentation.MultiPaneMenuComponent import ru.mobileup.samples.features.navigation.createNavigationComponent import ru.mobileup.samples.features.otp.createOtpComponent import ru.mobileup.samples.features.otp.presentation.OtpComponent @@ -47,7 +49,7 @@ class RealRootComponent( override val childStack = childStack( source = navigation, - initialConfiguration = ChildConfig.Menu, + initialConfiguration = ChildConfig.MultiPaneMenu, serializer = ChildConfig.serializer(), handleBackButton = true, childFactory = ::createChild @@ -76,6 +78,12 @@ class RealRootComponent( config: ChildConfig, componentContext: ComponentContext, ): RootComponent.Child = when (config) { + ChildConfig.MultiPaneMenu -> { + RootComponent.Child.MultiPaneMenu( + componentFactory.createMultiPaneComponent(componentContext, ::onMultiPaneOutput) + ) + } + ChildConfig.Menu -> { RootComponent.Child.Menu( componentFactory.createMenuComponent(componentContext, ::onMenuOutput) @@ -197,6 +205,10 @@ class RealRootComponent( } } + private fun onMultiPaneOutput(output: MultiPaneMenuComponent.Output) = when (output) { + MultiPaneMenuComponent.Output.SettingsRequested -> navigation.safePush(ChildConfig.Settings) + } + private fun onMenuOutput(output: MenuComponent.Output) { when (output) { is MenuComponent.Output.SampleChosen -> when (output.sample) { @@ -233,6 +245,9 @@ class RealRootComponent( @Serializable sealed interface ChildConfig { + @Serializable + data object MultiPaneMenu : ChildConfig + @Serializable data object Menu : ChildConfig diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RootComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RootComponent.kt index 6a7ea591..a17da940 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RootComponent.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RootComponent.kt @@ -15,6 +15,7 @@ import ru.mobileup.samples.features.form.presentation.FormComponent import ru.mobileup.samples.features.image.presentation.ImageComponent import ru.mobileup.samples.features.map.presentation.MapComponent import ru.mobileup.samples.features.menu.presentation.MenuComponent +import ru.mobileup.samples.features.multipane_menu.presentation.MultiPaneMenuComponent import ru.mobileup.samples.features.navigation.NavigationComponent import ru.mobileup.samples.features.otp.presentation.OtpComponent import ru.mobileup.samples.features.photo.presentation.PhotoComponent @@ -46,6 +47,7 @@ interface RootComponent : PredictiveBackComponent { val themeComponent: ThemeComponent sealed interface Child { + class MultiPaneMenu(val component: MultiPaneMenuComponent) : Child class Menu(val component: MenuComponent) : Child class Form(val component: FormComponent) : Child class Otp(val component: OtpComponent) : Child diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RootUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RootUi.kt index d5dc32b8..30aae2b8 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RootUi.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RootUi.kt @@ -27,6 +27,7 @@ import ru.mobileup.samples.features.form.presentation.FormUi import ru.mobileup.samples.features.image.presentation.ImageUi import ru.mobileup.samples.features.map.presentation.MapUi import ru.mobileup.samples.features.menu.presentation.MenuUi +import ru.mobileup.samples.features.multipane_menu.presentation.MultiPaneMenuUi import ru.mobileup.samples.features.navigation.NavigationUi import ru.mobileup.samples.features.otp.presentation.OtpUi import ru.mobileup.samples.features.photo.presentation.PhotoUi @@ -57,6 +58,7 @@ fun RootUi( animation = component.predictiveBackAnimation() ) { child -> when (val instance = child.instance) { + is RootComponent.Child.MultiPaneMenu -> MultiPaneMenuUi(instance.component) is RootComponent.Child.Menu -> MenuUi(instance.component) is RootComponent.Child.Form -> FormUi(instance.component) is RootComponent.Child.Otp -> OtpUi(instance.component) diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/uploader/presentation/UploaderUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/uploader/presentation/UploaderUi.kt index 43d1da03..6c79e6f0 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/uploader/presentation/UploaderUi.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/uploader/presentation/UploaderUi.kt @@ -34,8 +34,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import ru.mobileup.samples.core.theme.AppTheme import ru.mobileup.samples.core.theme.custom.CustomTheme -import ru.mobileup.samples.core.utils.SystemBarIconsColor -import ru.mobileup.samples.core.utils.SystemBars import ru.mobileup.samples.core.widget.button.AppButton import ru.mobileup.samples.core.widget.button.ButtonType import ru.mobileup.samples.features.R @@ -47,11 +45,6 @@ fun UploaderUi( component: UploaderComponent, modifier: Modifier = Modifier ) { - SystemBars( - statusBarColor = Color.Transparent, - statusBarIconsColor = SystemBarIconsColor.Light, - ) - UploaderContent( component = component, modifier = modifier @@ -83,7 +76,7 @@ private fun UploaderTopBar( Row( modifier = modifier .fillMaxWidth() - .background(CustomTheme.colors.palette.black) + .background(CustomTheme.colors.background.screen) .statusBarsPadding() .padding(horizontal = 8.dp, vertical = 24.dp) ) { @@ -96,7 +89,6 @@ private fun UploaderTopBar( Text( text = stringResource(R.string.uploader_title), - color = CustomTheme.colors.palette.white, modifier = Modifier .weight(2f) .align(Alignment.CenterVertically) diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/video/presentation/player/VideoPlayerUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/video/presentation/player/VideoPlayerUi.kt index 630d8ca6..8901e543 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/video/presentation/player/VideoPlayerUi.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/video/presentation/player/VideoPlayerUi.kt @@ -46,6 +46,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Slider import androidx.compose.material3.SliderDefaults import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -71,6 +72,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.view.WindowInsetsCompat import androidx.media3.common.util.UnstableApi +import androidx.window.core.layout.WindowWidthSizeClass import kotlinx.coroutines.delay import ru.mobileup.samples.core.dialog.standard.StandardDialog import ru.mobileup.samples.core.message.presentation.noOverlapByMessage @@ -122,12 +124,16 @@ fun VideoPlayerUi( modifier = modifier ) - SystemBars( - statusBarColor = Color.Transparent, - navigationBarColor = Color.Transparent, - statusBarIconsColor = SystemBarIconsColor.Light, - navigationBarIconsColor = SystemBarIconsColor.Light - ) + val changeSystemBarColor = currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT + + if (changeSystemBarColor) { + SystemBars( + statusBarColor = Color.Transparent, + navigationBarColor = Color.Transparent, + statusBarIconsColor = SystemBarIconsColor.Light, + navigationBarIconsColor = SystemBarIconsColor.Light + ) + } StandardDialog(component.resetTransformDialog) StandardDialog(component.saveDialog) diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/video/presentation/recorder/VideoRecorderUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/video/presentation/recorder/VideoRecorderUi.kt index 9b2c7f78..248dba3e 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/video/presentation/recorder/VideoRecorderUi.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/video/presentation/recorder/VideoRecorderUi.kt @@ -38,6 +38,7 @@ import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.Icon import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -64,6 +65,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.times import androidx.compose.ui.viewinterop.AndroidView +import androidx.window.core.layout.WindowWidthSizeClass import kotlinx.coroutines.launch import ru.mobileup.samples.core.message.presentation.noOverlapByMessage import ru.mobileup.samples.core.theme.AppTheme @@ -166,12 +168,16 @@ fun VideoRecorderUi( videoRecorderController.zoomChange(zoomChange = zoomChange) } - SystemBars( - statusBarColor = Color.Transparent, - navigationBarColor = Color.Transparent, - statusBarIconsColor = SystemBarIconsColor.Light, - navigationBarIconsColor = SystemBarIconsColor.Light - ) + val changeSystemBarColor = currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT + + if (changeSystemBarColor) { + SystemBars( + statusBarColor = Color.Transparent, + navigationBarColor = Color.Transparent, + statusBarIconsColor = SystemBarIconsColor.Light, + navigationBarIconsColor = SystemBarIconsColor.Light + ) + } LaunchedEffect(filtersPagerState.settledPage, filtersPagerState.isScrollInProgress) { val newEffectIndex = filtersPagerState.settledPage % availableFilters.size diff --git a/features/src/main/res/values-ru/menu_strings.xml b/features/src/main/res/values-ru/menu_strings.xml index f31f568b..b5969af5 100644 --- a/features/src/main/res/values-ru/menu_strings.xml +++ b/features/src/main/res/values-ru/menu_strings.xml @@ -17,4 +17,5 @@ Карта Чат WorkManager + Выбирете сэмпл \ No newline at end of file diff --git a/features/src/main/res/values/menu_strings.xml b/features/src/main/res/values/menu_strings.xml index a744339c..6a3ff272 100644 --- a/features/src/main/res/values/menu_strings.xml +++ b/features/src/main/res/values/menu_strings.xml @@ -17,4 +17,5 @@ Map Chat WorkManager + Select a sample \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e572c1f4..62b71d4a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ moduleGraph = "1.3.3" detekt = "1.23.8" dateTime = "0.6.2" coroutines = "1.10.1" -decompose = "3.2.2" +decompose = "3.3.0" ktor = "3.1.0" replica = "1.3.1-alpha1" koin = "4.0.2" @@ -47,6 +47,7 @@ googleMaps = "6.5.2" sesame = "1.5.0" room = "2.7.0" work = "2.10.1" +materialAdaptive = "1.1.0" [libraries] kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "dateTime" } @@ -88,6 +89,7 @@ compose-ui = { module = "androidx.compose.ui:ui" } compose-material = { module = "androidx.compose.material3:material3" } compose-material-icons = { module = "androidx.compose.material:material-icons-core" } compose-tooling = { module = "androidx.compose.ui:ui-tooling" } +compose-material-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "materialAdaptive" } activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activity" } compose-calendar = { group = "com.kizitonwose.calendar", name = "compose", version.ref = "calendar" } From a763fbad50324466c909dfd9268ce8a646ca0ea0 Mon Sep 17 00:00:00 2001 From: dantrap Date: Fri, 23 May 2025 14:47:32 +0300 Subject: [PATCH 2/2] Refactor: Rename MultiPaneMenu components and UI elements --- .../samples/features/menu/domain/Sample.kt | 2 +- .../samples/features/multipane_menu/DI.kt | 28 ++-- ...MenuComponent.kt => MultiPaneComponent.kt} | 8 +- .../{MultiPaneMenuUi.kt => MultiPaneUi.kt} | 124 +++++++++++++++--- ...Component.kt => RealMultiPaneComponent.kt} | 36 ++--- ...ponent.kt => MultiPaneDetailsComponent.kt} | 2 +- ...mpleDetailsUi.kt => MultiPaneDetailsUi.kt} | 42 +++--- ...nt.kt => RealMultiPaneDetailsComponent.kt} | 51 +++---- ...Component.kt => MultiPaneMenuComponent.kt} | 2 +- .../{SampleListUi.kt => MultiPaneMenuUi.kt} | 22 ++-- ...onent.kt => RealMultiPaneMenuComponent.kt} | 10 +- .../root/presentation/RealRootComponent.kt | 9 +- .../root/presentation/RootComponent.kt | 4 +- .../features/root/presentation/RootUi.kt | 4 +- .../src/main/res/values-ru/menu_strings.xml | 2 + features/src/main/res/values/menu_strings.xml | 2 + 16 files changed, 225 insertions(+), 123 deletions(-) rename features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/{MultiPaneMenuComponent.kt => MultiPaneComponent.kt} (73%) rename features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/{MultiPaneMenuUi.kt => MultiPaneUi.kt} (53%) rename features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/{RealMultiPaneMenuComponent.kt => RealMultiPaneComponent.kt} (62%) rename features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/{SampleDetailsComponent.kt => MultiPaneDetailsComponent.kt} (98%) rename features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/{SampleDetailsUi.kt => MultiPaneDetailsUi.kt} (51%) rename features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/{RealSampleDetailsComponent.kt => RealMultiPaneDetailsComponent.kt} (77%) rename features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/{SampleListComponent.kt => MultiPaneMenuComponent.kt} (90%) rename features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/{SampleListUi.kt => MultiPaneMenuUi.kt} (84%) rename features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/{RealSampleListComponent.kt => RealMultiPaneMenuComponent.kt} (51%) diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/menu/domain/Sample.kt b/features/src/main/kotlin/ru/mobileup/samples/features/menu/domain/Sample.kt index bfe1afd3..dd6e7d70 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/menu/domain/Sample.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/menu/domain/Sample.kt @@ -8,7 +8,7 @@ import ru.mobileup.samples.features.R enum class Sample( override val displayName: StringDesc ) : DisplayedEnum { - + MultiPaneMenu(R.string.menu_item_multi_pane_menu.strResDesc()), Form(R.string.menu_item_form.strResDesc()), Otp(R.string.menu_item_otp.strResDesc()), Photo(R.string.menu_item_photo.strResDesc()), diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/DI.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/DI.kt index a123515e..ba50670d 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/DI.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/DI.kt @@ -4,25 +4,25 @@ import com.arkivanov.decompose.ComponentContext import org.koin.core.component.get import ru.mobileup.samples.core.ComponentFactory import ru.mobileup.samples.features.menu.domain.Sample -import ru.mobileup.samples.features.multipane_menu.presentation.MultiPaneMenuComponent -import ru.mobileup.samples.features.multipane_menu.presentation.RealMultiPaneMenuComponent -import ru.mobileup.samples.features.multipane_menu.presentation.details.RealSampleDetailsComponent -import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent -import ru.mobileup.samples.features.multipane_menu.presentation.list.RealSampleListComponent -import ru.mobileup.samples.features.multipane_menu.presentation.list.SampleListComponent +import ru.mobileup.samples.features.multipane_menu.presentation.MultiPaneComponent +import ru.mobileup.samples.features.multipane_menu.presentation.RealMultiPaneComponent +import ru.mobileup.samples.features.multipane_menu.presentation.details.MultiPaneDetailsComponent +import ru.mobileup.samples.features.multipane_menu.presentation.details.RealMultiPaneDetailsComponent +import ru.mobileup.samples.features.multipane_menu.presentation.list.MultiPaneMenuComponent +import ru.mobileup.samples.features.multipane_menu.presentation.list.RealMultiPaneMenuComponent fun ComponentFactory.createMultiPaneComponent( componentContext: ComponentContext, - onOutput: (MultiPaneMenuComponent.Output) -> Unit, -): MultiPaneMenuComponent = RealMultiPaneMenuComponent(componentContext, onOutput, get()) + onOutput: (MultiPaneComponent.Output) -> Unit, +): MultiPaneComponent = RealMultiPaneComponent(componentContext, onOutput, get()) -fun ComponentFactory.createSampleListComponent( +fun ComponentFactory.createMultiPaneMenuComponent( componentContext: ComponentContext, - onOutput: (SampleListComponent.Output) -> Unit, -): SampleListComponent = RealSampleListComponent(componentContext, onOutput) + onOutput: (MultiPaneMenuComponent.Output) -> Unit, +): MultiPaneMenuComponent = RealMultiPaneMenuComponent(componentContext, onOutput) -fun ComponentFactory.createSampleDetailsComponent( +fun ComponentFactory.createMultiPaneDetailsComponent( componentContext: ComponentContext, sample: Sample, - onOutput: (SampleDetailsComponent.Output) -> Unit, -): SampleDetailsComponent = RealSampleDetailsComponent(componentContext, sample, onOutput, get()) + onOutput: (MultiPaneDetailsComponent.Output) -> Unit, +): MultiPaneDetailsComponent = RealMultiPaneDetailsComponent(componentContext, sample, onOutput, get()) diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneMenuComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneComponent.kt similarity index 73% rename from features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneMenuComponent.kt rename to features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneComponent.kt index bf0d95d4..6281e0a4 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneMenuComponent.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneComponent.kt @@ -5,13 +5,13 @@ import com.arkivanov.decompose.router.panels.ChildPanels import com.arkivanov.decompose.router.panels.ChildPanelsMode import kotlinx.coroutines.flow.StateFlow import ru.mobileup.samples.core.utils.PredictiveBackComponent -import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent -import ru.mobileup.samples.features.multipane_menu.presentation.list.SampleListComponent +import ru.mobileup.samples.features.multipane_menu.presentation.details.MultiPaneDetailsComponent +import ru.mobileup.samples.features.multipane_menu.presentation.list.MultiPaneMenuComponent @OptIn(ExperimentalDecomposeApi::class) -interface MultiPaneMenuComponent : PredictiveBackComponent { +interface MultiPaneComponent : PredictiveBackComponent { - val panels: StateFlow> + val panels: StateFlow> fun setMode(mode: ChildPanelsMode) fun onSettingsClick() diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneMenuUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneUi.kt similarity index 53% rename from features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneMenuUi.kt rename to features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneUi.kt index 278b3136..ea1839e2 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneMenuUi.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneUi.kt @@ -1,25 +1,42 @@ package ru.mobileup.samples.features.multipane_menu.presentation +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.outlined.Info import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.window.core.layout.WindowWidthSizeClass @@ -33,14 +50,17 @@ import com.arkivanov.decompose.extensions.compose.experimental.stack.animation.s import com.arkivanov.decompose.extensions.compose.stack.animation.predictiveback.materialPredictiveBackAnimatable import com.arkivanov.decompose.router.panels.ChildPanelsMode import ru.mobileup.samples.core.theme.custom.CustomTheme +import ru.mobileup.samples.core.utils.SystemBarIconsColor +import ru.mobileup.samples.core.utils.SystemBars import ru.mobileup.samples.features.R -import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsUi -import ru.mobileup.samples.features.multipane_menu.presentation.list.SampleListUi +import ru.mobileup.samples.features.multipane_menu.presentation.details.MultiPaneDetailsUi +import ru.mobileup.samples.features.multipane_menu.presentation.list.MultiPaneMenuUi +import ru.mobileup.samples.core.R as CoreR @OptIn(ExperimentalDecomposeApi::class, ExperimentalMaterial3Api::class) @Composable -fun MultiPaneMenuUi( - component: MultiPaneMenuComponent, +fun MultiPaneUi( + component: MultiPaneComponent, modifier: Modifier = Modifier, ) { val panels by component.panels.collectAsState() @@ -50,23 +70,34 @@ fun MultiPaneMenuUi( val mode = if (isCompact) ChildPanelsMode.SINGLE else ChildPanelsMode.DUAL + var showInfo by rememberSaveable { mutableStateOf(false) } + LaunchedEffect(mode) { component.setMode(mode) } Column( - modifier = modifier.run { - if (mode == ChildPanelsMode.DUAL) statusBarsPadding() else this - } + modifier = modifier.statusBarsPadding() ) { - if (mode == ChildPanelsMode.DUAL) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(56.dp) + Row( + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.End + ) { + IconButton( + onClick = { showInfo = !showInfo } ) { + Icon( + imageVector = Icons.Outlined.Info, + contentDescription = null, + tint = CustomTheme.colors.icon.primary + ) + } + + if (mode == ChildPanelsMode.DUAL) { IconButton( - modifier = Modifier.align(Alignment.CenterEnd), onClick = component::onSettingsClick ) { Icon( @@ -81,14 +112,14 @@ fun MultiPaneMenuUi( ChildPanels( panels = panels, mainChild = { - SampleListUi( + MultiPaneMenuUi( modifier = Modifier.background(CustomTheme.colors.background.screen), component = it.instance, mode = mode ) }, detailsChild = { - SampleDetailsUi( + MultiPaneDetailsUi( modifier = Modifier.background(CustomTheme.colors.background.screen), component = it.instance ) @@ -114,4 +145,67 @@ fun MultiPaneMenuUi( }, ) } + + MultiPaneInfoDialog( + showDialog = showInfo, + onDismiss = { showInfo = false } + ) +} + +@Composable +private fun MultiPaneInfoDialog( + showDialog: Boolean, + onDismiss: () -> Unit, + modifier: Modifier = Modifier, +) { + AnimatedVisibility( + modifier = modifier, + visible = showDialog, + enter = fadeIn(), + exit = fadeOut() + ) { + SystemBars( + statusBarColor = Color.Transparent, + navigationBarColor = Color.Transparent, + statusBarIconsColor = SystemBarIconsColor.Light, + navigationBarIconsColor = SystemBarIconsColor.Light + ) + + Box( + Modifier + .fillMaxSize() + .background(Color.Black.copy(alpha = 0.4f)) + .pointerInput(Unit) { + detectTapGestures { onDismiss() } + }, + contentAlignment = Alignment.Center + ) { + Surface( + modifier = Modifier + .padding(horizontal = 32.dp) + .widthIn(min = 280.dp, max = 560.dp), + shape = RoundedCornerShape(28.dp), + tonalElevation = 6.dp, + ) { + Column( + modifier = Modifier.padding( + start = 24.dp, + end = 24.dp, + top = 24.dp, + bottom = 16.dp + ), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text(text = stringResource(R.string.menu_multipane_info_description)) + TextButton( + modifier = Modifier.align(Alignment.End), + onClick = onDismiss + ) { + Text(stringResource(CoreR.string.common_ok)) + } + } + } + } + } } diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/RealMultiPaneMenuComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/RealMultiPaneComponent.kt similarity index 62% rename from features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/RealMultiPaneMenuComponent.kt rename to features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/RealMultiPaneComponent.kt index 4f3c5541..0fbc48f5 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/RealMultiPaneMenuComponent.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/RealMultiPaneComponent.kt @@ -17,58 +17,58 @@ import kotlinx.serialization.builtins.serializer import ru.mobileup.samples.core.ComponentFactory import ru.mobileup.samples.core.utils.toStateFlow import ru.mobileup.samples.features.menu.domain.Sample -import ru.mobileup.samples.features.multipane_menu.createSampleDetailsComponent -import ru.mobileup.samples.features.multipane_menu.createSampleListComponent -import ru.mobileup.samples.features.multipane_menu.presentation.details.SampleDetailsComponent -import ru.mobileup.samples.features.multipane_menu.presentation.list.SampleListComponent +import ru.mobileup.samples.features.multipane_menu.createMultiPaneDetailsComponent +import ru.mobileup.samples.features.multipane_menu.createMultiPaneMenuComponent +import ru.mobileup.samples.features.multipane_menu.presentation.details.MultiPaneDetailsComponent +import ru.mobileup.samples.features.multipane_menu.presentation.list.MultiPaneMenuComponent @OptIn(ExperimentalDecomposeApi::class) -class RealMultiPaneMenuComponent( +class RealMultiPaneComponent( componentContext: ComponentContext, - private val onOutput: (MultiPaneMenuComponent.Output) -> Unit, + private val onOutput: (MultiPaneComponent.Output) -> Unit, private val componentFactory: ComponentFactory, -) : ComponentContext by componentContext, MultiPaneMenuComponent { +) : ComponentContext by componentContext, MultiPaneComponent { private val navigation = PanelsNavigation() @OptIn(ExperimentalSerializationApi::class) - override val panels: StateFlow> = + override val panels: StateFlow> = childPanels( source = navigation, serializers = Unit.serializer() to DetailsConfig.serializer(), initialPanels = { Panels(main = Unit) }, handleBackButton = true, mainFactory = { _, ctx -> - componentFactory.createSampleListComponent(ctx, ::onSampleListOutput) + componentFactory.createMultiPaneMenuComponent(ctx, ::onMenuOutput) }, detailsFactory = { cfg, ctx -> - componentFactory.createSampleDetailsComponent( + componentFactory.createMultiPaneDetailsComponent( ctx, cfg.sample, - ::onSampleDetailsOutput + ::onDetailsOutput ) }, ).toStateFlow(lifecycle) - private fun onSampleListOutput(output: SampleListComponent.Output) = when (output) { - is SampleListComponent.Output.SampleChosen -> navigation.activateDetails( + private fun onMenuOutput(output: MultiPaneMenuComponent.Output) = when (output) { + is MultiPaneMenuComponent.Output.SampleChosen -> navigation.activateDetails( DetailsConfig(output.sample) ) - SampleListComponent.Output.SettingsRequested -> onOutput( - MultiPaneMenuComponent.Output.SettingsRequested + MultiPaneMenuComponent.Output.SettingsRequested -> onOutput( + MultiPaneComponent.Output.SettingsRequested ) } - private fun onSampleDetailsOutput(output: SampleDetailsComponent.Output) = when (output) { - SampleDetailsComponent.Output.OtpSuccessfullyVerified -> navigation.pop() + private fun onDetailsOutput(output: MultiPaneDetailsComponent.Output) = when (output) { + MultiPaneDetailsComponent.Output.OtpSuccessfullyVerified -> navigation.pop() } override fun onBackClick() = navigation.pop() override fun setMode(mode: ChildPanelsMode) = navigation.setMode(mode) - override fun onSettingsClick() = onOutput(MultiPaneMenuComponent.Output.SettingsRequested) + override fun onSettingsClick() = onOutput(MultiPaneComponent.Output.SettingsRequested) @Serializable private data class DetailsConfig(val sample: Sample) diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/SampleDetailsComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/MultiPaneDetailsComponent.kt similarity index 98% rename from features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/SampleDetailsComponent.kt rename to features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/MultiPaneDetailsComponent.kt index 3233ea0d..445150f9 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/SampleDetailsComponent.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/MultiPaneDetailsComponent.kt @@ -22,7 +22,7 @@ import ru.mobileup.samples.features.tutorial.presentation.TutorialSampleComponen import ru.mobileup.samples.features.video.presentation.VideoComponent import ru.mobileup.samples.features.work_manager.presentation.WorkManagerComponent -interface SampleDetailsComponent { +interface MultiPaneDetailsComponent { val childStack: StateFlow> diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/SampleDetailsUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/MultiPaneDetailsUi.kt similarity index 51% rename from features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/SampleDetailsUi.kt rename to features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/MultiPaneDetailsUi.kt index 5d43c7b9..c60de2cc 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/SampleDetailsUi.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/MultiPaneDetailsUi.kt @@ -26,8 +26,8 @@ import ru.mobileup.samples.features.video.presentation.VideoUi import ru.mobileup.samples.features.work_manager.presentation.WorkManagerUi @Composable -fun SampleDetailsUi( - component: SampleDetailsComponent, +fun MultiPaneDetailsUi( + component: MultiPaneDetailsComponent, modifier: Modifier = Modifier, ) { val stack by component.childStack.collectAsState() @@ -37,25 +37,25 @@ fun SampleDetailsUi( stack = stack, ) { child -> when (val instance = child.instance) { - is SampleDetailsComponent.Child.Form -> FormUi(instance.component) - is SampleDetailsComponent.Child.Otp -> OtpUi(instance.component) - is SampleDetailsComponent.Child.Photo -> PhotoUi(instance.component) - is SampleDetailsComponent.Child.Video -> VideoUi(instance.component) - is SampleDetailsComponent.Child.Document -> DocumentUi(instance.component) - is SampleDetailsComponent.Child.RemoteTransfer -> RemoteTransferUi(instance.component) - is SampleDetailsComponent.Child.Calendar -> CalendarUi(instance.component) - is SampleDetailsComponent.Child.QrCode -> QrCodeUi(instance.component) - is SampleDetailsComponent.Child.Chart -> ChartUi(instance.component) - is SampleDetailsComponent.Child.Navigation -> NavigationUi(instance.component) - is SampleDetailsComponent.Child.CollapsingToolbar -> CollapsingToolbarUi(instance.component) - is SampleDetailsComponent.Child.Image -> ImageUi(instance.component) - is SampleDetailsComponent.Child.Tutorial -> TutorialSampleUi(instance.component) - is SampleDetailsComponent.Child.SharedElements -> SharedElementsUi(instance.component) - is SampleDetailsComponent.Child.PinCodeSettings -> PinCodeSettingsUi(instance.component) - is SampleDetailsComponent.Child.Map -> MapUi(instance.component) - is SampleDetailsComponent.Child.Chat -> ChatUi(instance.component) - is SampleDetailsComponent.Child.WorkManager -> WorkManagerUi(instance.component) - is SampleDetailsComponent.Child.DivKit -> DivKitUi(instance.component) + is MultiPaneDetailsComponent.Child.Form -> FormUi(instance.component) + is MultiPaneDetailsComponent.Child.Otp -> OtpUi(instance.component) + is MultiPaneDetailsComponent.Child.Photo -> PhotoUi(instance.component) + is MultiPaneDetailsComponent.Child.Video -> VideoUi(instance.component) + is MultiPaneDetailsComponent.Child.Document -> DocumentUi(instance.component) + is MultiPaneDetailsComponent.Child.RemoteTransfer -> RemoteTransferUi(instance.component) + is MultiPaneDetailsComponent.Child.Calendar -> CalendarUi(instance.component) + is MultiPaneDetailsComponent.Child.QrCode -> QrCodeUi(instance.component) + is MultiPaneDetailsComponent.Child.Chart -> ChartUi(instance.component) + is MultiPaneDetailsComponent.Child.Navigation -> NavigationUi(instance.component) + is MultiPaneDetailsComponent.Child.CollapsingToolbar -> CollapsingToolbarUi(instance.component) + is MultiPaneDetailsComponent.Child.Image -> ImageUi(instance.component) + is MultiPaneDetailsComponent.Child.Tutorial -> TutorialSampleUi(instance.component) + is MultiPaneDetailsComponent.Child.SharedElements -> SharedElementsUi(instance.component) + is MultiPaneDetailsComponent.Child.PinCodeSettings -> PinCodeSettingsUi(instance.component) + is MultiPaneDetailsComponent.Child.Map -> MapUi(instance.component) + is MultiPaneDetailsComponent.Child.Chat -> ChatUi(instance.component) + is MultiPaneDetailsComponent.Child.WorkManager -> WorkManagerUi(instance.component) + is MultiPaneDetailsComponent.Child.DivKit -> DivKitUi(instance.component) } } } diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/RealSampleDetailsComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/RealMultiPaneDetailsComponent.kt similarity index 77% rename from features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/RealSampleDetailsComponent.kt rename to features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/RealMultiPaneDetailsComponent.kt index 79db0546..b86bc7f3 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/RealSampleDetailsComponent.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/RealMultiPaneDetailsComponent.kt @@ -30,16 +30,16 @@ import ru.mobileup.samples.features.tutorial.createTutorialSampleComponent import ru.mobileup.samples.features.video.createVideoComponent import ru.mobileup.samples.features.work_manager.createWorkManagerComponent -class RealSampleDetailsComponent( +class RealMultiPaneDetailsComponent( componentContext: ComponentContext, sample: Sample, - private val onOutput: (SampleDetailsComponent.Output) -> Unit, + private val onOutput: (MultiPaneDetailsComponent.Output) -> Unit, private val componentFactory: ComponentFactory, -) : ComponentContext by componentContext, SampleDetailsComponent { +) : ComponentContext by componentContext, MultiPaneDetailsComponent { private val navigation = StackNavigation() - override val childStack: StateFlow> = childStack( + override val childStack: StateFlow> = childStack( source = navigation, initialConfiguration = when (sample) { Sample.Form -> ChildConfig.Form @@ -61,6 +61,7 @@ class RealSampleDetailsComponent( Sample.Chat -> ChildConfig.Chat Sample.WorkManager -> ChildConfig.WorkManager Sample.DivKit -> ChildConfig.DivKit + else -> error("Not supported sample") }, serializer = ChildConfig.serializer(), handleBackButton = true, @@ -70,87 +71,87 @@ class RealSampleDetailsComponent( private fun createChild( config: ChildConfig, componentContext: ComponentContext, - ): SampleDetailsComponent.Child = when (config) { - ChildConfig.Form -> SampleDetailsComponent.Child.Form( + ): MultiPaneDetailsComponent.Child = when (config) { + ChildConfig.Form -> MultiPaneDetailsComponent.Child.Form( componentFactory.createFormComponent(componentContext) ) - ChildConfig.Otp -> SampleDetailsComponent.Child.Otp( + ChildConfig.Otp -> MultiPaneDetailsComponent.Child.Otp( componentFactory.createOtpComponent(componentContext, ::onOtpOutput) ) - ChildConfig.Photo -> SampleDetailsComponent.Child.Photo( + ChildConfig.Photo -> MultiPaneDetailsComponent.Child.Photo( componentFactory.createPhotoComponent(componentContext) ) - ChildConfig.Video -> SampleDetailsComponent.Child.Video( + ChildConfig.Video -> MultiPaneDetailsComponent.Child.Video( componentFactory.createVideoComponent(componentContext) ) - ChildConfig.Document -> SampleDetailsComponent.Child.Document( + ChildConfig.Document -> MultiPaneDetailsComponent.Child.Document( componentFactory.createDocumentComponent(componentContext) ) - ChildConfig.Uploader -> SampleDetailsComponent.Child.RemoteTransfer( + ChildConfig.Uploader -> MultiPaneDetailsComponent.Child.RemoteTransfer( componentFactory.createRemoteTransferComponent(componentContext) ) - ChildConfig.Calendar -> SampleDetailsComponent.Child.Calendar( + ChildConfig.Calendar -> MultiPaneDetailsComponent.Child.Calendar( componentFactory.createCalendarComponent(componentContext) ) - ChildConfig.QrCode -> SampleDetailsComponent.Child.QrCode( + ChildConfig.QrCode -> MultiPaneDetailsComponent.Child.QrCode( componentFactory.createQrCodeComponent(componentContext) ) - ChildConfig.Chart -> SampleDetailsComponent.Child.Chart( + ChildConfig.Chart -> MultiPaneDetailsComponent.Child.Chart( componentFactory.createChartComponent(componentContext) ) - ChildConfig.Navigation -> SampleDetailsComponent.Child.Navigation( + ChildConfig.Navigation -> MultiPaneDetailsComponent.Child.Navigation( componentFactory.createNavigationComponent(componentContext) ) - ChildConfig.CollapsingToolbar -> SampleDetailsComponent.Child.CollapsingToolbar( + ChildConfig.CollapsingToolbar -> MultiPaneDetailsComponent.Child.CollapsingToolbar( componentFactory.createCollapsingToolbarComponent(componentContext) ) - ChildConfig.Image -> SampleDetailsComponent.Child.Image( + ChildConfig.Image -> MultiPaneDetailsComponent.Child.Image( componentFactory.createImageComponent(componentContext) ) - ChildConfig.Tutorial -> SampleDetailsComponent.Child.Tutorial( + ChildConfig.Tutorial -> MultiPaneDetailsComponent.Child.Tutorial( componentFactory.createTutorialSampleComponent(componentContext) ) - ChildConfig.SharedElements -> SampleDetailsComponent.Child.SharedElements( + ChildConfig.SharedElements -> MultiPaneDetailsComponent.Child.SharedElements( componentFactory.createSharedElementsComponent(componentContext) ) - ChildConfig.PinCodeSettings -> SampleDetailsComponent.Child.PinCodeSettings( + ChildConfig.PinCodeSettings -> MultiPaneDetailsComponent.Child.PinCodeSettings( componentFactory.createPinCodeSettingsComponent(componentContext) ) - ChildConfig.Map -> SampleDetailsComponent.Child.Map( + ChildConfig.Map -> MultiPaneDetailsComponent.Child.Map( componentFactory.createMapMainComponent(componentContext) ) - ChildConfig.Chat -> SampleDetailsComponent.Child.Chat( + ChildConfig.Chat -> MultiPaneDetailsComponent.Child.Chat( componentFactory.createChatComponent(componentContext) ) - ChildConfig.WorkManager -> SampleDetailsComponent.Child.WorkManager( + ChildConfig.WorkManager -> MultiPaneDetailsComponent.Child.WorkManager( componentFactory.createWorkManagerComponent(componentContext) ) - ChildConfig.DivKit -> SampleDetailsComponent.Child.DivKit( + ChildConfig.DivKit -> MultiPaneDetailsComponent.Child.DivKit( componentFactory.createDivKitComponent(componentContext) ) } private fun onOtpOutput(output: OtpComponent.Output) = when (output) { OtpComponent.Output.OtpSuccessfullyVerified -> onOutput( - SampleDetailsComponent.Output.OtpSuccessfullyVerified + MultiPaneDetailsComponent.Output.OtpSuccessfullyVerified ) } diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/SampleListComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/MultiPaneMenuComponent.kt similarity index 90% rename from features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/SampleListComponent.kt rename to features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/MultiPaneMenuComponent.kt index 57253ab7..b24bb02c 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/SampleListComponent.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/MultiPaneMenuComponent.kt @@ -2,7 +2,7 @@ package ru.mobileup.samples.features.multipane_menu.presentation.list import ru.mobileup.samples.features.menu.domain.Sample -interface SampleListComponent { +interface MultiPaneMenuComponent { fun onButtonClick(sample: Sample) fun onSettingsClick() diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/SampleListUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/MultiPaneMenuUi.kt similarity index 84% rename from features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/SampleListUi.kt rename to features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/MultiPaneMenuUi.kt index 537d9663..9612e4c8 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/SampleListUi.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/MultiPaneMenuUi.kt @@ -29,8 +29,8 @@ import ru.mobileup.samples.features.menu.domain.Sample @OptIn(ExperimentalDecomposeApi::class) @Composable -fun SampleListUi( - component: SampleListComponent, +fun MultiPaneMenuUi( + component: MultiPaneMenuComponent, mode: ChildPanelsMode, modifier: Modifier = Modifier, ) { @@ -59,14 +59,16 @@ fun SampleListUi( .padding(32.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Sample.entries.forEach { sample -> - AppButton( - modifier = Modifier.fillMaxWidth(), - buttonType = ButtonType.Secondary, - text = sample.displayName.localized(), - onClick = { component.onButtonClick(sample) } - ) - } + Sample.entries + .filter { it != Sample.MultiPaneMenu } + .forEach { sample -> + AppButton( + modifier = Modifier.fillMaxWidth(), + buttonType = ButtonType.Secondary, + text = sample.displayName.localized(), + onClick = { component.onButtonClick(sample) } + ) + } /* Manually add bottom spacing equal to FAB height because the padding provided by Scaffold is not always correct on all Android versions. */ diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/RealSampleListComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/RealMultiPaneMenuComponent.kt similarity index 51% rename from features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/RealSampleListComponent.kt rename to features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/RealMultiPaneMenuComponent.kt index 04f90d91..9e1d27f3 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/RealSampleListComponent.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/RealMultiPaneMenuComponent.kt @@ -3,16 +3,16 @@ package ru.mobileup.samples.features.multipane_menu.presentation.list import com.arkivanov.decompose.ComponentContext import ru.mobileup.samples.features.menu.domain.Sample -class RealSampleListComponent( +class RealMultiPaneMenuComponent( componentContext: ComponentContext, - private val onOutput: (SampleListComponent.Output) -> Unit, -) : ComponentContext by componentContext, SampleListComponent { + private val onOutput: (MultiPaneMenuComponent.Output) -> Unit, +) : ComponentContext by componentContext, MultiPaneMenuComponent { override fun onButtonClick(sample: Sample) { - onOutput(SampleListComponent.Output.SampleChosen(sample)) + onOutput(MultiPaneMenuComponent.Output.SampleChosen(sample)) } override fun onSettingsClick() { - onOutput(SampleListComponent.Output.SettingsRequested) + onOutput(MultiPaneMenuComponent.Output.SettingsRequested) } } diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RealRootComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RealRootComponent.kt index 6371b2ca..7b6532c8 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RealRootComponent.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RealRootComponent.kt @@ -25,7 +25,7 @@ import ru.mobileup.samples.features.menu.createMenuComponent import ru.mobileup.samples.features.menu.domain.Sample import ru.mobileup.samples.features.menu.presentation.MenuComponent import ru.mobileup.samples.features.multipane_menu.createMultiPaneComponent -import ru.mobileup.samples.features.multipane_menu.presentation.MultiPaneMenuComponent +import ru.mobileup.samples.features.multipane_menu.presentation.MultiPaneComponent import ru.mobileup.samples.features.navigation.createNavigationComponent import ru.mobileup.samples.features.otp.createOtpComponent import ru.mobileup.samples.features.otp.presentation.OtpComponent @@ -50,7 +50,7 @@ class RealRootComponent( override val childStack = childStack( source = navigation, - initialConfiguration = ChildConfig.MultiPaneMenu, + initialConfiguration = ChildConfig.Menu, serializer = ChildConfig.serializer(), handleBackButton = true, childFactory = ::createChild @@ -210,8 +210,8 @@ class RealRootComponent( ) } - private fun onMultiPaneOutput(output: MultiPaneMenuComponent.Output) = when (output) { - MultiPaneMenuComponent.Output.SettingsRequested -> navigation.safePush(ChildConfig.Settings) + private fun onMultiPaneOutput(output: MultiPaneComponent.Output) = when (output) { + MultiPaneComponent.Output.SettingsRequested -> navigation.safePush(ChildConfig.Settings) } private fun onMenuOutput(output: MenuComponent.Output) { @@ -236,6 +236,7 @@ class RealRootComponent( Sample.Chat -> ChildConfig.Chat Sample.WorkManager -> ChildConfig.WorkManager Sample.DivKit -> ChildConfig.DivKit + Sample.MultiPaneMenu -> ChildConfig.MultiPaneMenu }.run(navigation::safePush) MenuComponent.Output.SettingsRequested -> navigation.safePush(ChildConfig.Settings) diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RootComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RootComponent.kt index 2d42bebe..109f7486 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RootComponent.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RootComponent.kt @@ -16,7 +16,7 @@ import ru.mobileup.samples.features.form.presentation.FormComponent import ru.mobileup.samples.features.image.presentation.ImageComponent import ru.mobileup.samples.features.map.presentation.MapComponent import ru.mobileup.samples.features.menu.presentation.MenuComponent -import ru.mobileup.samples.features.multipane_menu.presentation.MultiPaneMenuComponent +import ru.mobileup.samples.features.multipane_menu.presentation.MultiPaneComponent import ru.mobileup.samples.features.navigation.NavigationComponent import ru.mobileup.samples.features.otp.presentation.OtpComponent import ru.mobileup.samples.features.photo.presentation.PhotoComponent @@ -48,7 +48,7 @@ interface RootComponent : PredictiveBackComponent { val themeComponent: ThemeComponent sealed interface Child { - class MultiPaneMenu(val component: MultiPaneMenuComponent) : Child + class MultiPaneMenu(val component: MultiPaneComponent) : Child class Menu(val component: MenuComponent) : Child class Form(val component: FormComponent) : Child class Otp(val component: OtpComponent) : Child diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RootUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RootUi.kt index cf411985..fcdd9ce6 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RootUi.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/root/presentation/RootUi.kt @@ -28,7 +28,7 @@ import ru.mobileup.samples.features.form.presentation.FormUi import ru.mobileup.samples.features.image.presentation.ImageUi import ru.mobileup.samples.features.map.presentation.MapUi import ru.mobileup.samples.features.menu.presentation.MenuUi -import ru.mobileup.samples.features.multipane_menu.presentation.MultiPaneMenuUi +import ru.mobileup.samples.features.multipane_menu.presentation.MultiPaneUi import ru.mobileup.samples.features.navigation.NavigationUi import ru.mobileup.samples.features.otp.presentation.OtpUi import ru.mobileup.samples.features.photo.presentation.PhotoUi @@ -59,7 +59,7 @@ fun RootUi( animation = component.predictiveBackAnimation() ) { child -> when (val instance = child.instance) { - is RootComponent.Child.MultiPaneMenu -> MultiPaneMenuUi(instance.component) + is RootComponent.Child.MultiPaneMenu -> MultiPaneUi(instance.component) is RootComponent.Child.Menu -> MenuUi(instance.component) is RootComponent.Child.Form -> FormUi(instance.component) is RootComponent.Child.Otp -> OtpUi(instance.component) diff --git a/features/src/main/res/values-ru/menu_strings.xml b/features/src/main/res/values-ru/menu_strings.xml index 30d0f5ff..a3a31602 100644 --- a/features/src/main/res/values-ru/menu_strings.xml +++ b/features/src/main/res/values-ru/menu_strings.xml @@ -1,4 +1,5 @@ + MultiPane Menu Form OTP Фото @@ -19,4 +20,5 @@ WorkManager ДивКит (SDUI) Выбирете сэмпл + Этот пример демонстрирует адаптивный интерфейс с поддержкой Multi-pane UI. В зависимости от устройства, интерфейс изменяется: на телефонах контент отображается на отдельных экранах, а на планшетах — экран делится на две части, показывая список и детали одновременно. \ No newline at end of file diff --git a/features/src/main/res/values/menu_strings.xml b/features/src/main/res/values/menu_strings.xml index 9f847670..e2e56db1 100644 --- a/features/src/main/res/values/menu_strings.xml +++ b/features/src/main/res/values/menu_strings.xml @@ -1,4 +1,5 @@ + MultiPane Menu Form OTP Photo @@ -19,4 +20,5 @@ WorkManager DivKit (SDUI) Select a sample + This sample demonstrates an adaptive interface using Multi-pane UI. The layout adjusts based on the device: on phones, content is shown on separate screens, while on tablets, the screen is split to display both the list and details side by side. \ No newline at end of file