Merge Voyager screens (#8656)

* Merge Voyager screens

* cleanups
This commit is contained in:
Ivan Iskandar
2022-12-03 10:35:30 +07:00
committed by GitHub
parent 5313a5d5d2
commit 3d66eaea83
67 changed files with 1180 additions and 1991 deletions

View File

@@ -2,6 +2,7 @@ package eu.kanade.presentation.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@@ -16,10 +17,10 @@ import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.shape.ZeroCornerSize
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.BookmarkAdd
@@ -95,7 +96,11 @@ fun MangaBottomActionMenu(
}
Row(
modifier = Modifier
.padding(WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues())
.padding(
WindowInsets.navigationBars
.only(WindowInsetsSides.Bottom)
.asPaddingValues(),
)
.padding(horizontal = 8.dp, vertical = 12.dp),
) {
if (onBookmarkClicked != null) {
@@ -213,16 +218,16 @@ private fun RowScope.Button(
fun LibraryBottomActionMenu(
visible: Boolean,
modifier: Modifier = Modifier,
onChangeCategoryClicked: (() -> Unit)?,
onMarkAsReadClicked: (() -> Unit)?,
onMarkAsUnreadClicked: (() -> Unit)?,
onChangeCategoryClicked: () -> Unit,
onMarkAsReadClicked: () -> Unit,
onMarkAsUnreadClicked: () -> Unit,
onDownloadClicked: ((DownloadAction) -> Unit)?,
onDeleteClicked: (() -> Unit)?,
onDeleteClicked: () -> Unit,
) {
AnimatedVisibility(
visible = visible,
enter = expandVertically(expandFrom = Alignment.Bottom),
exit = shrinkVertically(shrinkTowards = Alignment.Bottom),
enter = expandVertically(animationSpec = tween(delayMillis = 300)),
exit = shrinkVertically(animationSpec = tween()),
) {
val scope = rememberCoroutineScope()
Surface(
@@ -244,36 +249,33 @@ fun LibraryBottomActionMenu(
}
Row(
modifier = Modifier
.navigationBarsPadding()
.windowInsetsPadding(
WindowInsets.navigationBars
.only(WindowInsetsSides.Bottom),
)
.padding(horizontal = 8.dp, vertical = 12.dp),
) {
if (onChangeCategoryClicked != null) {
Button(
title = stringResource(R.string.action_move_category),
icon = Icons.Outlined.Label,
toConfirm = confirm[0],
onLongClick = { onLongClickItem(0) },
onClick = onChangeCategoryClicked,
)
}
if (onMarkAsReadClicked != null) {
Button(
title = stringResource(R.string.action_mark_as_read),
icon = Icons.Outlined.DoneAll,
toConfirm = confirm[1],
onLongClick = { onLongClickItem(1) },
onClick = onMarkAsReadClicked,
)
}
if (onMarkAsUnreadClicked != null) {
Button(
title = stringResource(R.string.action_mark_as_unread),
icon = Icons.Outlined.RemoveDone,
toConfirm = confirm[2],
onLongClick = { onLongClickItem(2) },
onClick = onMarkAsUnreadClicked,
)
}
Button(
title = stringResource(R.string.action_move_category),
icon = Icons.Outlined.Label,
toConfirm = confirm[0],
onLongClick = { onLongClickItem(0) },
onClick = onChangeCategoryClicked,
)
Button(
title = stringResource(R.string.action_mark_as_read),
icon = Icons.Outlined.DoneAll,
toConfirm = confirm[1],
onLongClick = { onLongClickItem(1) },
onClick = onMarkAsReadClicked,
)
Button(
title = stringResource(R.string.action_mark_as_unread),
icon = Icons.Outlined.RemoveDone,
toConfirm = confirm[2],
onLongClick = { onLongClickItem(2) },
onClick = onMarkAsUnreadClicked,
)
if (onDownloadClicked != null) {
var downloadExpanded by remember { mutableStateOf(false) }
Button(
@@ -292,15 +294,13 @@ fun LibraryBottomActionMenu(
)
}
}
if (onDeleteClicked != null) {
Button(
title = stringResource(R.string.action_delete),
icon = Icons.Outlined.Delete,
toConfirm = confirm[4],
onLongClick = { onLongClickItem(4) },
onClick = onDeleteClicked,
)
}
Button(
title = stringResource(R.string.action_delete),
icon = Icons.Outlined.Delete,
toConfirm = confirm[4],
onLongClick = { onLongClickItem(4) },
onClick = onDeleteClicked,
)
}
}
}

View File

@@ -0,0 +1,48 @@
package eu.kanade.presentation.components
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBarDefaults
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/**
* M3 Navbar with no horizontal spacer
*
* @see [androidx.compose.material3.NavigationBar]
*/
@Composable
fun NavigationBar(
modifier: Modifier = Modifier,
containerColor: Color = NavigationBarDefaults.containerColor,
contentColor: Color = MaterialTheme.colorScheme.contentColorFor(containerColor),
tonalElevation: Dp = NavigationBarDefaults.Elevation,
windowInsets: WindowInsets = NavigationBarDefaults.windowInsets,
content: @Composable RowScope.() -> Unit,
) {
androidx.compose.material3.Surface(
color = containerColor,
contentColor = contentColor,
tonalElevation = tonalElevation,
modifier = modifier,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.windowInsetsPadding(windowInsets)
.height(80.dp)
.selectableGroup(),
content = content,
)
}
}

View File

@@ -0,0 +1,59 @@
package eu.kanade.presentation.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.material3.NavigationRailDefaults
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
/**
* Center-aligned M3 Navigation rail
*
* @see [androidx.compose.material3.NavigationRail]
*/
@Composable
fun NavigationRail(
modifier: Modifier = Modifier,
containerColor: Color = NavigationRailDefaults.ContainerColor,
contentColor: Color = contentColorFor(containerColor),
header: @Composable (ColumnScope.() -> Unit)? = null,
windowInsets: WindowInsets = NavigationRailDefaults.windowInsets,
content: @Composable ColumnScope.() -> Unit,
) {
androidx.compose.material3.Surface(
color = containerColor,
contentColor = contentColor,
modifier = modifier,
tonalElevation = 3.dp,
) {
Column(
Modifier
.fillMaxHeight()
.windowInsetsPadding(windowInsets)
.widthIn(min = 80.dp)
.padding(vertical = 4.dp)
.selectableGroup(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.CenterVertically),
) {
if (header != null) {
header()
Spacer(Modifier.height(8.dp))
}
content()
}
}
}

View File

@@ -16,11 +16,14 @@
package eu.kanade.presentation.components
import androidx.compose.foundation.layout.MutableWindowInsets
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.exclude
import androidx.compose.foundation.layout.withConsumedWindowInsets
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ScaffoldDefaults
@@ -31,6 +34,7 @@ import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -67,6 +71,7 @@ import kotlin.math.max
* * Remove height constraint for expanded app bar
* * Also take account of fab height when providing inner padding
* * Fixes for fab and snackbar horizontal placements when [contentWindowInsets] is used
* * Handle consumed window insets
*
* @param modifier the [Modifier] to be applied to this scaffold
* @param topBar top app bar of the screen, typically a [SmallTopAppBar]
@@ -103,9 +108,12 @@ fun Scaffold(
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
content: @Composable (PaddingValues) -> Unit,
) {
// Tachiyomi: Handle consumed window insets
val remainingWindowInsets = remember { MutableWindowInsets() }
androidx.compose.material3.Surface(
modifier = Modifier
.nestedScroll(topBarScrollBehavior.nestedScrollConnection)
.withConsumedWindowInsets { remainingWindowInsets.insets = contentWindowInsets.exclude(it) }
.then(modifier),
color = containerColor,
contentColor = contentColor,
@@ -116,7 +124,7 @@ fun Scaffold(
bottomBar = bottomBar,
content = content,
snackbar = snackbarHost,
contentWindowInsets = contentWindowInsets,
contentWindowInsets = remainingWindowInsets,
fab = floatingActionButton,
)
}

View File

@@ -20,7 +20,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
import kotlinx.coroutines.launch
@Composable
@@ -88,9 +87,7 @@ fun TabbedScreen(
verticalAlignment = Alignment.Top,
) { page ->
tabs[page].content(
TachiyomiBottomNavigationView.withBottomNavPadding(
PaddingValues(bottom = contentPadding.calculateBottomPadding()),
),
PaddingValues(bottom = contentPadding.calculateBottomPadding()),
snackbarHostState,
)
}

View File

@@ -5,7 +5,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.DeleteSweep
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ScaffoldDefaults
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
@@ -21,7 +20,6 @@ import eu.kanade.presentation.history.components.HistoryContent
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
import eu.kanade.tachiyomi.ui.history.HistoryState
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
import java.util.Date
@Composable
@@ -55,7 +53,6 @@ fun HistoryScreen(
)
},
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
contentWindowInsets = TachiyomiBottomNavigationView.withBottomNavInset(ScaffoldDefaults.contentWindowInsets),
) { contentPadding ->
state.list.let {
if (it == null) {

View File

@@ -1,10 +1,7 @@
package eu.kanade.presentation.more
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.CloudOff
import androidx.compose.material.icons.outlined.GetApp
@@ -29,8 +26,7 @@ import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.more.DownloadQueueState
import eu.kanade.tachiyomi.ui.more.MoreController
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
import eu.kanade.tachiyomi.util.Constants
@Composable
fun MoreScreen(
@@ -50,10 +46,7 @@ fun MoreScreen(
val uriHandler = LocalUriHandler.current
ScrollbarLazyColumn(
modifier = Modifier.statusBarsPadding(),
contentPadding = TachiyomiBottomNavigationView.withBottomNavPadding(
WindowInsets.navigationBars.asPaddingValues(),
),
modifier = Modifier.systemBarsPadding(),
) {
if (isFDroid) {
item {
@@ -169,7 +162,7 @@ fun MoreScreen(
TextPreferenceWidget(
title = stringResource(R.string.label_help),
icon = Icons.Outlined.HelpOutline,
onPreferenceClick = { uriHandler.openUri(MoreController.URL_HELP) },
onPreferenceClick = { uriHandler.openUri(Constants.URL_HELP) },
)
}
}

View File

@@ -0,0 +1,144 @@
package eu.kanade.presentation.more
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.OpenInNew
import androidx.compose.material.icons.outlined.NewReleases
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBarDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import com.halilibo.richtext.markdown.Markdown
import com.halilibo.richtext.ui.RichTextStyle
import com.halilibo.richtext.ui.material3.Material3RichText
import com.halilibo.richtext.ui.string.RichTextStringStyle
import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.util.padding
import eu.kanade.presentation.util.secondaryItemAlpha
import eu.kanade.tachiyomi.R
@Composable
fun NewUpdateScreen(
versionName: String,
changelogInfo: String,
onOpenInBrowser: () -> Unit,
onRejectUpdate: () -> Unit,
onAcceptUpdate: () -> Unit,
) {
Scaffold(
bottomBar = {
val strokeWidth = Dp.Hairline
val borderColor = MaterialTheme.colorScheme.outline
Column(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.drawBehind {
drawLine(
borderColor,
Offset(0f, 0f),
Offset(size.width, 0f),
strokeWidth.value,
)
}
.windowInsetsPadding(NavigationBarDefaults.windowInsets)
.padding(
horizontal = MaterialTheme.padding.medium,
vertical = MaterialTheme.padding.small,
),
) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = onAcceptUpdate,
) {
Text(text = stringResource(id = R.string.update_check_confirm))
}
TextButton(
modifier = Modifier.fillMaxWidth(),
onClick = onRejectUpdate,
) {
Text(text = stringResource(R.string.action_not_now))
}
}
},
) { paddingValues ->
// Status bar scrim
Box(
modifier = Modifier
.zIndex(2f)
.secondaryItemAlpha()
.background(MaterialTheme.colorScheme.background)
.fillMaxWidth()
.height(paddingValues.calculateTopPadding()),
)
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(paddingValues)
.padding(top = 48.dp)
.padding(horizontal = MaterialTheme.padding.medium),
) {
Icon(
imageVector = Icons.Outlined.NewReleases,
contentDescription = null,
modifier = Modifier
.padding(bottom = MaterialTheme.padding.small)
.size(48.dp),
tint = MaterialTheme.colorScheme.primary,
)
Text(
text = stringResource(R.string.update_check_notification_update_available),
style = MaterialTheme.typography.headlineLarge,
)
Text(
text = versionName,
modifier = Modifier.secondaryItemAlpha(),
style = MaterialTheme.typography.titleSmall,
)
Material3RichText(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = MaterialTheme.padding.large),
style = RichTextStyle(
stringStyle = RichTextStringStyle(
linkStyle = SpanStyle(color = MaterialTheme.colorScheme.primary),
),
),
) {
Markdown(content = changelogInfo)
TextButton(
onClick = onOpenInBrowser,
modifier = Modifier.padding(top = MaterialTheme.padding.small),
) {
Text(text = stringResource(R.string.update_check_open))
Spacer(modifier = Modifier.width(MaterialTheme.padding.tiny))
Icon(imageVector = Icons.Default.OpenInNew, contentDescription = null)
}
}
}
}
}

View File

@@ -19,7 +19,6 @@ import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import com.bluelinelabs.conductor.Router
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.LinkIcon
@@ -29,13 +28,12 @@ import eu.kanade.presentation.more.LogoHeader
import eu.kanade.presentation.more.about.LicensesScreen
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.presentation.util.LocalBackPress
import eu.kanade.presentation.util.LocalRouter
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
import eu.kanade.tachiyomi.data.updater.RELEASE_URL
import eu.kanade.tachiyomi.ui.more.NewUpdateDialogController
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
import eu.kanade.tachiyomi.util.CrashLogUtil
import eu.kanade.tachiyomi.util.lang.toDateTimestampString
import eu.kanade.tachiyomi.util.lang.withIOContext
@@ -61,7 +59,6 @@ object AboutScreen : Screen {
val uriHandler = LocalUriHandler.current
val handleBack = LocalBackPress.current
val navigator = LocalNavigator.currentOrThrow
val router = LocalRouter.currentOrThrow
Scaffold(
topBar = { scrollBehavior ->
@@ -96,7 +93,15 @@ object AboutScreen : Screen {
title = stringResource(R.string.check_for_updates),
onPreferenceClick = {
scope.launch {
checkVersion(context, router)
checkVersion(context) { result ->
val updateScreen = NewUpdateScreen(
versionName = result.release.version,
changelogInfo = result.release.info,
releaseLink = result.release.releaseLink,
downloadLink = result.release.getDownloadLink(),
)
navigator.push(updateScreen)
}
}
},
)
@@ -178,14 +183,14 @@ object AboutScreen : Screen {
/**
* Checks version and shows a user prompt if an update is available.
*/
private suspend fun checkVersion(context: Context, router: Router) {
private suspend fun checkVersion(context: Context, onAvailableUpdate: (AppUpdateResult.NewUpdate) -> Unit) {
val updateChecker = AppUpdateChecker()
withUIContext {
context.toast(R.string.update_check_look_for_updates)
try {
when (val result = withIOContext { updateChecker.checkForUpdate(context, isUserPrompt = true) }) {
is AppUpdateResult.NewUpdate -> {
NewUpdateDialogController(result).showDialog(router)
onAvailableUpdate(result)
}
is AppUpdateResult.NoNewUpdate -> {
context.toast(R.string.update_check_no_new_updates)

View File

@@ -9,7 +9,6 @@ import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material.icons.outlined.SelectAll
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ScaffoldDefaults
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.TopAppBarScrollBehavior
@@ -36,7 +35,6 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.updates.UpdatesItem
import eu.kanade.tachiyomi.ui.updates.UpdatesState
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.seconds
@@ -87,7 +85,6 @@ fun UpdateScreen(
)
},
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
contentWindowInsets = TachiyomiBottomNavigationView.withBottomNavInset(ScaffoldDefaults.contentWindowInsets),
) { contentPadding ->
when {
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))

View File

@@ -4,12 +4,7 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.staticCompositionLocalOf
import com.bluelinelabs.conductor.Router
/**
* For interop with Conductor
*/
val LocalRouter: ProvidableCompositionLocal<Router?> = staticCompositionLocalOf { null }
import cafe.adriel.voyager.navigator.Navigator
/**
* For invoking back press to the parent activity
@@ -17,3 +12,7 @@ val LocalRouter: ProvidableCompositionLocal<Router?> = staticCompositionLocalOf
val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null }
val LocalNavigatorContentPadding: ProvidableCompositionLocal<PaddingValues> = compositionLocalOf { PaddingValues() }
interface Tab : cafe.adriel.voyager.navigator.tab.Tab {
suspend fun onReselect(navigator: Navigator) {}
}