mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 14:27:57 +01:00 
			
		
		
		
	EmptyScreen: Compose-ify and apply content padding (#8177)
* Apply content padding to empty screen except the empty screens in browse * compose-ify EmptyScreen * center face when action show * fix padding * apply content padding to browse tabs * fix duplicate bottom insets
This commit is contained in:
		| @@ -13,6 +13,9 @@ import androidx.compose.foundation.layout.size | ||||
| import androidx.compose.foundation.lazy.grid.GridCells | ||||
| import androidx.compose.foundation.rememberScrollState | ||||
| import androidx.compose.material.icons.Icons | ||||
| import androidx.compose.material.icons.filled.HelpOutline | ||||
| import androidx.compose.material.icons.filled.Public | ||||
| import androidx.compose.material.icons.filled.Refresh | ||||
| import androidx.compose.material.icons.outlined.Favorite | ||||
| import androidx.compose.material.icons.outlined.FilterList | ||||
| import androidx.compose.material.icons.outlined.NewReleases | ||||
| @@ -49,6 +52,7 @@ import eu.kanade.presentation.browse.components.BrowseSourceToolbar | ||||
| import eu.kanade.presentation.components.AppStateBanners | ||||
| import eu.kanade.presentation.components.Divider | ||||
| import eu.kanade.presentation.components.EmptyScreen | ||||
| import eu.kanade.presentation.components.EmptyScreenAction | ||||
| import eu.kanade.presentation.components.ExtendedFloatingActionButton | ||||
| import eu.kanade.presentation.components.LoadingScreen | ||||
| import eu.kanade.presentation.components.Scaffold | ||||
| @@ -56,7 +60,6 @@ import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.source.LocalSource | ||||
| import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter | ||||
| import eu.kanade.tachiyomi.ui.more.MoreController | ||||
| import eu.kanade.tachiyomi.widget.EmptyView | ||||
|  | ||||
| @Composable | ||||
| fun BrowseSourceScreen( | ||||
| @@ -248,13 +251,29 @@ fun BrowseSourceContent( | ||||
|             message = getErrorMessage(errorState), | ||||
|             actions = if (state.source is LocalSource) { | ||||
|                 listOf( | ||||
|                     EmptyView.Action(R.string.local_source_help_guide, R.drawable.ic_help_24dp) { onLocalSourceHelpClick() }, | ||||
|                     EmptyScreenAction( | ||||
|                         stringResId = R.string.local_source_help_guide, | ||||
|                         icon = Icons.Default.HelpOutline, | ||||
|                         onClick = onLocalSourceHelpClick, | ||||
|                     ), | ||||
|                 ) | ||||
|             } else { | ||||
|                 listOf( | ||||
|                     EmptyView.Action(R.string.action_retry, R.drawable.ic_refresh_24dp) { mangaList.refresh() }, | ||||
|                     EmptyView.Action(R.string.action_open_in_web_view, R.drawable.ic_public_24dp) { onWebViewClick() }, | ||||
|                     EmptyView.Action(R.string.label_help, R.drawable.ic_help_24dp) { onHelpClick() }, | ||||
|                     EmptyScreenAction( | ||||
|                         stringResId = R.string.action_retry, | ||||
|                         icon = Icons.Default.Refresh, | ||||
|                         onClick = mangaList::refresh, | ||||
|                     ), | ||||
|                     EmptyScreenAction( | ||||
|                         stringResId = R.string.action_open_in_web_view, | ||||
|                         icon = Icons.Default.Public, | ||||
|                         onClick = onWebViewClick, | ||||
|                     ), | ||||
|                     EmptyScreenAction( | ||||
|                         stringResId = R.string.label_help, | ||||
|                         icon = Icons.Default.HelpOutline, | ||||
|                         onClick = onHelpClick, | ||||
|                     ), | ||||
|                 ) | ||||
|             }, | ||||
|         ) | ||||
|   | ||||
| @@ -133,7 +133,10 @@ private fun ExtensionDetails( | ||||
| ) { | ||||
|     when { | ||||
|         presenter.isLoading -> LoadingScreen() | ||||
|         presenter.extension == null -> EmptyScreen(textResource = R.string.empty_screen) | ||||
|         presenter.extension == null -> EmptyScreen( | ||||
|             textResource = R.string.empty_screen, | ||||
|             modifier = Modifier.padding(contentPadding), | ||||
|         ) | ||||
|         else -> { | ||||
|             val context = LocalContext.current | ||||
|             val extension = presenter.extension | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package eu.kanade.presentation.browse | ||||
|  | ||||
| import androidx.compose.foundation.layout.PaddingValues | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.foundation.lazy.items | ||||
| import androidx.compose.material3.Switch | ||||
| import androidx.compose.runtime.Composable | ||||
| @@ -37,7 +38,10 @@ fun ExtensionFilterScreen( | ||||
|     ) { contentPadding -> | ||||
|         when { | ||||
|             presenter.isLoading -> LoadingScreen() | ||||
|             presenter.isEmpty -> EmptyScreen(textResource = R.string.empty_screen) | ||||
|             presenter.isEmpty -> EmptyScreen( | ||||
|                 textResource = R.string.empty_screen, | ||||
|                 modifier = Modifier.padding(contentPadding), | ||||
|             ) | ||||
|             else -> { | ||||
|                 SourceFilterContent( | ||||
|                     contentPadding = contentPadding, | ||||
|   | ||||
| @@ -5,11 +5,9 @@ import androidx.compose.animation.core.animateDpAsState | ||||
| import androidx.compose.foundation.combinedClickable | ||||
| import androidx.compose.foundation.layout.Box | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.layout.PaddingValues | ||||
| import androidx.compose.foundation.layout.Row | ||||
| import androidx.compose.foundation.layout.RowScope | ||||
| import androidx.compose.foundation.layout.WindowInsets | ||||
| import androidx.compose.foundation.layout.asPaddingValues | ||||
| import androidx.compose.foundation.layout.navigationBars | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.foundation.layout.size | ||||
| import androidx.compose.foundation.lazy.items | ||||
| @@ -55,11 +53,11 @@ import eu.kanade.tachiyomi.extension.model.InstallStep | ||||
| import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel | ||||
| import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsPresenter | ||||
| import eu.kanade.tachiyomi.util.system.LocaleHelper | ||||
| import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView.Companion.bottomNavPadding | ||||
|  | ||||
| @Composable | ||||
| fun ExtensionScreen( | ||||
|     presenter: ExtensionsPresenter, | ||||
|     contentPadding: PaddingValues, | ||||
|     onLongClickItem: (Extension) -> Unit, | ||||
|     onClickItemCancel: (Extension) -> Unit, | ||||
|     onInstallExtension: (Extension.Available) -> Unit, | ||||
| @@ -77,10 +75,14 @@ fun ExtensionScreen( | ||||
|     ) { | ||||
|         when { | ||||
|             presenter.isLoading -> LoadingScreen() | ||||
|             presenter.isEmpty -> EmptyScreen(R.string.empty_screen) | ||||
|             presenter.isEmpty -> EmptyScreen( | ||||
|                 textResource = R.string.empty_screen, | ||||
|                 modifier = Modifier.padding(contentPadding), | ||||
|             ) | ||||
|             else -> { | ||||
|                 ExtensionContent( | ||||
|                     state = presenter, | ||||
|                     contentPadding = contentPadding, | ||||
|                     onLongClickItem = onLongClickItem, | ||||
|                     onClickItemCancel = onClickItemCancel, | ||||
|                     onInstallExtension = onInstallExtension, | ||||
| @@ -98,6 +100,7 @@ fun ExtensionScreen( | ||||
| @Composable | ||||
| private fun ExtensionContent( | ||||
|     state: ExtensionsState, | ||||
|     contentPadding: PaddingValues, | ||||
|     onLongClickItem: (Extension) -> Unit, | ||||
|     onClickItemCancel: (Extension) -> Unit, | ||||
|     onInstallExtension: (Extension.Available) -> Unit, | ||||
| @@ -110,7 +113,7 @@ private fun ExtensionContent( | ||||
|     var trustState by remember { mutableStateOf<Extension.Untrusted?>(null) } | ||||
|  | ||||
|     FastScrollLazyColumn( | ||||
|         contentPadding = bottomNavPadding + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues, | ||||
|         contentPadding = contentPadding + topPaddingValues, | ||||
|     ) { | ||||
|         items( | ||||
|             items = state.items, | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package eu.kanade.presentation.browse | ||||
|  | ||||
| import androidx.compose.foundation.layout.PaddingValues | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.foundation.lazy.items | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.LaunchedEffect | ||||
| @@ -39,7 +40,10 @@ fun MigrateMangaScreen( | ||||
|     ) { contentPadding -> | ||||
|         when { | ||||
|             presenter.isLoading -> LoadingScreen() | ||||
|             presenter.isEmpty -> EmptyScreen(textResource = R.string.empty_screen) | ||||
|             presenter.isEmpty -> EmptyScreen( | ||||
|                 textResource = R.string.empty_screen, | ||||
|                 modifier = Modifier.padding(contentPadding), | ||||
|             ) | ||||
|             else -> { | ||||
|                 MigrateMangaContent( | ||||
|                     contentPadding = contentPadding, | ||||
|   | ||||
| @@ -3,10 +3,8 @@ package eu.kanade.presentation.browse | ||||
| import androidx.compose.foundation.background | ||||
| import androidx.compose.foundation.layout.Arrangement | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.layout.PaddingValues | ||||
| import androidx.compose.foundation.layout.Row | ||||
| import androidx.compose.foundation.layout.WindowInsets | ||||
| import androidx.compose.foundation.layout.asPaddingValues | ||||
| import androidx.compose.foundation.layout.navigationBars | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.foundation.lazy.items | ||||
| import androidx.compose.material.icons.Icons | ||||
| @@ -42,20 +40,24 @@ import eu.kanade.presentation.util.topPaddingValues | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrationSourcesPresenter | ||||
| import eu.kanade.tachiyomi.util.system.copyToClipboard | ||||
| import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView.Companion.bottomNavPadding | ||||
|  | ||||
| @Composable | ||||
| fun MigrateSourceScreen( | ||||
|     presenter: MigrationSourcesPresenter, | ||||
|     contentPadding: PaddingValues, | ||||
|     onClickItem: (Source) -> Unit, | ||||
| ) { | ||||
|     val context = LocalContext.current | ||||
|     when { | ||||
|         presenter.isLoading -> LoadingScreen() | ||||
|         presenter.isEmpty -> EmptyScreen(textResource = R.string.information_empty_library) | ||||
|         presenter.isEmpty -> EmptyScreen( | ||||
|             textResource = R.string.information_empty_library, | ||||
|             modifier = Modifier.padding(contentPadding), | ||||
|         ) | ||||
|         else -> | ||||
|             MigrateSourceList( | ||||
|                 list = presenter.items, | ||||
|                 contentPadding = contentPadding, | ||||
|                 onClickItem = onClickItem, | ||||
|                 onLongClickItem = { source -> | ||||
|                     val sourceId = source.id.toString() | ||||
| @@ -72,6 +74,7 @@ fun MigrateSourceScreen( | ||||
| @Composable | ||||
| private fun MigrateSourceList( | ||||
|     list: List<Pair<Source, Long>>, | ||||
|     contentPadding: PaddingValues, | ||||
|     onClickItem: (Source) -> Unit, | ||||
|     onLongClickItem: (Source) -> Unit, | ||||
|     sortingMode: SetMigrateSorting.Mode, | ||||
| @@ -80,7 +83,7 @@ private fun MigrateSourceList( | ||||
|     onToggleSortingDirection: () -> Unit, | ||||
| ) { | ||||
|     ScrollbarLazyColumn( | ||||
|         contentPadding = bottomNavPadding + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues, | ||||
|         contentPadding = contentPadding + topPaddingValues, | ||||
|     ) { | ||||
|         stickyHeader(key = "header") { | ||||
|             Row( | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package eu.kanade.presentation.browse | ||||
|  | ||||
| import androidx.compose.foundation.layout.PaddingValues | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.foundation.lazy.items | ||||
| import androidx.compose.material3.Checkbox | ||||
| import androidx.compose.material3.Switch | ||||
| @@ -43,7 +44,10 @@ fun SourcesFilterScreen( | ||||
|     ) { contentPadding -> | ||||
|         when { | ||||
|             presenter.isLoading -> LoadingScreen() | ||||
|             presenter.isEmpty -> EmptyScreen(textResource = R.string.source_filter_empty_screen) | ||||
|             presenter.isEmpty -> EmptyScreen( | ||||
|                 textResource = R.string.source_filter_empty_screen, | ||||
|                 modifier = Modifier.padding(contentPadding), | ||||
|             ) | ||||
|             else -> { | ||||
|                 SourcesFilterContent( | ||||
|                     contentPadding = contentPadding, | ||||
|   | ||||
| @@ -2,10 +2,8 @@ package eu.kanade.presentation.browse | ||||
|  | ||||
| import androidx.compose.foundation.clickable | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.layout.WindowInsets | ||||
| import androidx.compose.foundation.layout.asPaddingValues | ||||
| import androidx.compose.foundation.layout.PaddingValues | ||||
| import androidx.compose.foundation.layout.fillMaxWidth | ||||
| import androidx.compose.foundation.layout.navigationBars | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.foundation.lazy.items | ||||
| import androidx.compose.material.icons.Icons | ||||
| @@ -40,12 +38,12 @@ import eu.kanade.tachiyomi.source.LocalSource | ||||
| import eu.kanade.tachiyomi.ui.browse.source.SourcesPresenter | ||||
| import eu.kanade.tachiyomi.util.system.LocaleHelper | ||||
| import eu.kanade.tachiyomi.util.system.toast | ||||
| import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView.Companion.bottomNavPadding | ||||
| import kotlinx.coroutines.flow.collectLatest | ||||
|  | ||||
| @Composable | ||||
| fun SourcesScreen( | ||||
|     presenter: SourcesPresenter, | ||||
|     contentPadding: PaddingValues, | ||||
|     onClickItem: (Source, String) -> Unit, | ||||
|     onClickDisable: (Source) -> Unit, | ||||
|     onClickPin: (Source) -> Unit, | ||||
| @@ -53,10 +51,14 @@ fun SourcesScreen( | ||||
|     val context = LocalContext.current | ||||
|     when { | ||||
|         presenter.isLoading -> LoadingScreen() | ||||
|         presenter.isEmpty -> EmptyScreen(R.string.source_empty_screen) | ||||
|         presenter.isEmpty -> EmptyScreen( | ||||
|             textResource = R.string.source_empty_screen, | ||||
|             modifier = Modifier.padding(contentPadding), | ||||
|         ) | ||||
|         else -> { | ||||
|             SourceList( | ||||
|                 state = presenter, | ||||
|                 contentPadding = contentPadding, | ||||
|                 onClickItem = onClickItem, | ||||
|                 onClickDisable = onClickDisable, | ||||
|                 onClickPin = onClickPin, | ||||
| @@ -77,12 +79,13 @@ fun SourcesScreen( | ||||
| @Composable | ||||
| private fun SourceList( | ||||
|     state: SourcesState, | ||||
|     contentPadding: PaddingValues, | ||||
|     onClickItem: (Source, String) -> Unit, | ||||
|     onClickDisable: (Source) -> Unit, | ||||
|     onClickPin: (Source) -> Unit, | ||||
| ) { | ||||
|     ScrollbarLazyColumn( | ||||
|         contentPadding = bottomNavPadding + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues, | ||||
|         contentPadding = contentPadding + topPaddingValues, | ||||
|     ) { | ||||
|         items( | ||||
|             items = state.items, | ||||
|   | ||||
| @@ -1,9 +1,11 @@ | ||||
| package eu.kanade.presentation.category | ||||
|  | ||||
| import androidx.compose.foundation.layout.PaddingValues | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.foundation.lazy.rememberLazyListState | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.LaunchedEffect | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.platform.LocalContext | ||||
| import androidx.compose.ui.res.stringResource | ||||
| import eu.kanade.presentation.category.components.CategoryContent | ||||
| @@ -48,7 +50,10 @@ fun CategoryScreen( | ||||
|         val context = LocalContext.current | ||||
|         when { | ||||
|             presenter.isLoading -> LoadingScreen() | ||||
|             presenter.isEmpty -> EmptyScreen(textResource = R.string.information_empty_category) | ||||
|             presenter.isEmpty -> EmptyScreen( | ||||
|                 textResource = R.string.information_empty_category, | ||||
|                 modifier = Modifier.padding(paddingValues), | ||||
|             ) | ||||
|             else -> { | ||||
|                 CategoryContent( | ||||
|                     state = presenter, | ||||
|   | ||||
| @@ -1,23 +1,49 @@ | ||||
| package eu.kanade.presentation.components | ||||
|  | ||||
| import android.view.ViewGroup | ||||
| import android.content.res.Configuration.UI_MODE_NIGHT_YES | ||||
| import androidx.annotation.StringRes | ||||
| import androidx.compose.foundation.layout.Box | ||||
| import androidx.compose.foundation.layout.Arrangement | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.layout.Row | ||||
| import androidx.compose.foundation.layout.Spacer | ||||
| import androidx.compose.foundation.layout.fillMaxSize | ||||
| import androidx.compose.foundation.layout.height | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.foundation.layout.paddingFromBaseline | ||||
| import androidx.compose.material.icons.Icons | ||||
| import androidx.compose.material.icons.filled.HelpOutline | ||||
| import androidx.compose.material.icons.filled.Refresh | ||||
| import androidx.compose.material3.Icon | ||||
| import androidx.compose.material3.MaterialTheme | ||||
| import androidx.compose.material3.Surface | ||||
| import androidx.compose.material3.Text | ||||
| import androidx.compose.material3.TextButton | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.remember | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.graphics.vector.ImageVector | ||||
| import androidx.compose.ui.layout.Layout | ||||
| import androidx.compose.ui.layout.layoutId | ||||
| import androidx.compose.ui.res.stringResource | ||||
| import androidx.compose.ui.viewinterop.AndroidView | ||||
| import eu.kanade.tachiyomi.widget.EmptyView | ||||
| import androidx.compose.ui.text.font.FontFamily | ||||
| import androidx.compose.ui.text.style.TextAlign | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import androidx.compose.ui.unit.dp | ||||
| import eu.kanade.presentation.theme.TachiyomiTheme | ||||
| import eu.kanade.presentation.util.secondaryItemAlpha | ||||
| import eu.kanade.tachiyomi.R | ||||
| import kotlin.random.Random | ||||
|  | ||||
| @Composable | ||||
| fun EmptyScreen( | ||||
|     @StringRes textResource: Int, | ||||
|     actions: List<EmptyView.Action>? = null, | ||||
|     modifier: Modifier = Modifier, | ||||
|     actions: List<EmptyScreenAction>? = null, | ||||
| ) { | ||||
|     EmptyScreen( | ||||
|         message = stringResource(textResource), | ||||
|         modifier = modifier, | ||||
|         actions = actions, | ||||
|     ) | ||||
| } | ||||
| @@ -25,24 +51,174 @@ fun EmptyScreen( | ||||
| @Composable | ||||
| fun EmptyScreen( | ||||
|     message: String, | ||||
|     actions: List<EmptyView.Action>? = null, | ||||
|     modifier: Modifier = Modifier, | ||||
|     actions: List<EmptyScreenAction>? = null, | ||||
| ) { | ||||
|     Box( | ||||
|         modifier = Modifier | ||||
|             .fillMaxSize(), | ||||
|     ) { | ||||
|         AndroidView( | ||||
|             factory = { context -> | ||||
|                 EmptyView(context).apply { | ||||
|                     layoutParams = ViewGroup.LayoutParams( | ||||
|                         ViewGroup.LayoutParams.WRAP_CONTENT, | ||||
|                         ViewGroup.LayoutParams.WRAP_CONTENT, | ||||
|                     ) | ||||
|                     show(message, actions) | ||||
|     val face = remember { getRandomErrorFace() } | ||||
|     Layout( | ||||
|         content = { | ||||
|             Column( | ||||
|                 modifier = Modifier | ||||
|                     .layoutId("face") | ||||
|                     .padding(horizontal = 24.dp), | ||||
|                 horizontalAlignment = Alignment.CenterHorizontally, | ||||
|             ) { | ||||
|                 Text( | ||||
|                     text = face, | ||||
|                     modifier = Modifier.secondaryItemAlpha(), | ||||
|                     fontFamily = FontFamily.Monospace, | ||||
|                     style = MaterialTheme.typography.displayMedium, | ||||
|                 ) | ||||
|  | ||||
|                 Text( | ||||
|                     text = message, | ||||
|                     modifier = Modifier.paddingFromBaseline(top = 24.dp), | ||||
|                     style = MaterialTheme.typography.bodyMedium, | ||||
|                 ) | ||||
|             } | ||||
|             if (!actions.isNullOrEmpty()) { | ||||
|                 Row( | ||||
|                     modifier = Modifier | ||||
|                         .layoutId("actions") | ||||
|                         .padding( | ||||
|                             top = 24.dp, | ||||
|                             start = horizontalPadding, | ||||
|                             end = horizontalPadding, | ||||
|                         ), | ||||
|                     horizontalArrangement = Arrangement.spacedBy(space = 8.dp), | ||||
|                 ) { | ||||
|                     actions.forEach { | ||||
|                         ActionButton( | ||||
|                             modifier = Modifier.weight(1f), | ||||
|                             title = stringResource(id = it.stringResId), | ||||
|                             icon = it.icon, | ||||
|                             onClick = it.onClick, | ||||
|                         ) | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             modifier = Modifier | ||||
|                 .align(Alignment.Center), | ||||
|         ) | ||||
|             } | ||||
|         }, | ||||
|         modifier = modifier.fillMaxSize(), | ||||
|     ) { measurables, constraints -> | ||||
|         val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0) | ||||
|         val facePlaceable = measurables.first { it.layoutId == "face" } | ||||
|             .measure(looseConstraints) | ||||
|         val actionsPlaceable = measurables.firstOrNull { it.layoutId == "actions" } | ||||
|             ?.measure(looseConstraints) | ||||
|  | ||||
|         layout(constraints.maxWidth, constraints.maxHeight) { | ||||
|             val faceY = (constraints.maxHeight - facePlaceable.height) / 2 | ||||
|             facePlaceable.placeRelative( | ||||
|                 x = (constraints.maxWidth - facePlaceable.width) / 2, | ||||
|                 y = faceY, | ||||
|             ) | ||||
|  | ||||
|             actionsPlaceable?.placeRelative( | ||||
|                 x = (constraints.maxWidth - actionsPlaceable.width) / 2, | ||||
|                 y = faceY + facePlaceable.height, | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Composable | ||||
| private fun ActionButton( | ||||
|     modifier: Modifier = Modifier, | ||||
|     title: String, | ||||
|     icon: ImageVector, | ||||
|     onClick: () -> Unit, | ||||
| ) { | ||||
|     TextButton( | ||||
|         modifier = modifier, | ||||
|         onClick = onClick, | ||||
|     ) { | ||||
|         Column(horizontalAlignment = Alignment.CenterHorizontally) { | ||||
|             Icon( | ||||
|                 imageVector = icon, | ||||
|                 contentDescription = null, | ||||
|             ) | ||||
|             Spacer(Modifier.height(4.dp)) | ||||
|             Text( | ||||
|                 text = title, | ||||
|                 textAlign = TextAlign.Center, | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Preview( | ||||
|     name = "Light", | ||||
|     widthDp = 400, | ||||
|     heightDp = 400, | ||||
| ) | ||||
| @Preview( | ||||
|     name = "Dark", | ||||
|     widthDp = 400, | ||||
|     heightDp = 400, | ||||
|     uiMode = UI_MODE_NIGHT_YES, | ||||
| ) | ||||
| @Composable | ||||
| private fun NoActionPreview() { | ||||
|     TachiyomiTheme { | ||||
|         Surface { | ||||
|             EmptyScreen( | ||||
|                 textResource = R.string.empty_screen, | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Preview( | ||||
|     name = "Light", | ||||
|     widthDp = 400, | ||||
|     heightDp = 400, | ||||
| ) | ||||
| @Preview( | ||||
|     name = "Dark", | ||||
|     widthDp = 400, | ||||
|     heightDp = 400, | ||||
|     uiMode = UI_MODE_NIGHT_YES, | ||||
| ) | ||||
| @Composable | ||||
| private fun WithActionPreview() { | ||||
|     TachiyomiTheme { | ||||
|         Surface { | ||||
|             EmptyScreen( | ||||
|                 textResource = R.string.empty_screen, | ||||
|                 actions = listOf( | ||||
|                     EmptyScreenAction( | ||||
|                         stringResId = R.string.action_retry, | ||||
|                         icon = Icons.Default.Refresh, | ||||
|                         onClick = {}, | ||||
|                     ), | ||||
|                     EmptyScreenAction( | ||||
|                         stringResId = R.string.getting_started_guide, | ||||
|                         icon = Icons.Default.HelpOutline, | ||||
|                         onClick = {}, | ||||
|                     ), | ||||
|                 ), | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| data class EmptyScreenAction( | ||||
|     @StringRes val stringResId: Int, | ||||
|     val icon: ImageVector, | ||||
|     val onClick: () -> Unit, | ||||
| ) | ||||
|  | ||||
| private val horizontalPadding = 24.dp | ||||
|  | ||||
| private val ERROR_FACES = listOf( | ||||
|     "(・o・;)", | ||||
|     "Σ(ಠ_ಠ)", | ||||
|     "ಥ_ಥ", | ||||
|     "(˘・_・˘)", | ||||
|     "(; ̄Д ̄)", | ||||
|     "(・Д・。", | ||||
| ) | ||||
|  | ||||
| private fun getRandomErrorFace(): String { | ||||
|     return ERROR_FACES[Random.nextInt(ERROR_FACES.size)] | ||||
| } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package eu.kanade.presentation.components | ||||
|  | ||||
| import androidx.annotation.StringRes | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.layout.PaddingValues | ||||
| import androidx.compose.foundation.layout.calculateEndPadding | ||||
| import androidx.compose.foundation.layout.calculateStartPadding | ||||
| import androidx.compose.foundation.layout.fillMaxSize | ||||
| @@ -17,6 +18,7 @@ import androidx.compose.ui.platform.LocalLayoutDirection | ||||
| import androidx.compose.ui.res.stringResource | ||||
| import com.google.accompanist.pager.HorizontalPager | ||||
| import com.google.accompanist.pager.rememberPagerState | ||||
| import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView | ||||
| import kotlinx.coroutines.launch | ||||
|  | ||||
| @Composable | ||||
| @@ -95,7 +97,11 @@ fun TabbedScreen( | ||||
|                 state = state, | ||||
|                 verticalAlignment = Alignment.Top, | ||||
|             ) { page -> | ||||
|                 tabs[page].content() | ||||
|                 tabs[page].content( | ||||
|                     TachiyomiBottomNavigationView.withBottomNavPadding( | ||||
|                         PaddingValues(bottom = contentPadding.calculateBottomPadding()), | ||||
|                     ), | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @@ -105,5 +111,5 @@ data class TabContent( | ||||
|     @StringRes val titleRes: Int, | ||||
|     val badgeNumber: Int? = null, | ||||
|     val actions: List<AppBar.Action> = emptyList(), | ||||
|     val content: @Composable () -> Unit, | ||||
|     val content: @Composable (contentPadding: PaddingValues) -> Unit, | ||||
| ) | ||||
|   | ||||
| @@ -1,9 +1,11 @@ | ||||
| package eu.kanade.presentation.history | ||||
|  | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.LaunchedEffect | ||||
| import androidx.compose.runtime.collectAsState | ||||
| import androidx.compose.runtime.getValue | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.platform.LocalContext | ||||
| import eu.kanade.domain.history.model.HistoryWithRelations | ||||
| import eu.kanade.presentation.components.EmptyScreen | ||||
| @@ -19,6 +21,7 @@ import eu.kanade.tachiyomi.ui.reader.ReaderActivity | ||||
| import eu.kanade.tachiyomi.ui.recent.history.HistoryPresenter | ||||
| import eu.kanade.tachiyomi.ui.recent.history.HistoryPresenter.Dialog | ||||
| import eu.kanade.tachiyomi.util.system.toast | ||||
| import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView | ||||
| import kotlinx.coroutines.flow.collectLatest | ||||
| import java.util.Date | ||||
|  | ||||
| @@ -41,15 +44,19 @@ fun HistoryScreen( | ||||
|         }, | ||||
|     ) { contentPadding -> | ||||
|         val items by presenter.getHistory().collectAsState(initial = null) | ||||
|         val contentPaddingWithNavBar = TachiyomiBottomNavigationView.withBottomNavPadding(contentPadding) | ||||
|         items.let { | ||||
|             if (it == null) { | ||||
|                 LoadingScreen() | ||||
|             } else if (it.isEmpty()) { | ||||
|                 EmptyScreen(textResource = R.string.information_no_recent_manga) | ||||
|                 EmptyScreen( | ||||
|                     textResource = R.string.information_no_recent_manga, | ||||
|                     modifier = Modifier.padding(contentPaddingWithNavBar), | ||||
|                 ) | ||||
|             } else { | ||||
|                 HistoryContent( | ||||
|                     history = it, | ||||
|                     contentPadding = contentPadding, | ||||
|                     contentPadding = contentPaddingWithNavBar, | ||||
|                     onClickCover = onClickCover, | ||||
|                     onClickResume = onClickResume, | ||||
|                     onClickDelete = { item -> presenter.dialog = Dialog.Delete(item) }, | ||||
|   | ||||
| @@ -11,8 +11,6 @@ import eu.kanade.presentation.components.RelativeDateHeader | ||||
| import eu.kanade.presentation.components.ScrollbarLazyColumn | ||||
| import eu.kanade.presentation.history.HistoryUiModel | ||||
| import eu.kanade.presentation.util.plus | ||||
| import eu.kanade.presentation.util.topPaddingValues | ||||
| import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView.Companion.bottomNavPadding | ||||
| import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
| import java.text.DateFormat | ||||
| @@ -30,7 +28,7 @@ fun HistoryContent( | ||||
|     val dateFormat: DateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) } | ||||
|  | ||||
|     ScrollbarLazyColumn( | ||||
|         contentPadding = contentPadding + bottomNavPadding + topPaddingValues, | ||||
|         contentPadding = contentPadding, | ||||
|     ) { | ||||
|         items( | ||||
|             items = history, | ||||
|   | ||||
| @@ -1,17 +1,26 @@ | ||||
| package eu.kanade.presentation.library | ||||
|  | ||||
| import androidx.compose.animation.Crossfade | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.material.icons.Icons | ||||
| import androidx.compose.material.icons.filled.HelpOutline | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.getValue | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.platform.LocalUriHandler | ||||
| import eu.kanade.domain.category.model.Category | ||||
| import eu.kanade.domain.library.model.display | ||||
| import eu.kanade.presentation.components.EmptyScreen | ||||
| import eu.kanade.presentation.components.EmptyScreenAction | ||||
| import eu.kanade.presentation.components.LibraryBottomActionMenu | ||||
| import eu.kanade.presentation.components.LoadingScreen | ||||
| import eu.kanade.presentation.components.Scaffold | ||||
| import eu.kanade.presentation.library.components.LibraryContent | ||||
| import eu.kanade.presentation.library.components.LibraryToolbar | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.source.LocalSource | ||||
| import eu.kanade.tachiyomi.ui.library.LibraryPresenter | ||||
| import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView | ||||
|  | ||||
| @Composable | ||||
| fun LibraryScreen( | ||||
| @@ -60,9 +69,26 @@ fun LibraryScreen( | ||||
|                     ) | ||||
|                 }, | ||||
|             ) { paddingValues -> | ||||
|                 val contentPadding = TachiyomiBottomNavigationView.withBottomNavPadding(paddingValues) | ||||
|                 if (presenter.searchQuery.isNullOrEmpty() && presenter.isLibraryEmpty) { | ||||
|                     val handler = LocalUriHandler.current | ||||
|                     EmptyScreen( | ||||
|                         textResource = R.string.information_empty_library, | ||||
|                         modifier = Modifier.padding(contentPadding), | ||||
|                         actions = listOf( | ||||
|                             EmptyScreenAction( | ||||
|                                 stringResId = R.string.getting_started_guide, | ||||
|                                 icon = Icons.Default.HelpOutline, | ||||
|                                 onClick = { handler.openUri("https://tachiyomi.org/help/guides/getting-started") }, | ||||
|                             ), | ||||
|                         ), | ||||
|                     ) | ||||
|                     return@Scaffold | ||||
|                 } | ||||
|  | ||||
|                 LibraryContent( | ||||
|                     state = presenter, | ||||
|                     contentPadding = paddingValues, | ||||
|                     contentPadding = contentPadding, | ||||
|                     currentPage = { presenter.activeCategory }, | ||||
|                     isLibraryEmpty = presenter.isLibraryEmpty, | ||||
|                     showPageTabs = presenter.tabVisibility, | ||||
|   | ||||
| @@ -15,7 +15,6 @@ import androidx.compose.ui.zIndex | ||||
| import eu.kanade.presentation.components.FastScrollLazyVerticalGrid | ||||
| import eu.kanade.presentation.util.plus | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView.Companion.bottomNavPadding | ||||
|  | ||||
| @Composable | ||||
| fun LazyLibraryGrid( | ||||
| @@ -27,7 +26,7 @@ fun LazyLibraryGrid( | ||||
|     FastScrollLazyVerticalGrid( | ||||
|         columns = if (columns == 0) GridCells.Adaptive(128.dp) else GridCells.Fixed(columns), | ||||
|         modifier = modifier, | ||||
|         contentPadding = contentPadding + bottomNavPadding + PaddingValues(12.dp), | ||||
|         contentPadding = contentPadding + PaddingValues(12.dp), | ||||
|         verticalArrangement = Arrangement.spacedBy(12.dp), | ||||
|         horizontalArrangement = Arrangement.spacedBy(12.dp), | ||||
|         content = content, | ||||
|   | ||||
| @@ -15,18 +15,15 @@ import androidx.compose.runtime.rememberCoroutineScope | ||||
| import androidx.compose.runtime.setValue | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.platform.LocalLayoutDirection | ||||
| import androidx.compose.ui.platform.LocalUriHandler | ||||
| import com.google.accompanist.pager.rememberPagerState | ||||
| import eu.kanade.core.prefs.PreferenceMutableState | ||||
| import eu.kanade.domain.category.model.Category | ||||
| import eu.kanade.domain.library.model.LibraryDisplayMode | ||||
| import eu.kanade.domain.library.model.LibraryManga | ||||
| import eu.kanade.presentation.components.EmptyScreen | ||||
| import eu.kanade.presentation.components.SwipeRefresh | ||||
| import eu.kanade.presentation.library.LibraryState | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.presentation.util.plus | ||||
| import eu.kanade.tachiyomi.ui.library.LibraryItem | ||||
| import eu.kanade.tachiyomi.widget.EmptyView | ||||
| import kotlinx.coroutines.delay | ||||
| import kotlinx.coroutines.launch | ||||
|  | ||||
| @@ -100,19 +97,6 @@ fun LibraryContent( | ||||
|             }, | ||||
|             enabled = state.selectionMode.not(), | ||||
|         ) { | ||||
|             if (state.searchQuery.isNullOrEmpty() && isLibraryEmpty) { | ||||
|                 val handler = LocalUriHandler.current | ||||
|                 EmptyScreen( | ||||
|                     R.string.information_empty_library, | ||||
|                     listOf( | ||||
|                         EmptyView.Action(R.string.getting_started_guide, R.drawable.ic_help_24dp) { | ||||
|                             handler.openUri("https://tachiyomi.org/help/guides/getting-started") | ||||
|                         }, | ||||
|                     ), | ||||
|                 ) | ||||
|                 return@SwipeRefresh | ||||
|             } | ||||
|  | ||||
|             LibraryPager( | ||||
|                 state = pagerState, | ||||
|                 contentPadding = PaddingValues(bottom = contentPadding.calculateBottomPadding()), | ||||
|   | ||||
| @@ -31,7 +31,6 @@ import eu.kanade.presentation.util.selectedBackground | ||||
| import eu.kanade.presentation.util.verticalPadding | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.ui.library.LibraryItem | ||||
| import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView.Companion.bottomNavPadding | ||||
|  | ||||
| @Composable | ||||
| fun LibraryList( | ||||
| @@ -45,7 +44,7 @@ fun LibraryList( | ||||
| ) { | ||||
|     FastScrollLazyColumn( | ||||
|         modifier = Modifier.fillMaxSize(), | ||||
|         contentPadding = bottomNavPadding + contentPadding, | ||||
|         contentPadding = contentPadding, | ||||
|     ) { | ||||
|         item { | ||||
|             if (searchQuery.isNullOrEmpty().not()) { | ||||
|   | ||||
| @@ -27,7 +27,7 @@ import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.ui.more.DownloadQueueState | ||||
| import eu.kanade.tachiyomi.ui.more.MoreController | ||||
| import eu.kanade.tachiyomi.ui.more.MorePresenter | ||||
| import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView.Companion.bottomNavPadding | ||||
| import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView | ||||
|  | ||||
| @Composable | ||||
| fun MoreScreen( | ||||
| @@ -43,7 +43,7 @@ fun MoreScreen( | ||||
|  | ||||
|     ScrollbarLazyColumn( | ||||
|         modifier = Modifier.statusBarsPadding(), | ||||
|         contentPadding = bottomNavPadding, | ||||
|         contentPadding = TachiyomiBottomNavigationView.withBottomNavPadding(), | ||||
|     ) { | ||||
|         item { | ||||
|             LogoHeader() | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.PaddingValues | ||||
| import androidx.compose.foundation.layout.calculateEndPadding | ||||
| import androidx.compose.foundation.layout.fillMaxHeight | ||||
| import androidx.compose.foundation.layout.fillMaxWidth | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.foundation.lazy.rememberLazyListState | ||||
| import androidx.compose.material.icons.Icons | ||||
| import androidx.compose.material.icons.filled.FlipToBack | ||||
| @@ -33,7 +34,6 @@ import eu.kanade.presentation.components.MangaBottomActionMenu | ||||
| import eu.kanade.presentation.components.Scaffold | ||||
| import eu.kanade.presentation.components.SwipeRefresh | ||||
| import eu.kanade.presentation.components.VerticalFastScroller | ||||
| import eu.kanade.presentation.util.plus | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.download.model.Download | ||||
| import eu.kanade.tachiyomi.data.library.LibraryUpdateService | ||||
| @@ -43,7 +43,7 @@ import eu.kanade.tachiyomi.ui.recent.updates.UpdatesPresenter | ||||
| import eu.kanade.tachiyomi.ui.recent.updates.UpdatesPresenter.Dialog | ||||
| import eu.kanade.tachiyomi.ui.recent.updates.UpdatesPresenter.Event | ||||
| import eu.kanade.tachiyomi.util.system.toast | ||||
| import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView.Companion.bottomNavPadding | ||||
| import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView | ||||
| import kotlinx.coroutines.delay | ||||
| import kotlinx.coroutines.flow.collectLatest | ||||
| import kotlinx.coroutines.launch | ||||
| @@ -96,13 +96,17 @@ fun UpdateScreen( | ||||
|             ) | ||||
|         }, | ||||
|     ) { contentPadding -> | ||||
|         val contentPaddingWithNavBar = TachiyomiBottomNavigationView.withBottomNavPadding(contentPadding) | ||||
|         when { | ||||
|             presenter.isLoading -> LoadingScreen() | ||||
|             presenter.uiModels.isEmpty() -> EmptyScreen(textResource = R.string.information_no_recent) | ||||
|             presenter.uiModels.isEmpty() -> EmptyScreen( | ||||
|                 textResource = R.string.information_no_recent, | ||||
|                 modifier = Modifier.padding(contentPaddingWithNavBar), | ||||
|             ) | ||||
|             else -> { | ||||
|                 UpdateScreenContent( | ||||
|                     presenter = presenter, | ||||
|                     contentPadding = contentPadding, | ||||
|                     contentPadding = contentPaddingWithNavBar, | ||||
|                     onUpdateLibrary = onUpdateLibrary, | ||||
|                     onClickCover = onClickCover, | ||||
|                 ) | ||||
| @@ -120,10 +124,6 @@ private fun UpdateScreenContent( | ||||
| ) { | ||||
|     val context = LocalContext.current | ||||
|     val updatesListState = rememberLazyListState() | ||||
|  | ||||
|     // During selection mode bottom nav is not visible | ||||
|     val contentPaddingWithNavBar = contentPadding + bottomNavPadding | ||||
|  | ||||
|     val scope = rememberCoroutineScope() | ||||
|     var isRefreshing by remember { mutableStateOf(false) } | ||||
|  | ||||
| @@ -140,39 +140,35 @@ private fun UpdateScreenContent( | ||||
|             } | ||||
|         }, | ||||
|         enabled = presenter.selectionMode.not(), | ||||
|         indicatorPadding = contentPaddingWithNavBar, | ||||
|         indicatorPadding = contentPadding, | ||||
|     ) { | ||||
|         if (presenter.uiModels.isEmpty()) { | ||||
|             EmptyScreen(textResource = R.string.information_no_recent) | ||||
|         } else { | ||||
|             VerticalFastScroller( | ||||
|                 listState = updatesListState, | ||||
|                 topContentPadding = contentPaddingWithNavBar.calculateTopPadding(), | ||||
|                 endContentPadding = contentPaddingWithNavBar.calculateEndPadding(LocalLayoutDirection.current), | ||||
|         VerticalFastScroller( | ||||
|             listState = updatesListState, | ||||
|             topContentPadding = contentPadding.calculateTopPadding(), | ||||
|             endContentPadding = contentPadding.calculateEndPadding(LocalLayoutDirection.current), | ||||
|         ) { | ||||
|             LazyColumn( | ||||
|                 modifier = Modifier.fillMaxHeight(), | ||||
|                 state = updatesListState, | ||||
|                 contentPadding = contentPadding, | ||||
|             ) { | ||||
|                 LazyColumn( | ||||
|                     modifier = Modifier.fillMaxHeight(), | ||||
|                     state = updatesListState, | ||||
|                     contentPadding = contentPaddingWithNavBar, | ||||
|                 ) { | ||||
|                     if (presenter.lastUpdated > 0L) { | ||||
|                         updatesLastUpdatedItem(presenter.lastUpdated) | ||||
|                     } | ||||
|  | ||||
|                     updatesUiItems( | ||||
|                         uiModels = presenter.uiModels, | ||||
|                         selectionMode = presenter.selectionMode, | ||||
|                         onUpdateSelected = presenter::toggleSelection, | ||||
|                         onClickCover = onClickCover, | ||||
|                         onClickUpdate = { | ||||
|                             val intent = ReaderActivity.newIntent(context, it.update.mangaId, it.update.chapterId) | ||||
|                             context.startActivity(intent) | ||||
|                         }, | ||||
|                         onDownloadChapter = presenter::downloadChapters, | ||||
|                         relativeTime = presenter.relativeTime, | ||||
|                         dateFormat = presenter.dateFormat, | ||||
|                     ) | ||||
|                 if (presenter.lastUpdated > 0L) { | ||||
|                     updatesLastUpdatedItem(presenter.lastUpdated) | ||||
|                 } | ||||
|  | ||||
|                 updatesUiItems( | ||||
|                     uiModels = presenter.uiModels, | ||||
|                     selectionMode = presenter.selectionMode, | ||||
|                     onUpdateSelected = presenter::toggleSelection, | ||||
|                     onClickCover = onClickCover, | ||||
|                     onClickUpdate = { | ||||
|                         val intent = ReaderActivity.newIntent(context, it.update.mangaId, it.update.chapterId) | ||||
|                         context.startActivity(intent) | ||||
|                     }, | ||||
|                     onDownloadChapter = presenter::downloadChapters, | ||||
|                     relativeTime = presenter.relativeTime, | ||||
|                     dateFormat = presenter.dateFormat, | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -34,9 +34,10 @@ fun extensionsTab( | ||||
|             onClick = { router?.pushController(ExtensionFilterController()) }, | ||||
|         ), | ||||
|     ), | ||||
|     content = { | ||||
|     content = { contentPadding -> | ||||
|         ExtensionScreen( | ||||
|             presenter = presenter, | ||||
|             contentPadding = contentPadding, | ||||
|             onLongClickItem = { extension -> | ||||
|                 when (extension) { | ||||
|                     is Extension.Available -> presenter.installExtension(extension) | ||||
|   | ||||
| @@ -31,9 +31,10 @@ fun migrateSourcesTab( | ||||
|                 }, | ||||
|             ), | ||||
|         ), | ||||
|         content = { | ||||
|         content = { contentPadding -> | ||||
|             MigrateSourceScreen( | ||||
|                 presenter = presenter, | ||||
|                 contentPadding = contentPadding, | ||||
|                 onClickItem = { source -> | ||||
|                     router?.pushController( | ||||
|                         MigrationMangaController( | ||||
|   | ||||
| @@ -32,9 +32,10 @@ fun sourcesTab( | ||||
|             onClick = { router?.pushController(SourceFilterController()) }, | ||||
|         ), | ||||
|     ), | ||||
|     content = { | ||||
|     content = { contentPadding -> | ||||
|         SourcesScreen( | ||||
|             presenter = presenter, | ||||
|             contentPadding = contentPadding, | ||||
|             onClickItem = { source, query -> | ||||
|                 presenter.onOpenSource(source) | ||||
|                 router?.pushController(BrowseSourceController(source, query)) | ||||
|   | ||||
| @@ -256,7 +256,10 @@ class DownloadController : | ||||
|             }, | ||||
|         ) { contentPadding -> | ||||
|             if (downloadList.isEmpty()) { | ||||
|                 EmptyScreen(textResource = R.string.information_no_downloads) | ||||
|                 EmptyScreen( | ||||
|                     textResource = R.string.information_no_downloads, | ||||
|                     modifier = Modifier.padding(contentPadding), | ||||
|                 ) | ||||
|                 return@Scaffold | ||||
|             } | ||||
|             val density = LocalDensity.current | ||||
|   | ||||
| @@ -1,26 +1,24 @@ | ||||
| package eu.kanade.tachiyomi.widget | ||||
|  | ||||
| import android.content.Context | ||||
| import android.content.res.ColorStateList | ||||
| import android.util.AttributeSet | ||||
| import android.view.LayoutInflater | ||||
| import android.widget.LinearLayout | ||||
| import android.widget.RelativeLayout | ||||
| import androidx.annotation.DrawableRes | ||||
| import androidx.annotation.StringRes | ||||
| import androidx.appcompat.view.ContextThemeWrapper | ||||
| import androidx.compose.material3.LocalContentColor | ||||
| import androidx.compose.material3.MaterialTheme | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.CompositionLocalProvider | ||||
| import androidx.compose.runtime.getValue | ||||
| import androidx.compose.runtime.mutableStateOf | ||||
| import androidx.compose.runtime.setValue | ||||
| import androidx.compose.ui.platform.AbstractComposeView | ||||
| import androidx.core.view.isVisible | ||||
| import com.google.android.material.button.MaterialButton | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.databinding.CommonViewEmptyBinding | ||||
| import eu.kanade.tachiyomi.util.system.getThemeColor | ||||
| import kotlin.random.Random | ||||
| import eu.kanade.presentation.components.EmptyScreen | ||||
| import eu.kanade.presentation.theme.TachiyomiTheme | ||||
|  | ||||
| class EmptyView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : | ||||
|     RelativeLayout(context, attrs) { | ||||
|     AbstractComposeView(context, attrs) { | ||||
|  | ||||
|     private val binding: CommonViewEmptyBinding = | ||||
|         CommonViewEmptyBinding.inflate(LayoutInflater.from(context), this, true) | ||||
|     var message by mutableStateOf("") | ||||
|  | ||||
|     /** | ||||
|      * Hide the information view | ||||
| @@ -33,62 +31,17 @@ class EmptyView @JvmOverloads constructor(context: Context, attrs: AttributeSet? | ||||
|      * Show the information view | ||||
|      * @param textResource text of information view | ||||
|      */ | ||||
|     fun show(@StringRes textResource: Int, actions: List<Action>? = null) { | ||||
|         show(context.getString(textResource), actions) | ||||
|     } | ||||
|  | ||||
|     fun show(message: String, actions: List<Action>? = null) { | ||||
|         binding.textFace.text = getRandomErrorFace() | ||||
|         binding.textLabel.text = message | ||||
|  | ||||
|         binding.actionsContainer.removeAllViews() | ||||
|         val buttonContext = ContextThemeWrapper(context, R.style.Widget_Tachiyomi_Button_ActionButton) | ||||
|         val buttonColor = ColorStateList.valueOf(context.getThemeColor(R.attr.colorOnBackground)) | ||||
|         actions?.forEach { | ||||
|             val button = MaterialButton( | ||||
|                 buttonContext, | ||||
|                 null, | ||||
|                 R.attr.borderlessButtonStyle, | ||||
|             ).apply { | ||||
|                 layoutParams = LinearLayout.LayoutParams( | ||||
|                     0, | ||||
|                     LinearLayout.LayoutParams.WRAP_CONTENT, | ||||
|                     1f / actions.size, | ||||
|                 ) | ||||
|  | ||||
|                 setTextColor(buttonColor) | ||||
|                 iconTint = buttonColor | ||||
|  | ||||
|                 setIconResource(it.iconResId) | ||||
|                 setText(it.stringResId) | ||||
|  | ||||
|                 setOnClickListener(it.listener) | ||||
|             } | ||||
|  | ||||
|             binding.actionsContainer.addView(button) | ||||
|         } | ||||
|  | ||||
|     fun show(@StringRes textResource: Int) { | ||||
|         message = context.getString(textResource) | ||||
|         this.isVisible = true | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         private val ERROR_FACES = listOf( | ||||
|             "(・o・;)", | ||||
|             "Σ(ಠ_ಠ)", | ||||
|             "ಥ_ಥ", | ||||
|             "(˘・_・˘)", | ||||
|             "(; ̄Д ̄)", | ||||
|             "(・Д・。", | ||||
|         ) | ||||
|  | ||||
|         fun getRandomErrorFace(): String { | ||||
|             return ERROR_FACES[Random.nextInt(ERROR_FACES.size)] | ||||
|     @Composable | ||||
|     override fun Content() { | ||||
|         TachiyomiTheme { | ||||
|             CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onBackground) { | ||||
|                 EmptyScreen(message = message) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     data class Action( | ||||
|         @StringRes val stringResId: Int, | ||||
|         @DrawableRes val iconResId: Int, | ||||
|         val listener: OnClickListener, | ||||
|     ) | ||||
| } | ||||
|   | ||||
| @@ -9,10 +9,16 @@ import android.os.Parcelable | ||||
| import android.util.AttributeSet | ||||
| import android.view.ViewPropertyAnimator | ||||
| import androidx.compose.foundation.layout.PaddingValues | ||||
| import androidx.compose.foundation.layout.calculateEndPadding | ||||
| import androidx.compose.foundation.layout.calculateStartPadding | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.ReadOnlyComposable | ||||
| import androidx.compose.runtime.getValue | ||||
| import androidx.compose.runtime.mutableStateOf | ||||
| import androidx.compose.runtime.setValue | ||||
| import androidx.compose.ui.platform.LocalLayoutDirection | ||||
| import androidx.compose.ui.unit.dp | ||||
| import androidx.compose.ui.unit.max | ||||
| import androidx.customview.view.AbsSavedState | ||||
| import androidx.interpolator.view.animation.FastOutLinearInInterpolator | ||||
| import androidx.interpolator.view.animation.LinearOutSlowInInterpolator | ||||
| @@ -58,7 +64,7 @@ class TachiyomiBottomNavigationView @JvmOverloads constructor( | ||||
|  | ||||
|     override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { | ||||
|         super.onSizeChanged(w, h, oldw, oldh) | ||||
|         bottomNavPadding = PaddingValues(bottom = h.pxToDp.dp) | ||||
|         bottomNavPadding = h.pxToDp.dp | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -74,6 +80,7 @@ class TachiyomiBottomNavigationView @JvmOverloads constructor( | ||||
|             SLIDE_UP_ANIMATION_DURATION, | ||||
|             LinearOutSlowInInterpolator(), | ||||
|         ) | ||||
|         bottomNavPadding = height.pxToDp.dp | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -89,6 +96,7 @@ class TachiyomiBottomNavigationView @JvmOverloads constructor( | ||||
|             SLIDE_DOWN_ANIMATION_DURATION, | ||||
|             FastOutLinearInInterpolator(), | ||||
|         ) | ||||
|         bottomNavPadding = 0.dp | ||||
|     } | ||||
|  | ||||
|     private fun animateTranslation(targetY: Float, duration: Long, interpolator: TimeInterpolator) { | ||||
| @@ -149,7 +157,21 @@ class TachiyomiBottomNavigationView @JvmOverloads constructor( | ||||
|         private const val SLIDE_UP_ANIMATION_DURATION = 225L | ||||
|         private const val SLIDE_DOWN_ANIMATION_DURATION = 175L | ||||
|  | ||||
|         var bottomNavPadding by mutableStateOf(PaddingValues()) | ||||
|             private set | ||||
|         private var bottomNavPadding by mutableStateOf(0.dp) | ||||
|  | ||||
|         /** | ||||
|          * Merges [bottomNavPadding] to the origin's [PaddingValues] bottom side. | ||||
|          */ | ||||
|         @ReadOnlyComposable | ||||
|         @Composable | ||||
|         fun withBottomNavPadding(origin: PaddingValues = PaddingValues()): PaddingValues { | ||||
|             val layoutDirection = LocalLayoutDirection.current | ||||
|             return PaddingValues( | ||||
|                 start = origin.calculateStartPadding(layoutDirection), | ||||
|                 top = origin.calculateTopPadding(), | ||||
|                 end = origin.calculateEndPadding(layoutDirection), | ||||
|                 bottom = max(origin.calculateBottomPadding(), bottomNavPadding), | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user