Skip to content

Commit fc201f5

Browse files
committed
Add snackbar and garbage
1 parent e7ca3b1 commit fc201f5

4 files changed

Lines changed: 172 additions & 131 deletions

File tree

shared/src/commonMain/kotlin/site/remlit/snowdrop/App.kt

Lines changed: 162 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,27 @@ import androidx.compose.animation.fadeIn
77
import androidx.compose.animation.fadeOut
88
import androidx.compose.animation.slideInVertically
99
import androidx.compose.animation.slideOutVertically
10+
import androidx.compose.foundation.combinedClickable
11+
import androidx.compose.foundation.gestures.detectTapGestures
12+
import androidx.compose.foundation.layout.Box
1013
import androidx.compose.foundation.layout.Column
14+
import androidx.compose.foundation.layout.Row
15+
import androidx.compose.foundation.layout.fillMaxWidth
1116
import androidx.compose.foundation.layout.height
1217
import androidx.compose.foundation.layout.padding
1318
import androidx.compose.foundation.layout.width
1419
import androidx.compose.foundation.shape.CircleShape
20+
import androidx.compose.material3.Card
21+
import androidx.compose.material3.ExperimentalMaterial3Api
1522
import androidx.compose.material3.FabPosition
1623
import androidx.compose.material3.FloatingActionButton
1724
import androidx.compose.material3.Icon
25+
import androidx.compose.material3.ModalBottomSheet
1826
import androidx.compose.material3.NavigationBar
1927
import androidx.compose.material3.NavigationBarItem
2028
import androidx.compose.material3.Scaffold
29+
import androidx.compose.material3.SnackbarHost
30+
import androidx.compose.material3.SnackbarHostState
2131
import androidx.compose.material3.Text
2232
import androidx.compose.runtime.Composable
2333
import androidx.compose.runtime.CompositionLocalProvider
@@ -28,6 +38,7 @@ import androidx.compose.runtime.remember
2838
import androidx.compose.runtime.setValue
2939
import androidx.compose.ui.Modifier
3040
import androidx.compose.ui.draw.clip
41+
import androidx.compose.ui.input.pointer.pointerInput
3142
import androidx.compose.ui.tooling.preview.Preview
3243
import androidx.compose.ui.unit.dp
3344
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -49,6 +60,7 @@ import site.remlit.snowdrop.component.AppTheme
4960
import site.remlit.snowdrop.model.ui.Destination
5061
import site.remlit.snowdrop.util.ExternalUriHandler
5162
import site.remlit.snowdrop.util.LocalNavController
63+
import site.remlit.snowdrop.util.SnackbarController
5264
import site.remlit.snowdrop.util.atRoute
5365
import site.remlit.snowdrop.util.blockingSettings
5466
import site.remlit.snowdrop.util.getCurrentAccountObjectFlow
@@ -58,6 +70,7 @@ import site.remlit.snowdrop.util.scrollingUpward
5870
import site.remlit.snowdrop.util.settings
5971
import site.remlit.snowdrop.util.setupAppSettings
6072
import site.remlit.snowdrop.util.cache.setupCache
73+
import site.remlit.snowdrop.util.getAccounts
6174
import site.remlit.snowdrop.util.safeReturnable
6275
import site.remlit.snowdrop.view.*
6376
import 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)
105118
fun 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

shared/src/commonMain/kotlin/site/remlit/snowdrop/component/Status.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import androidx.compose.runtime.remember
3030
import androidx.compose.runtime.setValue
3131
import androidx.compose.ui.Alignment
3232
import androidx.compose.ui.Modifier
33-
import androidx.compose.ui.draw.blur
3433
import androidx.compose.ui.draw.clip
3534
import androidx.compose.ui.graphics.Color
3635
import androidx.compose.ui.platform.LocalClipboardManager
@@ -49,7 +48,6 @@ import site.remlit.snowdrop.ThreadRoute
4948
import site.remlit.snowdrop.component.dropdown.DangerDropdownItem
5049
import site.remlit.snowdrop.model.Status
5150
import site.remlit.snowdrop.model.Account
52-
import site.remlit.snowdrop.model.Emoji
5351
import site.remlit.snowdrop.util.BoostColor
5452
import site.remlit.snowdrop.util.LikeColor
5553
import site.remlit.snowdrop.util.LocalNavController

shared/src/commonMain/kotlin/site/remlit/snowdrop/util/Settings.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ expect val settings: FlowSettings
2020
@OptIn(ExperimentalSettingsApi::class)
2121
val blockingSettings = settings.toBlockingSettings()
2222

23-
fun getAccounts() = blockingSettings.getString("accounts", "")
23+
fun getAccounts() = blockingSettings.getString("accounts", "").split(" ")
2424
fun getCurrentAccountId() = blockingSettings.getString("current_account", "")
2525
fun getCurrentAccountHost() = blockingSettings.getString("account_${getCurrentAccountId()}_host", "")
2626

@@ -34,7 +34,7 @@ fun logoutAccount(accountId: String) {
3434

3535
blockingSettings.putString(
3636
"accounts",
37-
getAccounts().split(" ").filter { it != accountId }
37+
getAccounts().filter { it != accountId }
3838
.joinToString(" ")
3939
)
4040
}

0 commit comments

Comments
 (0)