123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409 |
- package eu.kanade.presentation.browse
- import androidx.annotation.StringRes
- import androidx.compose.foundation.combinedClickable
- import androidx.compose.foundation.layout.Arrangement
- import androidx.compose.foundation.layout.Column
- 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.lazy.items
- import androidx.compose.material.icons.Icons
- import androidx.compose.material.icons.filled.Close
- import androidx.compose.material3.AlertDialog
- import androidx.compose.material3.Button
- import androidx.compose.material3.Icon
- import androidx.compose.material3.IconButton
- import androidx.compose.material3.LocalTextStyle
- import androidx.compose.material3.MaterialTheme
- import androidx.compose.material3.Text
- import androidx.compose.material3.TextButton
- import androidx.compose.runtime.Composable
- import androidx.compose.runtime.getValue
- import androidx.compose.runtime.mutableStateOf
- import androidx.compose.runtime.remember
- import androidx.compose.runtime.setValue
- import androidx.compose.ui.Alignment
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.LocalContext
- import androidx.compose.ui.res.stringResource
- import androidx.compose.ui.text.style.TextOverflow
- import androidx.compose.ui.unit.dp
- import com.google.accompanist.swiperefresh.SwipeRefresh
- import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
- import eu.kanade.presentation.browse.components.BaseBrowseItem
- import eu.kanade.presentation.browse.components.ExtensionIcon
- import eu.kanade.presentation.components.EmptyScreen
- import eu.kanade.presentation.components.FastScrollLazyColumn
- import eu.kanade.presentation.components.LoadingScreen
- import eu.kanade.presentation.components.SwipeRefreshIndicator
- import eu.kanade.presentation.theme.header
- import eu.kanade.presentation.util.bottomNavPaddingValues
- import eu.kanade.presentation.util.horizontalPadding
- import eu.kanade.presentation.util.plus
- import eu.kanade.presentation.util.topPaddingValues
- import eu.kanade.tachiyomi.R
- import eu.kanade.tachiyomi.extension.model.Extension
- 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
- @Composable
- fun ExtensionScreen(
- presenter: ExtensionsPresenter,
- onLongClickItem: (Extension) -> Unit,
- onClickItemCancel: (Extension) -> Unit,
- onInstallExtension: (Extension.Available) -> Unit,
- onUninstallExtension: (Extension) -> Unit,
- onUpdateExtension: (Extension.Installed) -> Unit,
- onTrustExtension: (Extension.Untrusted) -> Unit,
- onOpenExtension: (Extension.Installed) -> Unit,
- onClickUpdateAll: () -> Unit,
- onRefresh: () -> Unit,
- ) {
- SwipeRefresh(
- state = rememberSwipeRefreshState(presenter.isRefreshing),
- indicator = { s, trigger -> SwipeRefreshIndicator(s, trigger) },
- onRefresh = onRefresh,
- ) {
- when {
- presenter.isLoading -> LoadingScreen()
- presenter.isEmpty -> EmptyScreen(R.string.empty_screen)
- else -> {
- ExtensionContent(
- state = presenter,
- onLongClickItem = onLongClickItem,
- onClickItemCancel = onClickItemCancel,
- onInstallExtension = onInstallExtension,
- onUninstallExtension = onUninstallExtension,
- onUpdateExtension = onUpdateExtension,
- onTrustExtension = onTrustExtension,
- onOpenExtension = onOpenExtension,
- onClickUpdateAll = onClickUpdateAll,
- )
- }
- }
- }
- }
- @Composable
- fun ExtensionContent(
- state: ExtensionsState,
- onLongClickItem: (Extension) -> Unit,
- onClickItemCancel: (Extension) -> Unit,
- onInstallExtension: (Extension.Available) -> Unit,
- onUninstallExtension: (Extension) -> Unit,
- onUpdateExtension: (Extension.Installed) -> Unit,
- onTrustExtension: (Extension.Untrusted) -> Unit,
- onOpenExtension: (Extension.Installed) -> Unit,
- onClickUpdateAll: () -> Unit,
- ) {
- var trustState by remember { mutableStateOf<Extension.Untrusted?>(null) }
- FastScrollLazyColumn(
- contentPadding = bottomNavPaddingValues + WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
- ) {
- items(
- items = state.items,
- key = {
- when (it) {
- is ExtensionUiModel.Header.Resource -> it.textRes
- is ExtensionUiModel.Header.Text -> it.text
- is ExtensionUiModel.Item -> it.key()
- }
- },
- contentType = {
- when (it) {
- is ExtensionUiModel.Item -> "item"
- else -> "header"
- }
- },
- ) { item ->
- when (item) {
- is ExtensionUiModel.Header.Resource -> {
- val action: @Composable RowScope.() -> Unit =
- if (item.textRes == R.string.ext_updates_pending) {
- {
- Button(onClick = { onClickUpdateAll() }) {
- Text(
- text = stringResource(R.string.ext_update_all),
- style = LocalTextStyle.current.copy(
- color = MaterialTheme.colorScheme.onPrimary,
- ),
- )
- }
- }
- } else {
- {}
- }
- ExtensionHeader(
- textRes = item.textRes,
- modifier = Modifier.animateItemPlacement(),
- action = action,
- )
- }
- is ExtensionUiModel.Header.Text -> {
- ExtensionHeader(
- text = item.text,
- modifier = Modifier.animateItemPlacement(),
- )
- }
- is ExtensionUiModel.Item -> {
- ExtensionItem(
- modifier = Modifier.animateItemPlacement(),
- item = item,
- onClickItem = {
- when (it) {
- is Extension.Available -> onInstallExtension(it)
- is Extension.Installed -> onOpenExtension(it)
- is Extension.Untrusted -> { trustState = it }
- }
- },
- onLongClickItem = onLongClickItem,
- onClickItemCancel = onClickItemCancel,
- onClickItemAction = {
- when (it) {
- is Extension.Available -> onInstallExtension(it)
- is Extension.Installed -> {
- if (it.hasUpdate) {
- onUpdateExtension(it)
- } else {
- onOpenExtension(it)
- }
- }
- is Extension.Untrusted -> { trustState = it }
- }
- },
- )
- }
- }
- }
- }
- if (trustState != null) {
- ExtensionTrustDialog(
- onClickConfirm = {
- onTrustExtension(trustState!!)
- trustState = null
- },
- onClickDismiss = {
- onUninstallExtension(trustState!!)
- trustState = null
- },
- onDismissRequest = {
- trustState = null
- },
- )
- }
- }
- @Composable
- fun ExtensionItem(
- modifier: Modifier = Modifier,
- item: ExtensionUiModel.Item,
- onClickItem: (Extension) -> Unit,
- onLongClickItem: (Extension) -> Unit,
- onClickItemCancel: (Extension) -> Unit,
- onClickItemAction: (Extension) -> Unit,
- ) {
- val (extension, installStep) = item
- BaseBrowseItem(
- modifier = modifier
- .combinedClickable(
- onClick = { onClickItem(extension) },
- onLongClick = { onLongClickItem(extension) },
- ),
- onClickItem = { onClickItem(extension) },
- onLongClickItem = { onLongClickItem(extension) },
- icon = {
- ExtensionIcon(extension = extension)
- },
- action = {
- ExtensionItemActions(
- extension = extension,
- installStep = installStep,
- onClickItemCancel = onClickItemCancel,
- onClickItemAction = onClickItemAction,
- )
- },
- ) {
- ExtensionItemContent(
- extension = extension,
- modifier = Modifier.weight(1f),
- )
- }
- }
- @Composable
- fun ExtensionItemContent(
- extension: Extension,
- modifier: Modifier = Modifier,
- ) {
- val context = LocalContext.current
- val warning = remember(extension) {
- when {
- extension is Extension.Untrusted -> R.string.ext_untrusted
- extension is Extension.Installed && extension.isUnofficial -> R.string.ext_unofficial
- extension is Extension.Installed && extension.isObsolete -> R.string.ext_obsolete
- extension.isNsfw -> R.string.ext_nsfw_short
- else -> null
- }
- }
- Column(
- modifier = modifier.padding(start = horizontalPadding),
- ) {
- Text(
- text = extension.name,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
- style = MaterialTheme.typography.bodyMedium,
- )
- Row(
- horizontalArrangement = Arrangement.spacedBy(4.dp),
- ) {
- if (extension.lang.isNullOrEmpty().not()) {
- Text(
- text = LocaleHelper.getSourceDisplayName(extension.lang, context),
- style = MaterialTheme.typography.bodySmall,
- )
- }
- if (extension.versionName.isNotEmpty()) {
- Text(
- text = extension.versionName,
- style = MaterialTheme.typography.bodySmall,
- )
- }
- if (warning != null) {
- Text(
- text = stringResource(warning).uppercase(),
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
- style = MaterialTheme.typography.bodySmall.copy(
- color = MaterialTheme.colorScheme.error,
- ),
- )
- }
- }
- }
- }
- @Composable
- fun ExtensionItemActions(
- extension: Extension,
- installStep: InstallStep,
- modifier: Modifier = Modifier,
- onClickItemCancel: (Extension) -> Unit = {},
- onClickItemAction: (Extension) -> Unit = {},
- ) {
- val isIdle = remember(installStep) {
- installStep == InstallStep.Idle || installStep == InstallStep.Error
- }
- Row(modifier = modifier) {
- TextButton(
- onClick = { onClickItemAction(extension) },
- enabled = isIdle,
- ) {
- Text(
- text = when (installStep) {
- InstallStep.Pending -> stringResource(R.string.ext_pending)
- InstallStep.Downloading -> stringResource(R.string.ext_downloading)
- InstallStep.Installing -> stringResource(R.string.ext_installing)
- InstallStep.Installed -> stringResource(R.string.ext_installed)
- InstallStep.Error -> stringResource(R.string.action_retry)
- InstallStep.Idle -> {
- when (extension) {
- is Extension.Installed -> {
- if (extension.hasUpdate) {
- stringResource(R.string.ext_update)
- } else {
- stringResource(R.string.action_settings)
- }
- }
- is Extension.Untrusted -> stringResource(R.string.ext_trust)
- is Extension.Available -> stringResource(R.string.ext_install)
- }
- }
- },
- style = LocalTextStyle.current.copy(
- color = if (isIdle) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceTint,
- ),
- )
- }
- if (isIdle.not()) {
- IconButton(onClick = { onClickItemCancel(extension) }) {
- Icon(
- imageVector = Icons.Default.Close,
- contentDescription = "",
- tint = MaterialTheme.colorScheme.onBackground,
- )
- }
- }
- }
- }
- @Composable
- fun ExtensionHeader(
- @StringRes textRes: Int,
- modifier: Modifier = Modifier,
- action: @Composable RowScope.() -> Unit = {},
- ) {
- ExtensionHeader(
- text = stringResource(textRes),
- modifier = modifier,
- action = action,
- )
- }
- @Composable
- fun ExtensionHeader(
- text: String,
- modifier: Modifier = Modifier,
- action: @Composable RowScope.() -> Unit = {},
- ) {
- Row(
- modifier = modifier.padding(horizontal = horizontalPadding),
- verticalAlignment = Alignment.CenterVertically,
- ) {
- Text(
- text = text,
- modifier = Modifier
- .padding(vertical = 8.dp)
- .weight(1f),
- style = MaterialTheme.typography.header,
- )
- action()
- }
- }
- @Composable
- fun ExtensionTrustDialog(
- onClickConfirm: () -> Unit,
- onClickDismiss: () -> Unit,
- onDismissRequest: () -> Unit,
- ) {
- AlertDialog(
- title = {
- Text(text = stringResource(R.string.untrusted_extension))
- },
- text = {
- Text(text = stringResource(R.string.untrusted_extension_message))
- },
- confirmButton = {
- TextButton(onClick = onClickConfirm) {
- Text(text = stringResource(R.string.ext_trust))
- }
- },
- dismissButton = {
- TextButton(onClick = onClickDismiss) {
- Text(text = stringResource(R.string.ext_uninstall))
- }
- },
- onDismissRequest = onDismissRequest,
- )
- }
|