feat: EmptyScreen to replace EmptyView

This commit is contained in:
ziro 2024-01-13 12:33:23 +07:00
parent ff98cc65a9
commit 64c7b54075
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
7 changed files with 211 additions and 27 deletions

View file

@ -11,6 +11,7 @@ import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
import androidx.compose.material3.TopAppBarScrollBehavior
@ -34,11 +35,12 @@ fun YokaiScaffold(
onNavigationIconClicked: () -> Unit,
modifier: Modifier = Modifier,
title: String = "",
scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(state = rememberTopAppBarState()),
scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(state = rememberTopAppBarState()),
fab: @Composable () -> Unit = {},
navigationIcon: ImageVector = Icons.Filled.ArrowBack,
navigationIconLabel: String = stringResource(id = R.string.back),
actions: @Composable RowScope.() -> Unit = {},
appBarType: AppBarType = AppBarType.LARGE,
content: @Composable (PaddingValues) -> Unit,
) {
val view = LocalView.current
@ -57,25 +59,46 @@ fun YokaiScaffold(
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
floatingActionButton = fab,
topBar = {
LargeTopAppBar(
title = {
Text(text = title)
},
modifier = Modifier.statusBarsPadding(),
colors = topAppBarColors(
containerColor = color,
scrolledContainerColor = color,
),
navigationIcon = {
ToolTipButton(
toolTipLabel = navigationIconLabel,
icon = navigationIcon,
buttonClicked = onNavigationIconClicked,
)
},
scrollBehavior = scrollBehavior,
actions = actions,
)
when (appBarType) {
AppBarType.SMALL -> TopAppBar(
title = {
Text(text = title)
},
modifier = Modifier.statusBarsPadding(),
colors = topAppBarColors(
containerColor = color,
scrolledContainerColor = color,
),
navigationIcon = {
ToolTipButton(
toolTipLabel = navigationIconLabel,
icon = navigationIcon,
buttonClicked = onNavigationIconClicked,
)
},
scrollBehavior = scrollBehavior,
actions = actions,
)
AppBarType.LARGE -> LargeTopAppBar(
title = {
Text(text = title)
},
modifier = Modifier.statusBarsPadding(),
colors = topAppBarColors(
containerColor = color,
scrolledContainerColor = color,
),
navigationIcon = {
ToolTipButton(
toolTipLabel = navigationIconLabel,
icon = navigationIcon,
buttonClicked = onNavigationIconClicked,
)
},
scrollBehavior = scrollBehavior,
actions = actions,
)
}
},
content = content,
)
@ -88,3 +111,8 @@ fun getTopAppBarColor(title: String): Color {
false -> MaterialTheme.colorScheme.surface.copy(alpha = .7f)
}
}
enum class AppBarType {
SMALL,
LARGE,
}

View file

@ -0,0 +1,98 @@
package dev.yokai.presentation.component
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Download
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.util.compose.textHint
private val defaultIconModifier =
Modifier.size(128.dp)
/**
* Composable replacement for [eu.kanade.tachiyomi.widget.EmptyView]
*/
@Composable
fun EmptyScreen(
modifier: Modifier = Modifier,
image: ImageVector,
message: String,
actions: @Composable () -> Unit = {},
) = EmptyScreen(
modifier = modifier,
image = {
Image(
modifier = defaultIconModifier,
imageVector = image,
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.textHint),
)
},
message = message,
actions = actions,
)
@Composable
fun EmptyScreen(
modifier: Modifier = Modifier,
image: ImageBitmap,
message: String,
actions: @Composable () -> Unit = {},
) = EmptyScreen(
modifier = modifier,
image = {
Image(
modifier = defaultIconModifier,
bitmap = image,
contentDescription = null,
)
},
message = message,
actions = actions,
)
@Preview
@Composable
private fun EmptyScreen(
modifier: Modifier = Modifier,
image: @Composable () -> Unit = {
Image(modifier = defaultIconModifier, imageVector = Icons.Filled.Download, contentDescription = null)
},
message: String = "Something went wrong",
actions: @Composable () -> Unit = {},
) {
Column(
modifier = modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
image()
Text(
modifier = Modifier
.padding(top = 16.dp),
text = message,
color = MaterialTheme.colorScheme.textHint,
style = MaterialTheme.typography.labelMedium,
)
actions()
}
}

View file

