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:
Ivan Iskandar 2022-10-10 02:52:56 +07:00 committed by GitHub
parent 23bfa1f18f
commit 8500add09f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 413 additions and 239 deletions

View File

@ -13,6 +13,9 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons 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.Favorite
import androidx.compose.material.icons.outlined.FilterList import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material.icons.outlined.NewReleases 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.AppStateBanners
import eu.kanade.presentation.components.Divider import eu.kanade.presentation.components.Divider
import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.EmptyScreenAction
import eu.kanade.presentation.components.ExtendedFloatingActionButton import eu.kanade.presentation.components.ExtendedFloatingActionButton
import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold 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.source.LocalSource
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
import eu.kanade.tachiyomi.ui.more.MoreController import eu.kanade.tachiyomi.ui.more.MoreController
import eu.kanade.tachiyomi.widget.EmptyView
@Composable @Composable
fun BrowseSourceScreen( fun BrowseSourceScreen(
@ -248,13 +251,29 @@ fun BrowseSourceContent(
message = getErrorMessage(errorState), message = getErrorMessage(errorState),
actions = if (state.source is LocalSource) { actions = if (state.source is LocalSource) {
listOf( 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 { } else {
listOf( listOf(
EmptyView.Action(R.string.action_retry, R.drawable.ic_refresh_24dp) { mangaList.refresh() }, EmptyScreenAction(
EmptyView.Action(R.string.action_open_in_web_view, R.drawable.ic_public_24dp) { onWebViewClick() }, stringResId = R.string.action_retry,
EmptyView.Action(R.string.label_help, R.drawable.ic_help_24dp) { onHelpClick() }, 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,
),
) )
}, },
) )

View File

@ -133,7 +133,10 @@ private fun ExtensionDetails(
) { ) {
when { when {
presenter.isLoading -> LoadingScreen() 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 -> { else -> {
val context = LocalContext.current val context = LocalContext.current
val extension = presenter.extension val extension = presenter.extension

View File

@ -1,6 +1,7 @@
package eu.kanade.presentation.browse package eu.kanade.presentation.browse
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -37,7 +38,10 @@ fun ExtensionFilterScreen(
) { contentPadding -> ) { contentPadding ->
when { when {
presenter.isLoading -> LoadingScreen() presenter.isLoading -> LoadingScreen()
presenter.isEmpty -> EmptyScreen(textResource = R.string.empty_screen) presenter.isEmpty -> EmptyScreen(
textResource = R.string.empty_screen,
modifier = Modifier.padding(contentPadding),
)
else -> { else -> {
SourceFilterContent( SourceFilterContent(
contentPadding = contentPadding, contentPadding = contentPadding,

View File

@ -5,11 +5,9 @@ import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope 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.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.items 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.ExtensionUiModel
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsPresenter import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsPresenter
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView.Companion.bottomNavPadding
@Composable @Composable
fun ExtensionScreen( fun ExtensionScreen(
presenter: ExtensionsPresenter, presenter: ExtensionsPresenter,
contentPadding: PaddingValues,
onLongClickItem: (Extension) -> Unit, onLongClickItem: (Extension) -> Unit,
onClickItemCancel: (Extension) -> Unit, onClickItemCancel: (Extension) -> Unit,
onInstallExtension: (Extension.Available) -> Unit, onInstallExtension: (Extension.Available) -> Unit,
@ -77,10 +75,14 @@ fun ExtensionScreen(
) { ) {
when { when {
presenter.isLoading -> LoadingScreen() presenter.isLoading -> LoadingScreen()
presenter.isEmpty -> EmptyScreen(R.string.empty_screen) presenter.isEmpty -> EmptyScreen(
textResource = R.string.empty_screen,
modifier = Modifier.padding(contentPadding),
)
else -> { else -> {
ExtensionContent( ExtensionContent(
state = presenter, state = presenter,
contentPadding = contentPadding,
onLongClickItem = onLongClickItem, onLongClickItem = onLongClickItem,
onClickItemCancel = onClickItemCancel, onClickItemCancel = onClickItemCancel,
onInstallExtension = onInstallExtension, onInstallExtension = onInstallExtension,
@ -98,6 +100,7 @@ fun ExtensionScreen(
@Composable @Composable
private fun ExtensionContent( private fun ExtensionContent(
state: ExtensionsState, state: ExtensionsState,
contentPadding: PaddingValues,
onLongClickItem: (Extension) -> Unit, onLongClickItem: (Extension) -> Unit,
onClickItemCancel: (Extension) -> Unit, onClickItemCancel: (Extension) -> Unit,
onInstallExtension: (Extension.Available) -> Unit, onInstallExtension: (Extension.Available) -> Unit,
@ -110,7 +113,7 @@ private fun ExtensionContent(
var trustState by remember { mutableStateOf<Extension.Untrusted?>(null) } var trustState by remember { mutableStateOf<Extension.Untrusted?>(null) }
FastScrollLazyColumn( FastScrollLazyColumn(
contentPadding = bottomNavPadding + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues, contentPadding = contentPadding + topPaddingValues,
) { ) {
items( items(
items = state.items, items = state.items,

View File

@ -1,6 +1,7 @@
package eu.kanade.presentation.browse package eu.kanade.presentation.browse
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -39,7 +40,10 @@ fun MigrateMangaScreen(
) { contentPadding -> ) { contentPadding ->
when { when {
presenter.isLoading -> LoadingScreen() presenter.isLoading -> LoadingScreen()
presenter.isEmpty -> EmptyScreen(textResource = R.string.empty_screen) presenter.isEmpty -> EmptyScreen(
textResource = R.string.empty_screen,
modifier = Modifier.padding(contentPadding),
)
else -> { else -> {
MigrateMangaContent( MigrateMangaContent(
contentPadding = contentPadding, contentPadding = contentPadding,

View File

@ -3,10 +3,8 @@ package eu.kanade.presentation.browse
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row 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.layout.padding
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons 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.R
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrationSourcesPresenter import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrationSourcesPresenter
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView.Companion.bottomNavPadding
@Composable @Composable
fun MigrateSourceScreen( fun MigrateSourceScreen(
presenter: MigrationSourcesPresenter, presenter: MigrationSourcesPresenter,
contentPadding: PaddingValues,
onClickItem: (Source) -> Unit, onClickItem: (Source) -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
when { when {
presenter.isLoading -> LoadingScreen() 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 -> else ->
MigrateSourceList( MigrateSourceList(
list = presenter.items, list = presenter.items,
contentPadding = contentPadding,
onClickItem = onClickItem, onClickItem = onClickItem,
onLongClickItem = { source -> onLongClickItem = { source ->
val sourceId = source.id.toString() val sourceId = source.id.toString()
@ -72,6 +74,7 @@ fun MigrateSourceScreen(
@Composable @Composable
private fun MigrateSourceList( private fun MigrateSourceList(
list: List<Pair<Source, Long>>, list: List<Pair<Source, Long>>,
contentPadding: PaddingValues,
onClickItem: (Source) -> Unit, onClickItem: (Source) -> Unit,
onLongClickItem: (Source) -> Unit, onLongClickItem: (Source) -> Unit,
sortingMode: SetMigrateSorting.Mode, sortingMode: SetMigrateSorting.Mode,
@ -80,7 +83,7 @@ private fun MigrateSourceList(
onToggleSortingDirection: () -> Unit, onToggleSortingDirection: () -> Unit,
) { ) {
ScrollbarLazyColumn( ScrollbarLazyColumn(
contentPadding = bottomNavPadding + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues, contentPadding = contentPadding + topPaddingValues,
) { ) {
stickyHeader(key = "header") { stickyHeader(key = "header") {
Row( Row(

View File

@ -1,6 +1,7 @@
package eu.kanade.presentation.browse package eu.kanade.presentation.browse
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
@ -43,7 +44,10 @@ fun SourcesFilterScreen(
) { contentPadding -> ) { contentPadding ->
when { when {
presenter.isLoading -> LoadingScreen() 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 -> { else -> {
SourcesFilterContent( SourcesFilterContent(
contentPadding = contentPadding, contentPadding = contentPadding,

View File

@ -2,10 +2,8 @@ package eu.kanade.presentation.browse
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons 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.ui.browse.source.SourcesPresenter
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView.Companion.bottomNavPadding
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@Composable @Composable
fun SourcesScreen( fun SourcesScreen(
presenter: SourcesPresenter, presenter: SourcesPresenter,
contentPadding: PaddingValues,
onClickItem: (Source, String) -> Unit, onClickItem: (Source, String) -> Unit,
onClickDisable: (Source) -> Unit, onClickDisable: (Source) -> Unit,
onClickPin: (Source) -> Unit, onClickPin: (Source) -> Unit,
@ -53,10 +51,14 @@ fun SourcesScreen(
val context = LocalContext.current val context = LocalContext.current
when { when {
presenter.isLoading -> LoadingScreen() 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 -> { else -> {
SourceList( SourceList(
state = presenter, state = presenter,
contentPadding = contentPadding,
onClickItem = onClickItem, onClickItem = onClickItem,
onClickDisable = onClickDisable, onClickDisable = onClickDisable,
onClickPin = onClickPin, onClickPin = onClickPin,
@ -77,12 +79,13 @@ fun SourcesScreen(
@Composable @Composable
private fun SourceList( private fun SourceList(
state: SourcesState, state: SourcesState,
contentPadding: PaddingValues,
onClickItem: (Source, String) -> Unit, onClickItem: (Source, String) -> Unit,
onClickDisable: (Source) -> Unit, onClickDisable: (Source) -> Unit,
onClickPin: (Source) -> Unit, onClickPin: (Source) -> Unit,
) { ) {
ScrollbarLazyColumn( ScrollbarLazyColumn(
contentPadding = bottomNavPadding + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues, contentPadding = contentPadding + topPaddingValues,
) { ) {
items( items(
items = state.items, items = state.items,

View File

@ -1,9 +1,11 @@
package eu.kanade.presentation.category package eu.kanade.presentation.category
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.category.components.CategoryContent import eu.kanade.presentation.category.components.CategoryContent
@ -48,7 +50,10 @@ fun CategoryScreen(
val context = LocalContext.current val context = LocalContext.current
when { when {
presenter.isLoading -> LoadingScreen() 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 -> { else -> {
CategoryContent( CategoryContent(
state = presenter, state = presenter,

View File

@ -1,23 +1,49 @@
package eu.kanade.presentation.components package eu.kanade.presentation.components
import android.view.ViewGroup import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.annotation.StringRes 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.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.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.res.stringResource
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.text.font.FontFamily
import eu.kanade.tachiyomi.widget.EmptyView 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 @Composable
fun EmptyScreen( fun EmptyScreen(
@StringRes textResource: Int, @StringRes textResource: Int,
actions: List<EmptyView.Action>? = null, modifier: Modifier = Modifier,
actions: List<EmptyScreenAction>? = null,
) { ) {
EmptyScreen( EmptyScreen(
message = stringResource(textResource), message = stringResource(textResource),
modifier = modifier,
actions = actions, actions = actions,
) )
} }
@ -25,24 +51,174 @@ fun EmptyScreen(
@Composable @Composable
fun EmptyScreen( fun EmptyScreen(
message: String, message: String,
actions: List<EmptyView.Action>? = null, modifier: Modifier = Modifier,
actions: List<EmptyScreenAction>? = null,
) { ) {
Box( val face = remember { getRandomErrorFace() }
modifier = Modifier Layout(
.fillMaxSize(), content = {
) { Column(
AndroidView( modifier = Modifier
factory = { context -> .layoutId("face")
EmptyView(context).apply { .padding(horizontal = 24.dp),
layoutParams = ViewGroup.LayoutParams( horizontalAlignment = Alignment.CenterHorizontally,
ViewGroup.LayoutParams.WRAP_CONTENT, ) {
ViewGroup.LayoutParams.WRAP_CONTENT, Text(
) text = face,
show(message, actions) 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)]
}

View File

@ -2,6 +2,7 @@ package eu.kanade.presentation.components
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -17,6 +18,7 @@ import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.rememberPagerState import com.google.accompanist.pager.rememberPagerState
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable @Composable
@ -95,7 +97,11 @@ fun TabbedScreen(
state = state, state = state,
verticalAlignment = Alignment.Top, verticalAlignment = Alignment.Top,
) { page -> ) { page ->
tabs[page].content() tabs[page].content(
TachiyomiBottomNavigationView.withBottomNavPadding(
PaddingValues(bottom = contentPadding.calculateBottomPadding()),
),
)
} }
} }
} }
@ -105,5 +111,5 @@ data class TabContent(
@StringRes val titleRes: Int, @StringRes val titleRes: Int,
val badgeNumber: Int? = null, val badgeNumber: Int? = null,
val actions: List<AppBar.Action> = emptyList(), val actions: List<AppBar.Action> = emptyList(),
val content: @Composable () -> Unit, val content: @Composable (contentPadding: PaddingValues) -> Unit,
) )

View File

@ -1,9 +1,11 @@
package eu.kanade.presentation.history package eu.kanade.presentation.history
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import eu.kanade.domain.history.model.HistoryWithRelations import eu.kanade.domain.history.model.HistoryWithRelations
import eu.kanade.presentation.components.EmptyScreen 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
import eu.kanade.tachiyomi.ui.recent.history.HistoryPresenter.Dialog import eu.kanade.tachiyomi.ui.recent.history.HistoryPresenter.Dialog
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import java.util.Date import java.util.Date
@ -41,15 +44,19 @@ fun HistoryScreen(
}, },
) { contentPadding -> ) { contentPadding ->
val items by presenter.getHistory().collectAsState(initial = null) val items by presenter.getHistory().collectAsState(initial = null)
val contentPaddingWithNavBar = TachiyomiBottomNavigationView.withBottomNavPadding(contentPadding)
items.let { items.let {
if (it == null) { if (it == null) {
LoadingScreen() LoadingScreen()
} else if (it.isEmpty()) { } 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 { } else {
HistoryContent( HistoryContent(
history = it, history = it,
contentPadding = contentPadding, contentPadding = contentPaddingWithNavBar,
onClickCover = onClickCover, onClickCover = onClickCover,
onClickResume = onClickResume, onClickResume = onClickResume,
onClickDelete = { item -> presenter.dialog = Dialog.Delete(item) }, onClickDelete = { item -> presenter.dialog = Dialog.Delete(item) },

View File

@ -11,8 +11,6 @@ import eu.kanade.presentation.components.RelativeDateHeader
import eu.kanade.presentation.components.ScrollbarLazyColumn import eu.kanade.presentation.components.ScrollbarLazyColumn
import eu.kanade.presentation.history.HistoryUiModel import eu.kanade.presentation.history.HistoryUiModel
import eu.kanade.presentation.util.plus 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.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.text.DateFormat import java.text.DateFormat
@ -30,7 +28,7 @@ fun HistoryContent(
val dateFormat: DateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) } val dateFormat: DateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
ScrollbarLazyColumn( ScrollbarLazyColumn(
contentPadding = contentPadding + bottomNavPadding + topPaddingValues, contentPadding = contentPadding,
) { ) {
items( items(
items = history, items = history,

View File

@ -1,17 +1,26 @@
package eu.kanade.presentation.library package eu.kanade.presentation.library
import androidx.compose.animation.Crossfade 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.Composable
import androidx.compose.runtime.getValue 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.category.model.Category
import eu.kanade.domain.library.model.display 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.LibraryBottomActionMenu
import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.library.components.LibraryContent import eu.kanade.presentation.library.components.LibraryContent
import eu.kanade.presentation.library.components.LibraryToolbar import eu.kanade.presentation.library.components.LibraryToolbar
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.ui.library.LibraryPresenter import eu.kanade.tachiyomi.ui.library.LibraryPresenter
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
@Composable @Composable
fun LibraryScreen( fun LibraryScreen(
@ -60,9 +69,26 @@ fun LibraryScreen(
) )
}, },
) { paddingValues -> ) { 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( LibraryContent(
state = presenter, state = presenter,
contentPadding = paddingValues, contentPadding = contentPadding,
currentPage = { presenter.activeCategory }, currentPage = { presenter.activeCategory },
isLibraryEmpty = presenter.isLibraryEmpty, isLibraryEmpty = presenter.isLibraryEmpty,
showPageTabs = presenter.tabVisibility, showPageTabs = presenter.tabVisibility,

View File

@ -15,7 +15,6 @@ import androidx.compose.ui.zIndex
import eu.kanade.presentation.components.FastScrollLazyVerticalGrid import eu.kanade.presentation.components.FastScrollLazyVerticalGrid
import eu.kanade.presentation.util.plus import eu.kanade.presentation.util.plus
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView.Companion.bottomNavPadding
@Composable @Composable
fun LazyLibraryGrid( fun LazyLibraryGrid(
@ -27,7 +26,7 @@ fun LazyLibraryGrid(
FastScrollLazyVerticalGrid( FastScrollLazyVerticalGrid(
columns = if (columns == 0) GridCells.Adaptive(128.dp) else GridCells.Fixed(columns), columns = if (columns == 0) GridCells.Adaptive(128.dp) else GridCells.Fixed(columns),
modifier = modifier, modifier = modifier,
contentPadding = contentPadding + bottomNavPadding + PaddingValues(12.dp), contentPadding = contentPadding + PaddingValues(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp),
content = content, content = content,

View File

@ -15,18 +15,15 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalUriHandler
import com.google.accompanist.pager.rememberPagerState import com.google.accompanist.pager.rememberPagerState
import eu.kanade.core.prefs.PreferenceMutableState import eu.kanade.core.prefs.PreferenceMutableState
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
import eu.kanade.domain.library.model.LibraryDisplayMode import eu.kanade.domain.library.model.LibraryDisplayMode
import eu.kanade.domain.library.model.LibraryManga import eu.kanade.domain.library.model.LibraryManga
import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.presentation.components.SwipeRefresh import eu.kanade.presentation.components.SwipeRefresh
import eu.kanade.presentation.library.LibraryState 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.ui.library.LibraryItem
import eu.kanade.tachiyomi.widget.EmptyView
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -100,19 +97,6 @@ fun LibraryContent(
}, },
enabled = state.selectionMode.not(), 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( LibraryPager(
state = pagerState, state = pagerState,
contentPadding = PaddingValues(bottom = contentPadding.calculateBottomPadding()), contentPadding = PaddingValues(bottom = contentPadding.calculateBottomPadding()),

View File

@ -31,7 +31,6 @@ import eu.kanade.presentation.util.selectedBackground
import eu.kanade.presentation.util.verticalPadding import eu.kanade.presentation.util.verticalPadding
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.library.LibraryItem import eu.kanade.tachiyomi.ui.library.LibraryItem
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView.Companion.bottomNavPadding
@Composable @Composable
fun LibraryList( fun LibraryList(
@ -45,7 +44,7 @@ fun LibraryList(
) { ) {
FastScrollLazyColumn( FastScrollLazyColumn(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentPadding = bottomNavPadding + contentPadding, contentPadding = contentPadding,
) { ) {
item { item {
if (searchQuery.isNullOrEmpty().not()) { if (searchQuery.isNullOrEmpty().not()) {

View File

@ -27,7 +27,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.more.DownloadQueueState import eu.kanade.tachiyomi.ui.more.DownloadQueueState
import eu.kanade.tachiyomi.ui.more.MoreController import eu.kanade.tachiyomi.ui.more.MoreController
import eu.kanade.tachiyomi.ui.more.MorePresenter import eu.kanade.tachiyomi.ui.more.MorePresenter
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView.Companion.bottomNavPadding import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
@Composable @Composable
fun MoreScreen( fun MoreScreen(
@ -43,7 +43,7 @@ fun MoreScreen(
ScrollbarLazyColumn( ScrollbarLazyColumn(
modifier = Modifier.statusBarsPadding(), modifier = Modifier.statusBarsPadding(),
contentPadding = bottomNavPadding, contentPadding = TachiyomiBottomNavigationView.withBottomNavPadding(),
) { ) {
item { item {
LogoHeader() LogoHeader()

View File

@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.FlipToBack 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.Scaffold
import eu.kanade.presentation.components.SwipeRefresh import eu.kanade.presentation.components.SwipeRefresh
import eu.kanade.presentation.components.VerticalFastScroller import eu.kanade.presentation.components.VerticalFastScroller
import eu.kanade.presentation.util.plus
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.library.LibraryUpdateService 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.Dialog
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesPresenter.Event import eu.kanade.tachiyomi.ui.recent.updates.UpdatesPresenter.Event
import eu.kanade.tachiyomi.util.system.toast 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.delay
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -96,13 +96,17 @@ fun UpdateScreen(
) )
}, },
) { contentPadding -> ) { contentPadding ->
val contentPaddingWithNavBar = TachiyomiBottomNavigationView.withBottomNavPadding(contentPadding)
when { when {
presenter.isLoading -> LoadingScreen() 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 -> { else -> {
UpdateScreenContent( UpdateScreenContent(
presenter = presenter, presenter = presenter,
contentPadding = contentPadding, contentPadding = contentPaddingWithNavBar,
onUpdateLibrary = onUpdateLibrary, onUpdateLibrary = onUpdateLibrary,
onClickCover = onClickCover, onClickCover = onClickCover,
) )
@ -120,10 +124,6 @@ private fun UpdateScreenContent(
) { ) {
val context = LocalContext.current val context = LocalContext.current
val updatesListState = rememberLazyListState() val updatesListState = rememberLazyListState()
// During selection mode bottom nav is not visible
val contentPaddingWithNavBar = contentPadding + bottomNavPadding
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var isRefreshing by remember { mutableStateOf(false) } var isRefreshing by remember { mutableStateOf(false) }
@ -140,39 +140,35 @@ private fun UpdateScreenContent(
} }
}, },
enabled = presenter.selectionMode.not(), enabled = presenter.selectionMode.not(),
indicatorPadding = contentPaddingWithNavBar, indicatorPadding = contentPadding,
) { ) {
if (presenter.uiModels.isEmpty()) { VerticalFastScroller(
EmptyScreen(textResource = R.string.information_no_recent) listState = updatesListState,
} else { topContentPadding = contentPadding.calculateTopPadding(),
VerticalFastScroller( endContentPadding = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
listState = updatesListState, ) {
topContentPadding = contentPaddingWithNavBar.calculateTopPadding(), LazyColumn(
endContentPadding = contentPaddingWithNavBar.calculateEndPadding(LocalLayoutDirection.current), modifier = Modifier.fillMaxHeight(),
state = updatesListState,
contentPadding = contentPadding,
) { ) {
LazyColumn( if (presenter.lastUpdated > 0L) {
modifier = Modifier.fillMaxHeight(), updatesLastUpdatedItem(presenter.lastUpdated)
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,
)
} }
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,
)
} }
} }
} }

View File

@ -34,9 +34,10 @@ fun extensionsTab(
onClick = { router?.pushController(ExtensionFilterController()) }, onClick = { router?.pushController(ExtensionFilterController()) },
), ),
), ),
content = { content = { contentPadding ->
ExtensionScreen( ExtensionScreen(
presenter = presenter, presenter = presenter,
contentPadding = contentPadding,
onLongClickItem = { extension -> onLongClickItem = { extension ->
when (extension) { when (extension) {
is Extension.Available -> presenter.installExtension(extension) is Extension.Available -> presenter.installExtension(extension)

View File

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

View File

@ -32,9 +32,10 @@ fun sourcesTab(
onClick = { router?.pushController(SourceFilterController()) }, onClick = { router?.pushController(SourceFilterController()) },
), ),
), ),
content = { content = { contentPadding ->
SourcesScreen( SourcesScreen(
presenter = presenter, presenter = presenter,
contentPadding = contentPadding,
onClickItem = { source, query -> onClickItem = { source, query ->
presenter.onOpenSource(source) presenter.onOpenSource(source)
router?.pushController(BrowseSourceController(source, query)) router?.pushController(BrowseSourceController(source, query))

View File

@ -256,7 +256,10 @@ class DownloadController :
}, },
) { contentPadding -> ) { contentPadding ->
if (downloadList.isEmpty()) { if (downloadList.isEmpty()) {
EmptyScreen(textResource = R.string.information_no_downloads) EmptyScreen(
textResource = R.string.information_no_downloads,
modifier = Modifier.padding(contentPadding),
)
return@Scaffold return@Scaffold
} }
val density = LocalDensity.current val density = LocalDensity.current

View File

@ -1,26 +1,24 @@
package eu.kanade.tachiyomi.widget package eu.kanade.tachiyomi.widget
import android.content.Context import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet 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.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 androidx.core.view.isVisible
import com.google.android.material.button.MaterialButton import eu.kanade.presentation.components.EmptyScreen
import eu.kanade.tachiyomi.R import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.tachiyomi.databinding.CommonViewEmptyBinding
import eu.kanade.tachiyomi.util.system.getThemeColor
import kotlin.random.Random
class EmptyView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : class EmptyView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
RelativeLayout(context, attrs) { AbstractComposeView(context, attrs) {
private val binding: CommonViewEmptyBinding = var message by mutableStateOf("")
CommonViewEmptyBinding.inflate(LayoutInflater.from(context), this, true)
/** /**
* Hide the information view * Hide the information view
@ -33,62 +31,17 @@ class EmptyView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
* Show the information view * Show the information view
* @param textResource text of information view * @param textResource text of information view
*/ */
fun show(@StringRes textResource: Int, actions: List<Action>? = null) { fun show(@StringRes textResource: Int) {
show(context.getString(textResource), actions) message = context.getString(textResource)
}
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)
}
this.isVisible = true this.isVisible = true
} }
companion object { @Composable
private val ERROR_FACES = listOf( override fun Content() {
"(・o・;)", TachiyomiTheme {
"Σ(ಠ_ಠ)", CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onBackground) {
"ಥ_ಥ", EmptyScreen(message = message)
"(˘・_・˘)", }
"(; ̄Д ̄)",
"(・Д・。",
)
fun getRandomErrorFace(): String {
return ERROR_FACES[Random.nextInt(ERROR_FACES.size)]
} }
} }
data class Action(
@StringRes val stringResId: Int,
@DrawableRes val iconResId: Int,
val listener: OnClickListener,
)
} }

View File

@ -9,10 +9,16 @@ import android.os.Parcelable
import android.util.AttributeSet import android.util.AttributeSet
import android.view.ViewPropertyAnimator import android.view.ViewPropertyAnimator
import androidx.compose.foundation.layout.PaddingValues 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.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max
import androidx.customview.view.AbsSavedState import androidx.customview.view.AbsSavedState
import androidx.interpolator.view.animation.FastOutLinearInInterpolator import androidx.interpolator.view.animation.FastOutLinearInInterpolator
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator 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) { override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh) 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, SLIDE_UP_ANIMATION_DURATION,
LinearOutSlowInInterpolator(), LinearOutSlowInInterpolator(),
) )
bottomNavPadding = height.pxToDp.dp
} }
/** /**
@ -89,6 +96,7 @@ class TachiyomiBottomNavigationView @JvmOverloads constructor(
SLIDE_DOWN_ANIMATION_DURATION, SLIDE_DOWN_ANIMATION_DURATION,
FastOutLinearInInterpolator(), FastOutLinearInInterpolator(),
) )
bottomNavPadding = 0.dp
} }
private fun animateTranslation(targetY: Float, duration: Long, interpolator: TimeInterpolator) { 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_UP_ANIMATION_DURATION = 225L
private const val SLIDE_DOWN_ANIMATION_DURATION = 175L private const val SLIDE_DOWN_ANIMATION_DURATION = 175L
var bottomNavPadding by mutableStateOf(PaddingValues()) private var bottomNavPadding by mutableStateOf(0.dp)
private set
/**
* 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),
)
}
} }
} }

View File

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

View File

@ -81,16 +81,6 @@
</style> </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--> <!--Widgets.MaterialDivider-->
<!--=======================--> <!--=======================-->