Browse Source

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
Ivan Iskandar 2 years ago
parent
commit
8500add09f
27 changed files with 413 additions and 239 deletions
  1. 24 5
      app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt
  2. 4 1
      app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt
  3. 5 1
      app/src/main/java/eu/kanade/presentation/browse/ExtensionFilterScreen.kt
  4. 9 6
      app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt
  5. 5 1
      app/src/main/java/eu/kanade/presentation/browse/MigrateMangaScreen.kt
  6. 9 6
      app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt
  7. 5 1
      app/src/main/java/eu/kanade/presentation/browse/SourcesFilterScreen.kt
  8. 9 6
      app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt
  9. 6 1
      app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt
  10. 198 22
      app/src/main/java/eu/kanade/presentation/components/EmptyScreen.kt
  11. 8 2
      app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt
  12. 9 2
      app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt
  13. 1 3
      app/src/main/java/eu/kanade/presentation/history/components/HistoryContent.kt
  14. 27 1
      app/src/main/java/eu/kanade/presentation/library/LibraryScreen.kt
  15. 1 2
      app/src/main/java/eu/kanade/presentation/library/components/LazyLibraryGrid.kt
  16. 1 17
      app/src/main/java/eu/kanade/presentation/library/components/LibraryContent.kt
  17. 1 2
      app/src/main/java/eu/kanade/presentation/library/components/LibraryList.kt
  18. 2 2
      app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt
  19. 34 38
      app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt
  20. 2 1
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsTab.kt
  21. 2 1
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourcesTab.kt
  22. 2 1
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt
  23. 4 1
      app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt
  24. 20 67
      app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt
  25. 25 3
      app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiBottomNavigationView.kt
  26. 0 36
      app/src/main/res/layout/common_view_empty.xml
  27. 0 10
      app/src/main/res/values/styles.xml

+ 24 - 5
app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt

@@ -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,
+                    ),
                 )
             },
         )

+ 4 - 1
app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt

@@ -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

+ 5 - 1
app/src/main/java/eu/kanade/presentation/browse/ExtensionFilterScreen.kt

@@ -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,

+ 9 - 6
app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt

@@ -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,

+ 5 - 1
app/src/main/java/eu/kanade/presentation/browse/MigrateMangaScreen.kt

@@ -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,

+ 9 - 6
app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt

@@ -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(

+ 5 - 1
app/src/main/java/eu/kanade/presentation/browse/SourcesFilterScreen.kt

@@ -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,

+ 9 - 6
app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt

@@ -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,

+ 6 - 1
app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt

@@ -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,

+ 198 - 22
app/src/main/java/eu/kanade/presentation/components/EmptyScreen.kt

@@ -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)]
+}

+ 8 - 2
app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt

@@ -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,
 )

+ 9 - 2
app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt

@@ -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) },

+ 1 - 3
app/src/main/java/eu/kanade/presentation/history/components/HistoryContent.kt

@@ -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,

+ 27 - 1
app/src/main/java/eu/kanade/presentation/library/LibraryScreen.kt

@@ -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,

+ 1 - 2
app/src/main/java/eu/kanade/presentation/library/components/LazyLibraryGrid.kt

@@ -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,

+ 1 - 17
app/src/main/java/eu/kanade/presentation/library/components/LibraryContent.kt

@@ -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()),

+ 1 - 2
app/src/main/java/eu/kanade/presentation/library/components/LibraryList.kt

@@ -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()) {

+ 2 - 2
app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt

@@ -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()

+ 34 - 38
app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt

@@ -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,
+                )
             }
         }
     }

+ 2 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsTab.kt

@@ -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)

+ 2 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourcesTab.kt

@@ -31,9 +31,10 @@ fun migrateSourcesTab(
                 },
             ),
         ),
-        content = {
+        content = { contentPadding ->
             MigrateSourceScreen(
                 presenter = presenter,
+                contentPadding = contentPadding,
                 onClickItem = { source ->
                     router?.pushController(
                         MigrationMangaController(

+ 2 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt

@@ -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))

+ 4 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt

@@ -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

+ 20 - 67
app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt

@@ -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,
-    )
 }

+ 25 - 3
app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiBottomNavigationView.kt

@@ -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),
+            )
+        }
     }
 }

+ 0 - 36
app/src/main/res/layout/common_view_empty.xml

@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:gravity="center"
-    android:orientation="vertical"
-    android:padding="16dp">
-
-    <TextView
-        android:id="@+id/text_face"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:gravity="center"
-        android:textAppearance="?attr/textAppearanceBodyMedium"
-        android:textColor="?android:attr/textColorSecondary"
-        android:textSize="48sp"
-        tools:text="-_-" />
-
-    <TextView
-        android:id="@+id/text_label"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_margin="16dp"
-        android:gravity="center"
-        android:textAppearance="?attr/textAppearanceBodyMedium"
-        android:textColor="?android:attr/textColorSecondary"
-        tools:text="Label" />
-
-    <LinearLayout
-        android:id="@+id/actions_container"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal" />
-
-</LinearLayout>

+ 0 - 10
app/src/main/res/values/styles.xml

@@ -81,16 +81,6 @@
     </style>
 
 
-    <!--==============-->
-    <!--Widgets.Button-->
-    <!--==============-->
-    <style name="Widget.Tachiyomi.Button.ActionButton" parent="Widget.Material3.Button.TextButton.Icon">
-        <item name="iconGravity">top</item>
-        <item name="iconTint">@color/button_action_selector</item>
-        <item name="iconPadding">4dp</item>
-        <item name="android:textColor">@color/button_action_selector</item>
-        <item name="android:textSize">12sp</item>
-    </style>
     <!--=======================-->
     <!--Widgets.MaterialDivider-->
     <!--=======================-->