Skip to content

Commit

Permalink
Merge pull request #650 from arkivanov/pages-sample-2
Browse files Browse the repository at this point in the history
Added Pages sample tab
  • Loading branch information
arkivanov authored Feb 17, 2024
2 parents 8d56415 + 9649a2a commit 208b9ff
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 4 deletions.
3 changes: 2 additions & 1 deletion sample/app-desktop/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/build
/build
saved_state.dat
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.arkivanov.sample.shared.pages

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowForward
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.decompose.extensions.compose.jetbrains.pages.Pages
import com.arkivanov.decompose.extensions.compose.jetbrains.pages.PagesScrollAnimation
import com.arkivanov.sample.shared.customnavigation.KittenContent

@OptIn(ExperimentalFoundationApi::class, ExperimentalDecomposeApi::class)
@Composable
fun PagesContent(component: PagesComponent, modifier: Modifier = Modifier) {
Box(modifier = modifier) {
Pages(
pages = component.pages,
onPageSelected = component::selectPage,
modifier = Modifier.fillMaxSize(),
scrollAnimation = PagesScrollAnimation.Default,
) { _, page ->
KittenContent(
component = page,
textStyle = MaterialTheme.typography.h6,
modifier = Modifier.fillMaxSize(),
)
}

Row(
modifier = Modifier.align(Alignment.BottomCenter).padding(bottom = 16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
OutlinedButton(
onClick = component::selectPrev,
modifier = Modifier.size(48.dp),
contentPadding = PaddingValues(0.dp),
) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = "Previous",
)
}

OutlinedButton(
onClick = component::selectNext,
modifier = Modifier.size(48.dp),
contentPadding = PaddingValues(0.dp),
) {
Icon(
imageVector = Icons.Default.ArrowForward,
contentDescription = "Next",
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.List
import androidx.compose.material.icons.filled.LocationOn
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import com.arkivanov.decompose.extensions.compose.jetbrains.stack.Children
import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.Direction
import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.StackAnimation
Expand All @@ -40,12 +42,15 @@ import com.arkivanov.sample.shared.customnavigation.CustomNavigationComponent
import com.arkivanov.sample.shared.customnavigation.CustomNavigationContent
import com.arkivanov.sample.shared.dynamicfeatures.DynamicFeaturesContent
import com.arkivanov.sample.shared.multipane.MultiPaneContent
import com.arkivanov.sample.shared.pages.PagesComponent
import com.arkivanov.sample.shared.pages.PagesContent
import com.arkivanov.sample.shared.root.RootComponent.Child
import com.arkivanov.sample.shared.root.RootComponent.Child.CardsChild
import com.arkivanov.sample.shared.root.RootComponent.Child.CountersChild
import com.arkivanov.sample.shared.root.RootComponent.Child.CustomNavigationChild
import com.arkivanov.sample.shared.root.RootComponent.Child.DynamicFeaturesChild
import com.arkivanov.sample.shared.root.RootComponent.Child.MultiPaneChild
import com.arkivanov.sample.shared.root.RootComponent.Child.PagesChild

@Composable
fun RootContent(component: RootComponent, modifier: Modifier = Modifier) {
Expand Down Expand Up @@ -78,7 +83,8 @@ private fun Children(component: RootComponent, modifier: Modifier = Modifier) {
is CardsChild -> CardsContent(component = child.component, modifier = Modifier.fillMaxSize())
is MultiPaneChild -> MultiPaneContent(component = child.component, modifier = Modifier.fillMaxSize())
is DynamicFeaturesChild -> DynamicFeaturesContent(component = child.component, modifier = Modifier.fillMaxSize())
is CustomNavigationChild -> CustomNavigationContent(component = child.component, Modifier.fillMaxSize())
is CustomNavigationChild -> CustomNavigationContent(component = child.component, modifier = Modifier.fillMaxSize())
is PagesChild -> PagesContent(component = child.component, modifier = Modifier.fillMaxSize())
}
}
}
Expand Down Expand Up @@ -143,6 +149,18 @@ private fun BottomBar(component: RootComponent, modifier: Modifier = Modifier) {
)
},
)

BottomNavigationItem(
selected = activeComponent is PagesComponent,
onClick = component::onPagesTabClicked,
icon = {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = "Pages",
modifier = Modifier.rotate(90F),
)
},
)
}
}