@ -3,14 +3,16 @@ package dev.yokai.presentation.extension.repo
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ExtensionOff
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import dev.yokai.presentation.AppBarType
import dev.yokai.presentation.YokaiScaffold
import dev.yokai.presentation.component.EmptyScreen
import eu.kanade.tachiyomi.util.system.toast
@Composable
@ -30,11 +32,13 @@ fun ExtensionRepoScreen(
) {
Icon(Icons.Filled.Add, "Add repo")
}
}
},
appBarType = AppBarType.SMALL,
) { innerPadding ->
Text(
EmptyScreen(
modifier = Modifier.padding(innerPadding),
text = "Hello World!"
image = Icons.Filled.ExtensionOff,
message = "No extension repo found",
)
}
}

View file

@ -1,15 +1,18 @@
package dev.yokai.presentation.extension.repo
import androidx.annotation.StringRes
import androidx.compose.runtime.Immutable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dev.yokai.domain.Result
import dev.yokai.domain.extension.repo.ExtensionRepoRepository
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.launchIO
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.update
import okhttp3.internal.toImmutableList
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -18,8 +21,11 @@ class ExtensionRepoViewModel :
ViewModel() {
private val repository = ExtensionRepoRepository(Injekt.get())
private val _repoState: MutableStateFlow<ExtensionRepoState> = MutableStateFlow(ExtensionRepoState.Loading)
val repoState: StateFlow<ExtensionRepoState> = _repoState.asStateFlow()
private val mutableRepoState: MutableStateFlow<ExtensionRepoState> = MutableStateFlow(ExtensionRepoState.Loading)
val repoState: StateFlow<ExtensionRepoState> = mutableRepoState.asStateFlow()
private val internalEvent: MutableStateFlow<ExtensionRepoEvent> = MutableStateFlow(ExtensionRepoEvent.NoOp)
val event: StateFlow<ExtensionRepoEvent> = internalEvent.asStateFlow()
init {
refresh()
@ -29,7 +35,13 @@ class ExtensionRepoViewModel :
viewModelScope.launchIO {
val result = repository.addRepo(url)
if (result is Result.Error) return@launchIO
refresh()
}
}
fun deleteRepo(repo: String) {
viewModelScope.launchIO {
repository.deleteRepo(repo)
refresh()
}
}
@ -37,12 +49,18 @@ class ExtensionRepoViewModel :
fun refresh() {
viewModelScope.launchIO {
repository.getRepo().collectLatest { repos ->
_repoState.value = ExtensionRepoState.Success(repos = repos.toImmutableList())
mutableRepoState.update { ExtensionRepoState.Success(repos = repos.toImmutableList()) }
}
}
}
}
sealed class ExtensionRepoEvent {
sealed class LocalizedMessage(@StringRes val stringRes: Int) : ExtensionRepoEvent()
data object InvalidUrl : LocalizedMessage(R.string.invalid_repo_url)
data object NoOp : ExtensionRepoEvent()
}
sealed class ExtensionRepoState {
@Immutable

View file

@ -0,0 +1,26 @@
package dev.yokai.presentation.extension.repo.component
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@Composable
fun ExtensionRepoItem(
modifier: Modifier = Modifier,
repoUrl: String,
) {
Row(
modifier = modifier,
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = repoUrl,
)
Image(imageVector = Icons.Filled.Delete, contentDescription = null)
}
}

View file

@ -0,0 +1,5 @@
package eu.kanade.tachiyomi.util.compose
import androidx.compose.material3.ColorScheme
val ColorScheme.textHint get() = onBackground.copy(alpha = 0.35f)

View file

@ -905,7 +905,11 @@
<string name="nsfw_sources">NSFW (18+) sources</string>
<string name="show_in_sources_and_extensions">Show in sources and extensions lists</string>
<string name="does_not_prevent_unofficial_nsfw">This does not prevent unofficial or potentially incorrectly flagged extensions from surfacing NSFW (18+) content within the app.</string>
<!-- Extension repos -->
<string name="source_repos">Extension Repos</string>
<string name="action_add_repo">Add repo</string>
<string name="invalid_repo_url">Invalid repo url</string>
<!-- About section -->
<string name="version">Version</string>
@ -1189,4 +1193,5 @@
<string name="warning">Warning</string>
<string name="wifi">Wi-Fi</string>
<string name="webcomic">Webcomic</string>
<string name="internal_error">Internal error: %s</string>
</resources>