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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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>
<!--==============-->
<!--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-->
<!--=======================-->