@@ -7,17 +7,27 @@ import androidx.compose.animation.fadeIn
77import androidx.compose.animation.fadeOut
88import androidx.compose.animation.slideInVertically
99import androidx.compose.animation.slideOutVertically
10+ import androidx.compose.foundation.combinedClickable
11+ import androidx.compose.foundation.gestures.detectTapGestures
12+ import androidx.compose.foundation.layout.Box
1013import androidx.compose.foundation.layout.Column
14+ import androidx.compose.foundation.layout.Row
15+ import androidx.compose.foundation.layout.fillMaxWidth
1116import androidx.compose.foundation.layout.height
1217import androidx.compose.foundation.layout.padding
1318import androidx.compose.foundation.layout.width
1419import androidx.compose.foundation.shape.CircleShape
20+ import androidx.compose.material3.Card
21+ import androidx.compose.material3.ExperimentalMaterial3Api
1522import androidx.compose.material3.FabPosition
1623import androidx.compose.material3.FloatingActionButton
1724import androidx.compose.material3.Icon
25+ import androidx.compose.material3.ModalBottomSheet
1826import androidx.compose.material3.NavigationBar
1927import androidx.compose.material3.NavigationBarItem
2028import androidx.compose.material3.Scaffold
29+ import androidx.compose.material3.SnackbarHost
30+ import androidx.compose.material3.SnackbarHostState
2131import androidx.compose.material3.Text
2232import androidx.compose.runtime.Composable
2333import androidx.compose.runtime.CompositionLocalProvider
@@ -28,6 +38,7 @@ import androidx.compose.runtime.remember
2838import androidx.compose.runtime.setValue
2939import androidx.compose.ui.Modifier
3040import androidx.compose.ui.draw.clip
41+ import androidx.compose.ui.input.pointer.pointerInput
3142import androidx.compose.ui.tooling.preview.Preview
3243import androidx.compose.ui.unit.dp
3344import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -49,6 +60,7 @@ import site.remlit.snowdrop.component.AppTheme
4960import site.remlit.snowdrop.model.ui.Destination
5061import site.remlit.snowdrop.util.ExternalUriHandler
5162import site.remlit.snowdrop.util.LocalNavController
63+ import site.remlit.snowdrop.util.SnackbarController
5264import site.remlit.snowdrop.util.atRoute
5365import site.remlit.snowdrop.util.blockingSettings
5466import site.remlit.snowdrop.util.getCurrentAccountObjectFlow
@@ -58,6 +70,7 @@ import site.remlit.snowdrop.util.scrollingUpward
5870import site.remlit.snowdrop.util.settings
5971import site.remlit.snowdrop.util.setupAppSettings
6072import site.remlit.snowdrop.util.cache.setupCache
73+ import site.remlit.snowdrop.util.getAccounts
6174import site.remlit.snowdrop.util.safeReturnable
6275import site.remlit.snowdrop.view.*
6376import site.remlit.snowdrop.view.settings.*
@@ -101,7 +114,7 @@ data class SettingsDebugStorageRoute(val storage: Int) : Destination(101)
101114
102115@Composable
103116@Preview
104- @OptIn(ExperimentalSettingsApi ::class )
117+ @OptIn(ExperimentalSettingsApi ::class , ExperimentalMaterial3Api :: class )
105118fun App () = safe {
106119 setupAppSettings()
107120 setupCache()
@@ -115,11 +128,14 @@ fun App() = safe {
115128 val navBackStackEntry by navController.currentBackStackEntryAsState()
116129 val currentDest = navBackStackEntry?.destination
117130
131+ val snackbarHostState = remember { SnackbarHostState () }
132+
118133
119134 val loggedIn by settings.getBooleanOrNullFlow(" logged_in" )
120135 .collectAsStateWithLifecycle(null )
121136 val account by getCurrentAccountObjectFlow()
122137 .collectAsStateWithLifecycle(null )
138+ var showAccountSwitcher by remember { mutableStateOf(false ) }
123139
124140
125141 DisposableEffect (Unit ) {
@@ -165,142 +181,161 @@ fun App() = safe {
165181 AppTheme {
166182
167183 CompositionLocalProvider (LocalNavController provides navController) {
168- CompositionLocalProvider (LocalKamelConfig provides kamelConfig) {
169-
170- Scaffold (
171- bottomBar = {
172- if (loggedIn == true && ! shouldHideBottomBar()) {
173- NavigationBar {
174- NavigationBarItem (
175- selected = atRoute<TimelineRoute >(currentDest),
176- onClick = {
177- if (! atRoute<TimelineRoute >(currentDest))
178- navController.navigate(TimelineRoute )
179- },
180- icon = {
181- if (atRoute<TimelineRoute >(currentDest)) Icon (
182- painterResource(Res .drawable.icon_home_filled_24px),
183- null
184- )
185- else Icon (painterResource(Res .drawable.icon_home_24px), null )
186- },
187- label = { Text (" Timeline" ) }
188- )
189-
190- NavigationBarItem (
191- selected = atRoute<NotificationsRoute >(currentDest),
192- onClick = { navController.navigate(NotificationsRoute ) },
193- icon = {
194- if (atRoute<NotificationsRoute >(currentDest)) Icon (
195- painterResource(Res .drawable.icon_notifications_filled_24px),
196- null
197- )
198- else Icon (painterResource(Res .drawable.icon_notifications_24px), null )
199- },
200- label = { Text (" Notifications" ) }
201- )
202-
203- NavigationBarItem (
204- selected = atRoute<ExploreRoute >(currentDest),
205- onClick = { navController.navigate(ExploreRoute ) },
206- icon = {
207- if (atRoute<ExploreRoute >(currentDest)) Icon (
208- painterResource(Res .drawable.icon_explore_filled_24px),
209- null
210- )
211- else Icon (painterResource(Res .drawable.icon_explore_24px), null )
212- },
213- label = { Text (" Explore" ) }
214- )
215-
216- NavigationBarItem (
217- selected = atRoute<MyProfileRoute >(currentDest),
218- onClick = { navController.navigate(MyProfileRoute ) },
219- icon = {
220- if (account != null && account!! .avatarStatic != null ) {
221- KamelImage (
222- { asyncPainterResource(account!! .avatarStatic!! ) },
223- " Profile" ,
224- onLoading = { fallbackAvatarIcon() },
225- modifier = Modifier .clip(CircleShape )
226- .height(24 .dp)
227- .width(24 .dp)
184+ CompositionLocalProvider (SnackbarController provides snackbarHostState) {
185+ CompositionLocalProvider (LocalKamelConfig provides kamelConfig) {
186+
187+ Scaffold (
188+ bottomBar = {
189+ if (loggedIn == true && ! shouldHideBottomBar()) {
190+ NavigationBar {
191+ NavigationBarItem (
192+ selected = atRoute<TimelineRoute >(currentDest),
193+ onClick = {
194+ if (! atRoute<TimelineRoute >(currentDest))
195+ navController.navigate(TimelineRoute )
196+ },
197+ icon = {
198+ if (atRoute<TimelineRoute >(currentDest)) Icon (
199+ painterResource(Res .drawable.icon_home_filled_24px),
200+ null
201+ )
202+ else Icon (painterResource(Res .drawable.icon_home_24px), null )
203+ },
204+ label = { Text (" Timeline" ) }
205+ )
206+
207+ NavigationBarItem (
208+ selected = atRoute<NotificationsRoute >(currentDest),
209+ onClick = { navController.navigate(NotificationsRoute ) },
210+ icon = {
211+ if (atRoute<NotificationsRoute >(currentDest)) Icon (
212+ painterResource(Res .drawable.icon_notifications_filled_24px),
213+ null
214+ )
215+ else Icon (painterResource(Res .drawable.icon_notifications_24px), null )
216+ },
217+ label = { Text (" Notifications" ) }
218+ )
219+
220+ NavigationBarItem (
221+ selected = atRoute<ExploreRoute >(currentDest),
222+ onClick = { navController.navigate(ExploreRoute ) },
223+ icon = {
224+ if (atRoute<ExploreRoute >(currentDest)) Icon (
225+ painterResource(Res .drawable.icon_explore_filled_24px),
226+ null
228227 )
229- } else fallbackAvatarIcon()
230- },
231- label = { Text (" Profile" ) }
232- )
228+ else Icon (painterResource(Res .drawable.icon_explore_24px), null )
229+ },
230+ label = { Text (" Explore" ) }
231+ )
232+
233+ NavigationBarItem (
234+ selected = atRoute<MyProfileRoute >(currentDest),
235+ onClick = { navController.navigate(MyProfileRoute ) },
236+ icon = {
237+ if (account != null && account!! .avatarStatic != null ) {
238+ KamelImage (
239+ { asyncPainterResource(account!! .avatarStatic!! ) },
240+ " Profile" ,
241+ onLoading = { fallbackAvatarIcon() },
242+ modifier = Modifier .clip(CircleShape )
243+ .height(24 .dp)
244+ .width(24 .dp)
245+ )
246+ } else fallbackAvatarIcon()
247+ },
248+ label = { Text (" Profile" ) }
249+ )
250+ }
233251 }
234- }
235- },
236- floatingActionButton = {
237- AnimatedVisibility (
238- visible = shouldShowComposeFab(),
239- enter = fadeIn() + slideInVertically(initialOffsetY = { it }),
240- exit = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
241- ) {
242- FloatingActionButton (
243- onClick = { navController.navigate(ComposeRoute ) }
252+ },
253+ floatingActionButton = {
254+ AnimatedVisibility (
255+ visible = shouldShowComposeFab(),
256+ enter = fadeIn() + slideInVertically(initialOffsetY = { it }),
257+ exit = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
244258 ) {
245- if (atRoute<ProfileRoute >(currentDest)) Icon (painterResource(Res .drawable.icon_alternate_email_24px), null )
246- else Icon (painterResource(Res .drawable.icon_edit_square_24px), null )
259+ FloatingActionButton (
260+ onClick = { navController.navigate(ComposeRoute ) }
261+ ) {
262+ if (atRoute<ProfileRoute >(currentDest)) Icon (painterResource(Res .drawable.icon_alternate_email_24px), null )
263+ else Icon (painterResource(Res .drawable.icon_edit_square_24px), null )
264+ }
247265 }
266+ },
267+ floatingActionButtonPosition = FabPosition .End ,
268+ snackbarHost = {
269+ SnackbarHost (hostState = snackbarHostState)
248270 }
249- },
250- floatingActionButtonPosition = FabPosition .End
251- ) { bottomPadding ->
252- Column (
253- modifier = Modifier .padding(bottom = bottomPadding.calculateBottomPadding())
254- ) {
255- NavHost (
256- navController = navController,
257- startDestination = StartRoute ,
258- enterTransition = { EnterTransition .None },
259- exitTransition = { ExitTransition .None },
260- popEnterTransition = { EnterTransition .None },
261- popExitTransition = { ExitTransition .None }
271+ ) { bottomPadding ->
272+ Column (
273+ modifier = Modifier .padding(bottom = bottomPadding.calculateBottomPadding())
262274 ) {
263- composable<StartRoute > {
264- StartView (
265- navigateToLogin = { navController.navigate(LoginRoute ) },
266- navigateToTimeline = { navController.navigate(TimelineRoute ) },
267- )
268- }
269-
270- composable<LoginRoute > {
271- LoginView (
272- navigateToTimeline = { navController.navigate(TimelineRoute ) },
273- )
274- }
275- composable<TimelineRoute > { TimelineView () }
276- composable<NotificationsRoute > { NotificationsView () }
277- composable<ExploreRoute > { ExploreView () }
278- composable<MyProfileRoute > {
279- if (account != null ) ProfileView (account!! .id)
280- else Text (" Error" )
281- }
282-
283- composable<ThreadRoute > {
284- val args = it.toRoute<ThreadRoute >()
285- ThreadView (args.id)
286- }
287- composable<ProfileRoute > {
288- val args = it.toRoute<ProfileRoute >()
289- ProfileView (args.id)
290- }
291-
292- composable<ComposeRoute > { ComposeView () }
293-
294- // Settings
295- composable<SettingsRoute > { SettingsView () }
296- composable<SettingsDebugStorageRoute > {
297- val args = it.toRoute<SettingsDebugStorageRoute >()
298- SettingsDebugStorageView (args.storage)
275+ if (showAccountSwitcher)
276+ ModalBottomSheet (
277+ onDismissRequest = { showAccountSwitcher = false }
278+ ) {
279+ getAccounts().forEach { it ->
280+ Card (
281+ modifier = Modifier .padding(start = 10 .dp, end = 10 .dp, top = 10 .dp)
282+ .fillMaxWidth()
283+ ) {
284+ Text (it)
285+ }
286+ }
287+ }
288+
289+ NavHost (
290+ navController = navController,
291+ startDestination = StartRoute ,
292+ enterTransition = { EnterTransition .None },
293+ exitTransition = { ExitTransition .None },
294+ popEnterTransition = { EnterTransition .None },
295+ popExitTransition = { ExitTransition .None }
296+ ) {
297+ composable<StartRoute > {
298+ StartView (
299+ navigateToLogin = { navController.navigate(LoginRoute ) },
300+ navigateToTimeline = { navController.navigate(TimelineRoute ) },
301+ )
302+ }
303+
304+ composable<LoginRoute > {
305+ LoginView (
306+ navigateToTimeline = { navController.navigate(TimelineRoute ) },
307+ )
308+ }
309+ composable<TimelineRoute > { TimelineView () }
310+ composable<NotificationsRoute > { NotificationsView () }
311+ composable<ExploreRoute > { ExploreView () }
312+ composable<MyProfileRoute > {
313+ if (account != null ) ProfileView (account!! .id)
314+ else Text (" Error" )
315+ }
316+
317+ composable<ThreadRoute > {
318+ val args = it.toRoute<ThreadRoute >()
319+ ThreadView (args.id)
320+ }
321+ composable<ProfileRoute > {
322+ val args = it.toRoute<ProfileRoute >()
323+ ProfileView (args.id)
324+ }
325+
326+ composable<ComposeRoute > { ComposeView () }
327+
328+ // Settings
329+ composable<SettingsRoute > { SettingsView () }
330+ composable<SettingsDebugStorageRoute > {
331+ val args = it.toRoute<SettingsDebugStorageRoute >()
332+ SettingsDebugStorageView (args.storage)
333+ }
299334 }
300335 }
301336 }
302- }
303337
338+ }
304339 }
305340 }
306341
0 commit comments