@@ -3,20 +3,33 @@ package com.bumble.appyx.sandbox.client.spotlight
33import android.os.Parcelable
44import androidx.compose.foundation.layout.Arrangement
55import androidx.compose.foundation.layout.Box
6- import androidx.compose.foundation.layout.Column
76import androidx.compose.foundation.layout.Row
87import androidx.compose.foundation.layout.fillMaxSize
98import androidx.compose.foundation.layout.fillMaxWidth
109import androidx.compose.foundation.layout.padding
11- import androidx.compose.material3.Button
10+ import androidx.compose.material.icons.Icons
11+ import androidx.compose.material.icons.filled.ArrowBack
12+ import androidx.compose.material.icons.filled.ArrowForward
13+ import androidx.compose.material.icons.filled.Favorite
14+ import androidx.compose.material.icons.outlined.FavoriteBorder
1215import androidx.compose.material3.CircularProgressIndicator
16+ import androidx.compose.material3.ExperimentalMaterial3Api
17+ import androidx.compose.material3.FabPosition
18+ import androidx.compose.material3.FilledIconButton
19+ import androidx.compose.material3.Icon
20+ import androidx.compose.material3.IconButtonDefaults
21+ import androidx.compose.material3.NavigationBar
22+ import androidx.compose.material3.NavigationBarItem
23+ import androidx.compose.material3.Scaffold
1324import androidx.compose.material3.Text
1425import androidx.compose.runtime.Composable
26+ import androidx.compose.runtime.State
1527import androidx.compose.runtime.collectAsState
1628import androidx.compose.runtime.getValue
1729import androidx.compose.runtime.mutableStateOf
1830import androidx.compose.ui.Alignment
1931import androidx.compose.ui.Modifier
32+ import androidx.compose.ui.draw.shadow
2033import androidx.compose.ui.unit.dp
2134import androidx.lifecycle.coroutineScope
2235import com.bumble.appyx.core.composable.Children
@@ -25,27 +38,27 @@ import com.bumble.appyx.core.node.Node
2538import com.bumble.appyx.core.node.ParentNode
2639import com.bumble.appyx.navmodel.spotlight.Spotlight
2740import com.bumble.appyx.navmodel.spotlight.backpresshandler.GoToPrevious
41+ import com.bumble.appyx.navmodel.spotlight.current
2842import com.bumble.appyx.navmodel.spotlight.elementsCount
2943import com.bumble.appyx.navmodel.spotlight.hasNext
3044import com.bumble.appyx.navmodel.spotlight.hasPrevious
31- import com.bumble.appyx.navmodel.spotlight.operation.next
3245import com.bumble.appyx.navmodel.spotlight.operation.activate
46+ import com.bumble.appyx.navmodel.spotlight.operation.next
3347import com.bumble.appyx.navmodel.spotlight.operation.previous
3448import com.bumble.appyx.navmodel.spotlight.operation.updateElements
35- import com.bumble.appyx.navmodel.spotlight.transitionhandler.rememberSpotlightSlider
3649import com.bumble.appyx.sandbox.client.child.ChildNode
37- import com.bumble.appyx.sandbox.client.spotlight.SpotlightExampleNode.Item.C1
38- import com.bumble.appyx.sandbox.client.spotlight.SpotlightExampleNode.Item.C2
39- import com.bumble.appyx.sandbox.client.spotlight.SpotlightExampleNode.Item.C3
4050import com.bumble.appyx.sandbox.client.spotlight.SpotlightExampleNode.NavTarget.Child1
4151import com.bumble.appyx.sandbox.client.spotlight.SpotlightExampleNode.NavTarget.Child2
4252import com.bumble.appyx.sandbox.client.spotlight.SpotlightExampleNode.NavTarget.Child3
43- import com.bumble.appyx.sandbox.client.spotlight.SpotlightExampleNode.State .Loaded
44- import com.bumble.appyx.sandbox.client.spotlight.SpotlightExampleNode.State .Loading
53+ import com.bumble.appyx.sandbox.client.spotlight.SpotlightExampleNode.ScreenState .Loaded
54+ import com.bumble.appyx.sandbox.client.spotlight.SpotlightExampleNode.ScreenState .Loading
4555import kotlinx.coroutines.delay
4656import kotlinx.coroutines.launch
4757import kotlinx.parcelize.Parcelize
4858
59+ /* *
60+ * Shows how to use spotlight to create a UI with BottomTabBar
61+ */
4962class SpotlightExampleNode (
5063 buildContext : BuildContext ,
5164 private val spotlight : Spotlight <NavTarget > = Spotlight (
@@ -58,20 +71,20 @@ class SpotlightExampleNode(
5871 navModel = spotlight
5972) {
6073
61- private val screenState = mutableStateOf<State ?>(null )
74+ private val screenState = mutableStateOf<ScreenState ?>(null )
6275
63- sealed class State {
64- object Loading : State ()
65- object Loaded : State ()
76+ sealed class ScreenState {
77+ object Loading : ScreenState ()
78+ object Loaded : ScreenState ()
6679 }
6780
6881 init {
6982 // simulate loading tabs
7083 if (spotlight.elementsCount() == 0 ) {
7184 screenState.value = Loading
7285 lifecycle.coroutineScope.launch {
73- delay(2000 )
74- spotlight.updateElements(items = Item .getItemList ())
86+ delay(1000 )
87+ spotlight.updateElements(items = Tab .getTabList ())
7588 screenState.value = Loaded
7689 }
7790 } else {
@@ -92,13 +105,13 @@ class SpotlightExampleNode(
92105 }
93106
94107 @Parcelize
95- private enum class Item (val navTarget : NavTarget ) : Parcelable {
108+ private enum class Tab (val navTarget : NavTarget ) : Parcelable {
96109 C1 (Child1 ),
97110 C2 (Child2 ),
98111 C3 (Child3 );
99112
100113 companion object {
101- fun getItemList () = values().map { it.navTarget }
114+ fun getTabList () = values().map { it.navTarget }
102115 }
103116 }
104117
@@ -126,82 +139,95 @@ class SpotlightExampleNode(
126139 }
127140 }
128141
142+ @OptIn(ExperimentalMaterial3Api ::class )
129143 @Suppress(" LongMethod" )
130144 @Composable
131145 private fun LoadedState (modifier : Modifier = Modifier ) {
132146 val hasPrevious = spotlight.hasPrevious().collectAsState(initial = false )
133147 val hasNext = spotlight.hasNext().collectAsState(initial = false )
134- Column (
135- verticalArrangement = Arrangement .spacedBy(24 .dp),
136- horizontalAlignment = Alignment .CenterHorizontally ,
137- modifier = modifier,
138- ) {
139- Row (
140- modifier = Modifier
141- .fillMaxWidth()
142- .padding(24 .dp),
143- verticalAlignment = Alignment .CenterVertically ,
144- horizontalArrangement = Arrangement .SpaceBetween
145- ) {
146- TextButton (
147- text = " Previous" ,
148- enabled = hasPrevious.value
149- ) {
150- spotlight.previous()
151- }
152- TextButton (
153- text = " Next" ,
154- enabled = hasNext.value
155- ) {
156- spotlight.next()
157- }
148+ val currentTab = spotlight.current().collectAsState(initial = null )
149+ Scaffold (
150+ modifier = modifier.fillMaxSize(),
151+ floatingActionButtonPosition = FabPosition .Center ,
152+ floatingActionButton = { PageButtons (hasPrevious.value, hasNext.value) },
153+ bottomBar = {
154+ BottomTabs (currentTab)
158155 }
159- Row (
160- modifier = Modifier
161- .fillMaxWidth()
162- .padding(24 .dp),
163- verticalAlignment = Alignment .CenterVertically ,
164- horizontalArrangement = Arrangement .SpaceBetween
165- ) {
166- TextButton (
167- text = " C1" ,
168- enabled = true
169- ) {
170- spotlight.activate(C1 )
171- }
172- TextButton (
173- text = " C2" ,
174- enabled = true
175- ) {
176- spotlight.activate(C2 )
177- }
178- TextButton (
179- text = " C3" ,
180- enabled = true
181- ) {
182- spotlight.activate(C3 )
183- }
184- }
185-
156+ ) {
186157 Children (
187158 modifier = Modifier
188- .padding(top = 12 .dp, bottom = 12 .dp)
189- .fillMaxWidth(),
190- transitionHandler = rememberSpotlightSlider(clipToBounds = true ),
159+ .padding(it),
160+ transitionHandler = rememberSpotlightFaderThrough(),
191161 navModel = spotlight
192162 )
163+ }
164+ }
193165
166+ @Composable
167+ private fun BottomTabs (currentTab : State <NavTarget ?>) {
168+ NavigationBar {
169+ Tab .values().forEach { tab ->
170+ val selected = currentTab.value == tab.navTarget
171+ NavigationBarItem (
172+ icon = {
173+ Icon (
174+ if (selected)
175+ Icons .Filled .Favorite
176+ else
177+ Icons .Outlined .FavoriteBorder ,
178+ contentDescription = tab.toString()
179+ )
180+ },
181+ label = { Text (tab.toString()) },
182+ selected = selected,
183+ onClick = { spotlight.activate(tab) }
184+ )
185+ }
194186 }
195187 }
196188
197189 @Composable
198- private fun TextButton (text : String , enabled : Boolean = true, onClick : () -> Unit ) {
199- Button (onClick = onClick, enabled = enabled, modifier = Modifier .padding(4 .dp)) {
200- Text (text = text)
190+ private fun PageButtons (
191+ hasPrevious : Boolean ,
192+ hasNext : Boolean
193+ ) {
194+ Row (
195+ modifier = Modifier
196+ .fillMaxWidth()
197+ .padding(24 .dp),
198+ verticalAlignment = Alignment .CenterVertically ,
199+ horizontalArrangement = Arrangement .SpaceBetween
200+ ) {
201+ FilledIconButton (
202+ onClick = { spotlight.previous() },
203+ modifier = if (hasPrevious) Modifier .shadow(
204+ 4 .dp,
205+ IconButtonDefaults .filledShape
206+ ) else Modifier ,
207+ enabled = hasPrevious,
208+ ) {
209+ Icon (
210+ Icons .Filled .ArrowBack ,
211+ contentDescription = " Previous"
212+ )
213+ }
214+ FilledIconButton (
215+ onClick = { spotlight.next() },
216+ modifier = if (hasNext) Modifier .shadow(
217+ 4 .dp,
218+ IconButtonDefaults .filledShape
219+ ) else Modifier ,
220+ enabled = hasNext,
221+ ) {
222+ Icon (
223+ Icons .Filled .ArrowForward ,
224+ contentDescription = " Next"
225+ )
226+ }
201227 }
202228 }
203229
204- private fun Spotlight <* >.activate (item : Item ) {
230+ private fun Spotlight <* >.activate (item : Tab ) {
205231 activate(item.ordinal)
206232 }
207233}
0 commit comments