Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Pages sample tab #650

Merged
merged 1 commit into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
}
Loading