Expand All @@ -163,6 +181,7 @@ private val Child.index: Int
is MultiPaneChild -> 2
is DynamicFeaturesChild -> 3
is CustomNavigationChild -> 4
is PagesChild -> 5
}

private fun StackAnimator.flipSide(): StackAnimator =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.arkivanov.sample.shared.root.RootComponent.Child.CountersChild
import com.arkivanov.sample.shared.root.RootComponent.Child.CustomNavigationChild
import com.arkivanov.sample.shared.root.RootComponent.Child.DynamicFeaturesChild
import com.arkivanov.sample.shared.root.RootComponent.Child.MultiPaneChild
import com.arkivanov.sample.shared.root.RootComponent.Child.PagesChild
import com.google.android.material.bottomnavigation.BottomNavigationView

@ExperimentalDecomposeApi
Expand All @@ -28,10 +29,12 @@ fun ViewContext.RootView(component: RootComponent): View {
val newView: View =
when (val child = newStack.active.instance) {
is CountersChild -> CountersView(child.component)

is CardsChild,
is MultiPaneChild,
is DynamicFeaturesChild,
is CustomNavigationChild -> NotImplementedView()
is CustomNavigationChild,
is PagesChild -> NotImplementedView()
}

if ((oldView != null) && (oldStack != null)) {
Expand Down Expand Up @@ -74,6 +77,7 @@ fun ViewContext.RootView(component: RootComponent): View {
is MultiPaneChild -> R.id.tab_multipane
is DynamicFeaturesChild -> R.id.tab_dynamic_features
is CustomNavigationChild -> R.id.tab_custom_navigation
is PagesChild -> error("Unsupported tab")
}

navigationView.setOnNavigationItemSelectedListener(listener)
Expand All @@ -90,4 +94,5 @@ private val RootComponent.Child.index: Int
is MultiPaneChild -> 2
is DynamicFeaturesChild -> 3
is CustomNavigationChild -> 4
is PagesChild -> 5
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.arkivanov.sample.shared.pages

import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.decompose.router.pages.ChildPages
import com.arkivanov.decompose.router.pages.Pages
import com.arkivanov.decompose.router.pages.PagesNavigation
import com.arkivanov.decompose.router.pages.childPages
import com.arkivanov.decompose.router.pages.select
import com.arkivanov.decompose.router.pages.selectNext
import com.arkivanov.decompose.router.pages.selectPrev
import com.arkivanov.decompose.value.Value
import com.arkivanov.sample.shared.customnavigation.DefaultKittenComponent
import com.arkivanov.sample.shared.customnavigation.KittenComponent
import com.arkivanov.sample.shared.customnavigation.KittenComponent.ImageType
import kotlinx.serialization.serializer

@OptIn(ExperimentalDecomposeApi::class)
class DefaultPagesComponent(
componentContext: ComponentContext,
) : PagesComponent, ComponentContext by componentContext {

private val nav = PagesNavigation<ImageType>()

override val pages: Value<ChildPages<*, KittenComponent>> =
childPages(
source = nav,
serializer = serializer<ImageType>(),
initialPages = { Pages(items = ImageType.entries, selectedIndex = 0) },
childFactory = { imageType, ctx -> DefaultKittenComponent(ctx, imageType) },
)

override fun selectPage(index: Int) {
nav.select(index = index)
}

override fun selectNext() {
nav.selectNext()
}

override fun selectPrev() {
nav.selectPrev()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.arkivanov.sample.shared.pages

import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.decompose.router.pages.ChildPages
import com.arkivanov.decompose.value.Value
import com.arkivanov.sample.shared.customnavigation.KittenComponent

interface PagesComponent {

@OptIn(ExperimentalDecomposeApi::class)
val pages: Value<ChildPages<*, KittenComponent>>

fun selectPage(index: Int)
fun selectNext()
fun selectPrev()
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import com.arkivanov.sample.shared.customnavigation.DefaultCustomNavigationCompo
import com.arkivanov.sample.shared.dynamicfeatures.DefaultDynamicFeaturesComponent
import com.arkivanov.sample.shared.dynamicfeatures.dynamicfeature.FeatureInstaller
import com.arkivanov.sample.shared.multipane.DefaultMultiPaneComponent
import com.arkivanov.sample.shared.pages.DefaultPagesComponent
import com.arkivanov.sample.shared.root.RootComponent.Child
import com.arkivanov.sample.shared.root.RootComponent.Child.CountersChild
import com.arkivanov.sample.shared.root.RootComponent.Child.CustomNavigationChild
import com.arkivanov.sample.shared.root.RootComponent.Child.DynamicFeaturesChild
import com.arkivanov.sample.shared.root.RootComponent.Child.MultiPaneChild
import com.arkivanov.sample.shared.root.RootComponent.Child.PagesChild
import kotlinx.serialization.Serializable

@OptIn(ExperimentalDecomposeApi::class)
Expand Down Expand Up @@ -57,6 +59,7 @@ class DefaultRootComponent(
is Config.MultiPane -> MultiPaneChild(DefaultMultiPaneComponent(componentContext))
is Config.DynamicFeatures -> DynamicFeaturesChild(DefaultDynamicFeaturesComponent(componentContext, featureInstaller))
is Config.CustomNavigation -> CustomNavigationChild(DefaultCustomNavigationComponent(componentContext))
is Config.Pages -> PagesChild(DefaultPagesComponent(componentContext))
}

override fun onCountersTabClicked() {
Expand All @@ -79,12 +82,17 @@ class DefaultRootComponent(
navigation.bringToFront(Config.CustomNavigation)
}

override fun onPagesTabClicked() {
navigation.bringToFront(Config.Pages)
}

private companion object {
private const val WEB_PATH_COUNTERS = "counters"
private const val WEB_PATH_CARDS = "cards"
private const val WEB_PATH_MULTI_PANE = "multi-pane"
private const val WEB_PATH_DYNAMIC_FEATURES = "dynamic-features"
private const val WEB_PATH_CUSTOM_NAVIGATION = "custom-navigation"
private const val WEB_PATH_PAGES = "pages"

private fun getInitialStack(webHistoryPaths: List<String>?, deepLink: DeepLink): List<Config> =
webHistoryPaths
Expand All @@ -105,6 +113,7 @@ class DefaultRootComponent(
Config.MultiPane -> "/$WEB_PATH_MULTI_PANE"
Config.DynamicFeatures -> "/$WEB_PATH_DYNAMIC_FEATURES"
Config.CustomNavigation -> "/$WEB_PATH_CUSTOM_NAVIGATION"
Config.Pages -> "/$WEB_PATH_PAGES"
}

private fun getConfigForPath(path: String): Config =
Expand All @@ -114,6 +123,7 @@ class DefaultRootComponent(
WEB_PATH_MULTI_PANE -> Config.MultiPane
WEB_PATH_DYNAMIC_FEATURES -> Config.DynamicFeatures
WEB_PATH_CUSTOM_NAVIGATION -> Config.CustomNavigation
WEB_PATH_PAGES -> Config.Pages
else -> Config.Counters
}
}
Expand All @@ -134,6 +144,9 @@ class DefaultRootComponent(

@Serializable
data object CustomNavigation : Config

@Serializable
data object Pages : Config
}

sealed interface DeepLink {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ class PreviewRootComponent : RootComponent {
override fun onMultiPaneTabClicked() {}
override fun onDynamicFeaturesTabClicked() {}
override fun onCustomNavigationTabClicked() {}
override fun onPagesTabClicked() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.arkivanov.sample.shared.counters.CountersComponent
import com.arkivanov.sample.shared.customnavigation.CustomNavigationComponent
import com.arkivanov.sample.shared.dynamicfeatures.DynamicFeaturesComponent
import com.arkivanov.sample.shared.multipane.MultiPaneComponent
import com.arkivanov.sample.shared.pages.PagesComponent

interface RootComponent {

Expand All @@ -17,12 +18,14 @@ interface RootComponent {
fun onMultiPaneTabClicked()
fun onDynamicFeaturesTabClicked()
fun onCustomNavigationTabClicked()
fun onPagesTabClicked()

sealed class Child {
class CountersChild(val component: CountersComponent) : Child()
class CardsChild(val component: CardsComponent) : Child()
class MultiPaneChild(val component: MultiPaneComponent) : Child()
class DynamicFeaturesChild(val component: DynamicFeaturesComponent) : Child()
class CustomNavigationChild(val component: CustomNavigationComponent) : Child()
class PagesChild(val component: PagesComponent) : Child()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.arkivanov.sample.shared.root.RootComponent.Child.CountersChild
import com.arkivanov.sample.shared.root.RootComponent.Child.CustomNavigationChild
import com.arkivanov.sample.shared.root.RootComponent.Child.DynamicFeaturesChild
import com.arkivanov.sample.shared.root.RootComponent.Child.MultiPaneChild
import com.arkivanov.sample.shared.root.RootComponent.Child.PagesChild
import com.arkivanov.sample.shared.useAsState
import mui.material.BottomNavigation
import mui.material.BottomNavigationAction
Expand Down Expand Up @@ -58,6 +59,7 @@ var RootContent: FC<RProps<RootComponent>> = FC { props ->
is MultiPaneChild -> componentContent(component = child.component, content = MultiPaneContent)
is DynamicFeaturesChild -> componentContent(component = child.component, content = DynamicFeaturesContent)
is CustomNavigationChild -> NotImplementedContent()
is PagesChild -> NotImplementedContent()
}.let {}
}

Expand All @@ -75,6 +77,7 @@ var RootContent: FC<RProps<RootComponent>> = FC { props ->
is MultiPaneChild -> TabItem.MULTI_PANE
is DynamicFeaturesChild -> TabItem.DYNAMIC_FEATURES
is CustomNavigationChild -> TabItem.CUSTOM_NAVIGATION
is PagesChild -> TabItem.PAGES
}

onChange = { _, newValue ->
Expand All @@ -84,6 +87,7 @@ var RootContent: FC<RProps<RootComponent>> = FC { props ->
TabItem.MULTI_PANE -> props.component.onMultiPaneTabClicked()
TabItem.DYNAMIC_FEATURES -> props.component.onDynamicFeaturesTabClicked()
TabItem.CUSTOM_NAVIGATION -> props.component.onCustomNavigationTabClicked()
TabItem.PAGES -> props.component.onPagesTabClicked()
}
}

Expand All @@ -96,7 +100,7 @@ var RootContent: FC<RProps<RootComponent>> = FC { props ->
BottomNavigationAction {
value = TabItem.CARDS
label = ReactNode("Cards")
icon = Icon.create { +"swipe_up" }
icon = Icon.create { +"note_stack" }
}

BottomNavigationAction {
Expand All @@ -116,6 +120,12 @@ var RootContent: FC<RProps<RootComponent>> = FC { props ->
label = ReactNode("Custom Navigation")
icon = Icon.create { +"location_on" }
}

BottomNavigationAction {
value = TabItem.CUSTOM_NAVIGATION
label = ReactNode("Pages")
icon = Icon.create { +"swipe" }
}
}
}
}
Expand All @@ -126,4 +136,5 @@ private enum class TabItem {
MULTI_PANE,
DYNAMIC_FEATURES,
CUSTOM_NAVIGATION,
PAGES,
}

0 comments on commit 208b9ff

Please sign in to comment.