diff --git a/features/build.gradle.kts b/features/build.gradle.kts index 1bb77aed..7e2de440 100644 --- a/features/build.gradle.kts +++ b/features/build.gradle.kts @@ -30,6 +30,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) @@ -88,9 +89,6 @@ dependencies { // Image Cropping implementation(libs.android.image.cropper) - // Work Manager - implementation(libs.work.ktx) - // DivKit implementation(libs.bundles.divkit.client) implementation(libs.bundles.divkit.server) diff --git a/features/module_graph/modules.dot b/features/module_graph/modules.dot index af6c8665..5a4df70f 100644 --- a/features/module_graph/modules.dot +++ b/features/module_graph/modules.dot @@ -3,43 +3,67 @@ calendar charts chat collapsing_toolbar +divkit document form image map menu +multipane_menu navigation otp photo pin_code qr_code +remote_transfer root settings shared_element_transitions tutorial -uploader video work_manager +multipane_menu -> calendar +multipane_menu -> charts +multipane_menu -> chat +multipane_menu -> collapsing_toolbar +multipane_menu -> divkit +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 -> remote_transfer +multipane_menu -> shared_element_transitions +multipane_menu -> tutorial +multipane_menu -> video +multipane_menu -> work_manager photo -> image photo -> video root -> calendar root -> charts root -> chat root -> collapsing_toolbar +root -> divkit root -> document root -> form root -> image root -> map root -> menu +root -> multipane_menu root -> navigation root -> otp root -> photo root -> pin_code root -> qr_code +root -> remote_transfer root -> settings root -> shared_element_transitions root -> tutorial -root -> uploader root -> video root -> work_manager } diff --git a/features/module_graph/modules.svg b/features/module_graph/modules.svg index 53fee31e..c3fd522c 100644 --- a/features/module_graph/modules.svg +++ b/features/module_graph/modules.svg @@ -1,264 +1,408 @@ - - - - + + + + -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 +divkit + +divkit - - -root->charts - - + + +multipane_menu->divkit + + - + -chat - -chat +document + +document - - -root->chat - - + + +multipane_menu->document + + - + -collapsing_toolbar - -collapsing_toolbar +form + +form - - -root->collapsing_toolbar - - + + +multipane_menu->form + + - + -document - -document +image + +image - - -root->document - - + + +multipane_menu->image + + - + -form - -form +map + +map - - -root->form - - + + +multipane_menu->map + + - + -map - -map +menu + +menu - + -root->map - - +multipane_menu->menu + + - + -menu - -menu +navigation + +navigation - + -root->menu - - +multipane_menu->navigation + + - + -navigation - -navigation +otp + +otp - + -root->navigation - - +multipane_menu->otp + + - + -otp - -otp +photo + +photo - + -root->otp - - +multipane_menu->photo + + pin_code - -pin_code + +pin_code - - -root->pin_code - - + + +multipane_menu->pin_code + + qr_code - -qr_code + +qr_code - - -root->qr_code - - + + +multipane_menu->qr_code + + - + -settings - -settings +remote_transfer + +remote_transfer - - -root->settings - - + + +multipane_menu->remote_transfer + + shared_element_transitions - -shared_element_transitions + +shared_element_transitions - - -root->shared_element_transitions - - + + +multipane_menu->shared_element_transitions + + tutorial - -tutorial + +tutorial - - -root->tutorial - - + + +multipane_menu->tutorial + + - + -uploader - -uploader +video + +video - - -root->uploader - - + + +multipane_menu->video + + work_manager - -work_manager + +work_manager - + + +multipane_menu->work_manager + + + + + +photo->image + + + + +photo->video + + + + + +root + +root + + + +root->multipane_menu + + + + + +root->calendar + + + + + +root->charts + + + + + +root->chat + + + + + +root->collapsing_toolbar + + + + + +root->divkit + + + + + +root->document + + + + + +root->form + + + + + +root->image + + + + + +root->map + + + + + +root->menu + + + + + +root->navigation + + + + + +root->otp + + + + + +root->photo + + + + + +root->pin_code + + + + + +root->qr_code + + + + + +root->remote_transfer + + + + + +root->shared_element_transitions + + + + + +root->tutorial + + + + + +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/divkit/DI.kt b/features/src/main/kotlin/ru/mobileup/samples/features/divkit/DI.kt index 87565996..1476047e 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/divkit/DI.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/divkit/DI.kt @@ -7,12 +7,12 @@ import com.yandex.div.json.ParsingErrorLogger import org.koin.core.component.get import org.koin.dsl.module import ru.mobileup.samples.core.ComponentFactory +import ru.mobileup.samples.features.divkit.data.DivKitRepository +import ru.mobileup.samples.features.divkit.data.DivKitRepositoryImpl import ru.mobileup.samples.features.divkit.data.api.DivKitApi import ru.mobileup.samples.features.divkit.data.api.DivKitApiImpl import ru.mobileup.samples.features.divkit.data.db.DivKitGoodsDb import ru.mobileup.samples.features.divkit.data.db.DivKitGoodsDbImpl -import ru.mobileup.samples.features.divkit.data.DivKitRepository -import ru.mobileup.samples.features.divkit.data.DivKitRepositoryImpl import ru.mobileup.samples.features.divkit.presentation.DivKitComponent import ru.mobileup.samples.features.divkit.presentation.RealDivKitComponent import ru.mobileup.samples.features.divkit.presentation.example_details.DivKitExampleDetailsComponent diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/divkit/presentation/example_details/RealDivKitExampleDetailsComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/divkit/presentation/example_details/RealDivKitExampleDetailsComponent.kt index 87fd98ec..ab7c036a 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/divkit/presentation/example_details/RealDivKitExampleDetailsComponent.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/divkit/presentation/example_details/RealDivKitExampleDetailsComponent.kt @@ -1,6 +1,7 @@ package ru.mobileup.samples.features.divkit.presentation.example_details import android.net.Uri +import androidx.core.net.toUri import com.arkivanov.decompose.ComponentContext import com.yandex.div.core.Div2Context import com.yandex.div.core.DivActionHandler @@ -19,7 +20,6 @@ import ru.mobileup.samples.core.message.domain.Message import ru.mobileup.samples.core.utils.componentScope import ru.mobileup.samples.features.divkit.data.DivKitRepository import ru.mobileup.samples.features.divkit.presentation.extension.tag -import androidx.core.net.toUri class RealDivKitExampleDetailsComponent( componentContext: ComponentContext, 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/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 new file mode 100644 index 00000000..ba50670d --- /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.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: (MultiPaneComponent.Output) -> Unit, +): MultiPaneComponent = RealMultiPaneComponent(componentContext, onOutput, get()) + +fun ComponentFactory.createMultiPaneMenuComponent( + componentContext: ComponentContext, + onOutput: (MultiPaneMenuComponent.Output) -> Unit, +): MultiPaneMenuComponent = RealMultiPaneMenuComponent(componentContext, onOutput) + +fun ComponentFactory.createMultiPaneDetailsComponent( + componentContext: ComponentContext, + sample: Sample, + onOutput: (MultiPaneDetailsComponent.Output) -> Unit, +): MultiPaneDetailsComponent = RealMultiPaneDetailsComponent(componentContext, sample, onOutput, get()) diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneComponent.kt new file mode 100644 index 00000000..6281e0a4 --- /dev/null +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneComponent.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.MultiPaneDetailsComponent +import ru.mobileup.samples.features.multipane_menu.presentation.list.MultiPaneMenuComponent + +@OptIn(ExperimentalDecomposeApi::class) +interface MultiPaneComponent : 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/MultiPaneUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneUi.kt new file mode 100644 index 00000000..ea1839e2 --- /dev/null +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/MultiPaneUi.kt @@ -0,0 +1,211 @@ +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 +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.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.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 MultiPaneUi( + component: MultiPaneComponent, + 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 + + var showInfo by rememberSaveable { mutableStateOf(false) } + + LaunchedEffect(mode) { + component.setMode(mode) + } + + Column( + modifier = modifier.statusBarsPadding() + ) { + 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( + onClick = component::onSettingsClick + ) { + Icon( + imageVector = Icons.Default.Settings, + contentDescription = null, + tint = CustomTheme.colors.icon.primary + ) + } + } + } + + ChildPanels( + panels = panels, + mainChild = { + MultiPaneMenuUi( + modifier = Modifier.background(CustomTheme.colors.background.screen), + component = it.instance, + mode = mode + ) + }, + detailsChild = { + MultiPaneDetailsUi( + 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, + ) + }, + ) + } + + 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/RealMultiPaneComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/RealMultiPaneComponent.kt new file mode 100644 index 00000000..0fbc48f5 --- /dev/null +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/RealMultiPaneComponent.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.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 RealMultiPaneComponent( + componentContext: ComponentContext, + private val onOutput: (MultiPaneComponent.Output) -> Unit, + private val componentFactory: ComponentFactory, +) : ComponentContext by componentContext, MultiPaneComponent { + + 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.createMultiPaneMenuComponent(ctx, ::onMenuOutput) + }, + detailsFactory = { cfg, ctx -> + componentFactory.createMultiPaneDetailsComponent( + ctx, + cfg.sample, + ::onDetailsOutput + ) + }, + ).toStateFlow(lifecycle) + + private fun onMenuOutput(output: MultiPaneMenuComponent.Output) = when (output) { + is MultiPaneMenuComponent.Output.SampleChosen -> navigation.activateDetails( + DetailsConfig(output.sample) + ) + + MultiPaneMenuComponent.Output.SettingsRequested -> onOutput( + MultiPaneComponent.Output.SettingsRequested + ) + } + + 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(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/MultiPaneDetailsComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/MultiPaneDetailsComponent.kt new file mode 100644 index 00000000..445150f9 --- /dev/null +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/MultiPaneDetailsComponent.kt @@ -0,0 +1,54 @@ +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.divkit.presentation.DivKitComponent +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.remote_transfer.presentation.RemoteTransferComponent +import ru.mobileup.samples.features.shared_element_transitions.presentation.SharedElementsComponent +import ru.mobileup.samples.features.tutorial.presentation.TutorialSampleComponent +import ru.mobileup.samples.features.video.presentation.VideoComponent +import ru.mobileup.samples.features.work_manager.presentation.WorkManagerComponent + +interface MultiPaneDetailsComponent { + + 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 RemoteTransfer(val component: RemoteTransferComponent) : 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 + class DivKit(val component: DivKitComponent) : Child + } + + sealed interface Output { + data object OtpSuccessfullyVerified : Output + } +} diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/MultiPaneDetailsUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/MultiPaneDetailsUi.kt new file mode 100644 index 00000000..c60de2cc --- /dev/null +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/MultiPaneDetailsUi.kt @@ -0,0 +1,61 @@ +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.divkit.presentation.DivKitUi +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.remote_transfer.presentation.RemoteTransferUi +import ru.mobileup.samples.features.shared_element_transitions.presentation.SharedElementsUi +import ru.mobileup.samples.features.tutorial.presentation.TutorialSampleUi +import ru.mobileup.samples.features.video.presentation.VideoUi +import ru.mobileup.samples.features.work_manager.presentation.WorkManagerUi + +@Composable +fun MultiPaneDetailsUi( + component: MultiPaneDetailsComponent, + modifier: Modifier = Modifier, +) { + val stack by component.childStack.collectAsState() + + Children( + modifier = modifier, + stack = stack, + ) { child -> + when (val instance = child.instance) { + 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/RealMultiPaneDetailsComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/RealMultiPaneDetailsComponent.kt new file mode 100644 index 00000000..b86bc7f3 --- /dev/null +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/details/RealMultiPaneDetailsComponent.kt @@ -0,0 +1,218 @@ +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.divkit.createDivKitComponent +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.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.remote_transfer.createRemoteTransferComponent +import ru.mobileup.samples.features.shared_element_transitions.createSharedElementsComponent +import ru.mobileup.samples.features.tutorial.createTutorialSampleComponent +import ru.mobileup.samples.features.video.createVideoComponent +import ru.mobileup.samples.features.work_manager.createWorkManagerComponent + +class RealMultiPaneDetailsComponent( + componentContext: ComponentContext, + sample: Sample, + private val onOutput: (MultiPaneDetailsComponent.Output) -> Unit, + private val componentFactory: ComponentFactory, +) : ComponentContext by componentContext, MultiPaneDetailsComponent { + + 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.RemoteTransfer -> 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 + Sample.DivKit -> ChildConfig.DivKit + else -> error("Not supported sample") + }, + serializer = ChildConfig.serializer(), + handleBackButton = true, + childFactory = ::createChild + ).toStateFlow(lifecycle) + + private fun createChild( + config: ChildConfig, + componentContext: ComponentContext, + ): MultiPaneDetailsComponent.Child = when (config) { + ChildConfig.Form -> MultiPaneDetailsComponent.Child.Form( + componentFactory.createFormComponent(componentContext) + ) + + ChildConfig.Otp -> MultiPaneDetailsComponent.Child.Otp( + componentFactory.createOtpComponent(componentContext, ::onOtpOutput) + ) + + ChildConfig.Photo -> MultiPaneDetailsComponent.Child.Photo( + componentFactory.createPhotoComponent(componentContext) + ) + + ChildConfig.Video -> MultiPaneDetailsComponent.Child.Video( + componentFactory.createVideoComponent(componentContext) + ) + + ChildConfig.Document -> MultiPaneDetailsComponent.Child.Document( + componentFactory.createDocumentComponent(componentContext) + ) + + ChildConfig.Uploader -> MultiPaneDetailsComponent.Child.RemoteTransfer( + componentFactory.createRemoteTransferComponent(componentContext) + ) + + ChildConfig.Calendar -> MultiPaneDetailsComponent.Child.Calendar( + componentFactory.createCalendarComponent(componentContext) + ) + + ChildConfig.QrCode -> MultiPaneDetailsComponent.Child.QrCode( + componentFactory.createQrCodeComponent(componentContext) + ) + + ChildConfig.Chart -> MultiPaneDetailsComponent.Child.Chart( + componentFactory.createChartComponent(componentContext) + ) + + ChildConfig.Navigation -> MultiPaneDetailsComponent.Child.Navigation( + componentFactory.createNavigationComponent(componentContext) + ) + + ChildConfig.CollapsingToolbar -> MultiPaneDetailsComponent.Child.CollapsingToolbar( + componentFactory.createCollapsingToolbarComponent(componentContext) + ) + + ChildConfig.Image -> MultiPaneDetailsComponent.Child.Image( + componentFactory.createImageComponent(componentContext) + ) + + ChildConfig.Tutorial -> MultiPaneDetailsComponent.Child.Tutorial( + componentFactory.createTutorialSampleComponent(componentContext) + ) + + ChildConfig.SharedElements -> MultiPaneDetailsComponent.Child.SharedElements( + componentFactory.createSharedElementsComponent(componentContext) + ) + + ChildConfig.PinCodeSettings -> MultiPaneDetailsComponent.Child.PinCodeSettings( + componentFactory.createPinCodeSettingsComponent(componentContext) + ) + + ChildConfig.Map -> MultiPaneDetailsComponent.Child.Map( + componentFactory.createMapMainComponent(componentContext) + ) + + ChildConfig.Chat -> MultiPaneDetailsComponent.Child.Chat( + componentFactory.createChatComponent(componentContext) + ) + + ChildConfig.WorkManager -> MultiPaneDetailsComponent.Child.WorkManager( + componentFactory.createWorkManagerComponent(componentContext) + ) + + ChildConfig.DivKit -> MultiPaneDetailsComponent.Child.DivKit( + componentFactory.createDivKitComponent(componentContext) + ) + } + + private fun onOtpOutput(output: OtpComponent.Output) = when (output) { + OtpComponent.Output.OtpSuccessfullyVerified -> onOutput( + MultiPaneDetailsComponent.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 + + @Serializable + data object DivKit : ChildConfig + } +} diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/MultiPaneMenuComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/MultiPaneMenuComponent.kt new file mode 100644 index 00000000..b24bb02c --- /dev/null +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/MultiPaneMenuComponent.kt @@ -0,0 +1,14 @@ +package ru.mobileup.samples.features.multipane_menu.presentation.list + +import ru.mobileup.samples.features.menu.domain.Sample + +interface MultiPaneMenuComponent { + + 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/MultiPaneMenuUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/MultiPaneMenuUi.kt new file mode 100644 index 00000000..9612e4c8 --- /dev/null +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/MultiPaneMenuUi.kt @@ -0,0 +1,78 @@ +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 MultiPaneMenuUi( + component: MultiPaneMenuComponent, + 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 + .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. */ + if (mode == ChildPanelsMode.SINGLE) Spacer(Modifier.height(56.dp)) + } + } +} diff --git a/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/RealMultiPaneMenuComponent.kt b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/RealMultiPaneMenuComponent.kt new file mode 100644 index 00000000..9e1d27f3 --- /dev/null +++ b/features/src/main/kotlin/ru/mobileup/samples/features/multipane_menu/presentation/list/RealMultiPaneMenuComponent.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 RealMultiPaneMenuComponent( + componentContext: ComponentContext, + private val onOutput: (MultiPaneMenuComponent.Output) -> Unit, +) : ComponentContext by componentContext, MultiPaneMenuComponent { + + override fun onButtonClick(sample: Sample) { + onOutput(MultiPaneMenuComponent.Output.SampleChosen(sample)) + } + + override fun onSettingsClick() { + onOutput(MultiPaneMenuComponent.Output.SettingsRequested) + } +} 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/remote_transfer/presentation/RemoteTransferUi.kt b/features/src/main/kotlin/ru/mobileup/samples/features/remote_transfer/presentation/RemoteTransferUi.kt index 53367ca4..568f5a4a 100644 --- a/features/src/main/kotlin/ru/mobileup/samples/features/remote_transfer/presentation/RemoteTransferUi.kt +++ b/features/src/main/kotlin/ru/mobileup/samples/features/remote_transfer/presentation/RemoteTransferUi.kt @@ -30,8 +30,6 @@ import androidx.compose.ui.unit.dp import dev.icerock.moko.resources.compose.localized 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.utils.clickableNoRipple import ru.mobileup.samples.features.R import ru.mobileup.samples.features.remote_transfer.domain.RemoteTransferTab @@ -44,11 +42,6 @@ fun RemoteTransferUi( component: RemoteTransferComponent, modifier: Modifier = Modifier ) { - SystemBars( - statusBarColor = Color.Transparent, - statusBarIconsColor = SystemBarIconsColor.Light, - ) - RemoteTransferContent( component = component, modifier = modifier @@ -137,7 +130,7 @@ private fun RemoteTransferTopBar( Row( modifier = modifier .fillMaxWidth() - .background(CustomTheme.colors.palette.black) + .background(CustomTheme.colors.background.screen) .statusBarsPadding() .padding(horizontal = 8.dp, vertical = 24.dp) ) { @@ -150,7 +143,6 @@ private fun RemoteTransferTopBar( Text( text = stringResource(R.string.remote_transfer_title), - color = CustomTheme.colors.palette.white, modifier = Modifier .weight(2f) .align(Alignment.CenterVertically) 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 dff0d47f..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 @@ -24,6 +24,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.MultiPaneComponent import ru.mobileup.samples.features.navigation.createNavigationComponent import ru.mobileup.samples.features.otp.createOtpComponent import ru.mobileup.samples.features.otp.presentation.OtpComponent @@ -32,10 +34,10 @@ import ru.mobileup.samples.features.pin_code.createCheckPinCodeManagementCompone import ru.mobileup.samples.features.pin_code.createPinCodeSettingsComponent import ru.mobileup.samples.features.pin_code.presentation.check_management.CheckPinCodeManagementComponent import ru.mobileup.samples.features.qr_code.createQrCodeComponent +import ru.mobileup.samples.features.remote_transfer.createRemoteTransferComponent import ru.mobileup.samples.features.settings.createSettingsComponent import ru.mobileup.samples.features.shared_element_transitions.createSharedElementsComponent import ru.mobileup.samples.features.tutorial.createTutorialSampleComponent -import ru.mobileup.samples.features.remote_transfer.createRemoteTransferComponent import ru.mobileup.samples.features.video.createVideoComponent import ru.mobileup.samples.features.work_manager.createWorkManagerComponent @@ -77,6 +79,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) @@ -202,6 +210,10 @@ class RealRootComponent( ) } + private fun onMultiPaneOutput(output: MultiPaneComponent.Output) = when (output) { + MultiPaneComponent.Output.SettingsRequested -> navigation.safePush(ChildConfig.Settings) + } + private fun onMenuOutput(output: MenuComponent.Output) { when (output) { is MenuComponent.Output.SampleChosen -> when (output.sample) { @@ -224,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) @@ -239,6 +252,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 4985a21e..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,16 +16,17 @@ 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.MultiPaneComponent 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.check_management.CheckPinCodeManagementComponent import ru.mobileup.samples.features.pin_code.presentation.settings.PinCodeSettingsComponent import ru.mobileup.samples.features.qr_code.presentation.QrCodeComponent +import ru.mobileup.samples.features.remote_transfer.presentation.RemoteTransferComponent import ru.mobileup.samples.features.settings.presentation.SettingsComponent import ru.mobileup.samples.features.shared_element_transitions.presentation.SharedElementsComponent import ru.mobileup.samples.features.tutorial.presentation.TutorialSampleComponent -import ru.mobileup.samples.features.remote_transfer.presentation.RemoteTransferComponent import ru.mobileup.samples.features.video.presentation.VideoComponent import ru.mobileup.samples.features.work_manager.presentation.WorkManagerComponent @@ -47,6 +48,7 @@ interface RootComponent : PredictiveBackComponent { val themeComponent: ThemeComponent sealed interface 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 ff852cde..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,16 +28,17 @@ 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.MultiPaneUi 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.check_management.CheckPinCodeManagementUi import ru.mobileup.samples.features.pin_code.presentation.settings.PinCodeSettingsUi import ru.mobileup.samples.features.qr_code.presentation.QrCodeUi +import ru.mobileup.samples.features.remote_transfer.presentation.RemoteTransferUi import ru.mobileup.samples.features.settings.presentation.SettingsUi import ru.mobileup.samples.features.shared_element_transitions.presentation.SharedElementsUi import ru.mobileup.samples.features.tutorial.presentation.TutorialSampleUi -import ru.mobileup.samples.features.remote_transfer.presentation.RemoteTransferUi import ru.mobileup.samples.features.video.presentation.VideoUi import ru.mobileup.samples.features.work_manager.presentation.WorkManagerUi @@ -58,6 +59,7 @@ fun RootUi( animation = component.predictiveBackAnimation() ) { child -> when (val instance = child.instance) { + 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/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 4fee121b..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 Фото @@ -18,4 +19,6 @@ Чат 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 68ebdc77..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 @@ -18,4 +19,6 @@ Chat 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 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e6e4e3c8..57b93c12 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ detekt = "1.23.8" dateTime = "0.6.2" coroutines = "1.10.1" workManager = "2.10.1" -decompose = "3.2.2" +decompose = "3.3.0" ktor = "3.1.0" replica = "1.3.1-alpha1" koin = "4.0.2" @@ -47,9 +47,9 @@ androidImageCropper = "4.6.0" googleMaps = "6.5.2" sesame = "1.5.0" room = "2.7.0" -work = "2.10.1" divkit = "31.13.0" jackson = "2.18.2" +materialAdaptive = "1.1.0" [libraries] kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "dateTime" } @@ -92,6 +92,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" } @@ -158,8 +159,6 @@ room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } android-image-cropper = { module = "com.vanniktech:android-image-cropper", version.ref = "androidImageCropper" } -work-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "work" } - divkit-div = { module = "com.yandex.div:div", version.ref = "divkit"} divkit-core = { module = "com.yandex.div:div-core", version.ref = "divkit"} divkit-json = { module = "com.yandex.div:div-json", version.ref = "divkit"}