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
27 changed files with 413 additions and 239 deletions

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