From fd99a5f50268156fe4e7c0a047692427971bfdf6 Mon Sep 17 00:00:00 2001 From: Jobobby04 Date: Thu, 10 Nov 2022 23:48:03 -0500 Subject: [PATCH] Use Voyager on Sort Tags screen --- .../presentation/category/SortTagScreen.kt | 79 ++++------- .../presentation/category/SortTagState.kt | 27 ---- .../components/genre/SortTagContent.kt | 10 +- .../ui/category/genre/SortTagController.kt | 17 ++- .../ui/category/genre/SortTagPresenter.kt | 85 ------------ .../ui/category/genre/SortTagScreen.kt | 74 +++++++++++ .../ui/category/genre/SortTagScreenModel.kt | 123 ++++++++++++++++++ 7 files changed, 232 insertions(+), 183 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/presentation/category/SortTagState.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagPresenter.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagScreen.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagScreenModel.kt diff --git a/app/src/main/java/eu/kanade/presentation/category/SortTagScreen.kt b/app/src/main/java/eu/kanade/presentation/category/SortTagScreen.kt index cf81fe249..f5ab3ec8f 100644 --- a/app/src/main/java/eu/kanade/presentation/category/SortTagScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/category/SortTagScreen.kt @@ -1,31 +1,29 @@ 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.platform.LocalContext +import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import eu.kanade.presentation.category.components.CategoryCreateDialog -import eu.kanade.presentation.category.components.CategoryDeleteDialog import eu.kanade.presentation.category.components.CategoryFloatingActionButton import eu.kanade.presentation.category.components.genre.SortTagContent import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.EmptyScreen -import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.Scaffold 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.ui.category.genre.SortTagPresenter -import eu.kanade.tachiyomi.ui.category.genre.SortTagPresenter.Dialog -import eu.kanade.tachiyomi.util.system.toast -import kotlinx.coroutines.flow.collectLatest +import eu.kanade.tachiyomi.ui.category.genre.SortTagScreenState @Composable fun SortTagScreen( - presenter: SortTagPresenter, + state: SortTagScreenState.Success, + onClickCreate: () -> Unit, + onClickDelete: (String) -> Unit, + onClickMoveUp: (String, Int) -> Unit, + onClickMoveDown: (String, Int) -> Unit, navigateUp: () -> Unit, ) { val lazyListState = rememberLazyListState() @@ -40,56 +38,25 @@ fun SortTagScreen( floatingActionButton = { CategoryFloatingActionButton( lazyListState = lazyListState, - onCreate = { presenter.dialog = Dialog.Create }, + onCreate = onClickCreate, ) }, ) { paddingValues -> - val context = LocalContext.current - when { - presenter.isLoading -> LoadingScreen() - presenter.isEmpty -> EmptyScreen(textResource = R.string.information_empty_category) - else -> { - SortTagContent( - state = presenter, - lazyListState = lazyListState, - paddingValues = paddingValues + topPaddingValues + PaddingValues(horizontal = horizontalPadding), - onMoveUp = { tag, index -> presenter.moveUp(tag, index) }, - onMoveDown = { tag, index -> presenter.moveDown(tag, index) }, - ) - } + if (state.isEmpty) { + EmptyScreen( + textResource = R.string.information_empty_category, + modifier = Modifier.padding(paddingValues), + ) + return@Scaffold } - val onDismissRequest = { presenter.dialog = null } - when (val dialog = presenter.dialog) { - Dialog.Create -> { - CategoryCreateDialog( - onDismissRequest = onDismissRequest, - onCreate = { presenter.createTag(it) }, - title = stringResource(R.string.add_tag), - extraMessage = stringResource(R.string.action_add_tags_message), - ) - } - is Dialog.Delete -> { - CategoryDeleteDialog( - onDismissRequest = onDismissRequest, - onDelete = { presenter.delete(dialog.tag) }, - title = stringResource(R.string.delete_tag), - text = stringResource(R.string.delete_tag_confirmation, dialog.tag), - ) - } - else -> {} - } - LaunchedEffect(Unit) { - presenter.events.collectLatest { event -> - when (event) { - is SortTagPresenter.Event.TagExists -> { - context.toast(R.string.error_tag_exists) - } - is SortTagPresenter.Event.InternalError -> { - context.toast(R.string.internal_error) - } - } - } - } + SortTagContent( + tags = state.tags, + lazyListState = lazyListState, + paddingValues = paddingValues + topPaddingValues + PaddingValues(horizontal = horizontalPadding), + onClickDelete = onClickDelete, + onMoveUp = onClickMoveUp, + onMoveDown = onClickMoveDown, + ) } } diff --git a/app/src/main/java/eu/kanade/presentation/category/SortTagState.kt b/app/src/main/java/eu/kanade/presentation/category/SortTagState.kt deleted file mode 100644 index 0a4813f31..000000000 --- a/app/src/main/java/eu/kanade/presentation/category/SortTagState.kt +++ /dev/null @@ -1,27 +0,0 @@ -package eu.kanade.presentation.category - -import androidx.compose.runtime.Stable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import eu.kanade.tachiyomi.ui.category.genre.SortTagPresenter - -@Stable -interface SortTagState { - val isLoading: Boolean - var dialog: SortTagPresenter.Dialog? - val tags: List - val isEmpty: Boolean -} - -fun SortTagState(): SortTagState { - return SortTagStateImpl() -} - -class SortTagStateImpl : SortTagState { - override var isLoading: Boolean by mutableStateOf(true) - override var dialog: SortTagPresenter.Dialog? by mutableStateOf(null) - override var tags: List by mutableStateOf(emptyList()) - override val isEmpty: Boolean by derivedStateOf { tags.isEmpty() } -} diff --git a/app/src/main/java/eu/kanade/presentation/category/components/genre/SortTagContent.kt b/app/src/main/java/eu/kanade/presentation/category/components/genre/SortTagContent.kt index 027637f67..b767d5a87 100644 --- a/app/src/main/java/eu/kanade/presentation/category/components/genre/SortTagContent.kt +++ b/app/src/main/java/eu/kanade/presentation/category/components/genre/SortTagContent.kt @@ -7,25 +7,23 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import eu.kanade.presentation.category.SortTagState import eu.kanade.presentation.components.LazyColumn -import eu.kanade.tachiyomi.ui.category.genre.SortTagPresenter @Composable fun SortTagContent( - state: SortTagState, + tags: List, lazyListState: LazyListState, paddingValues: PaddingValues, + onClickDelete: (String) -> Unit, onMoveUp: (String, Int) -> Unit, onMoveDown: (String, Int) -> Unit, ) { - val tags = state.tags LazyColumn( state = lazyListState, contentPadding = paddingValues, verticalArrangement = Arrangement.spacedBy(8.dp), ) { - itemsIndexed(tags) { index, tag -> + itemsIndexed(tags, key = { _, tag -> tag }) { index, tag -> SortTagListItem( modifier = Modifier.animateItemPlacement(), tag = tag, @@ -33,7 +31,7 @@ fun SortTagContent( canMoveDown = index != tags.lastIndex, onMoveUp = { onMoveUp(tag, index) }, onMoveDown = { onMoveDown(tag, index) }, - onDelete = { state.dialog = SortTagPresenter.Dialog.Delete(tag) }, + onDelete = { onClickDelete(tag) }, ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagController.kt index 377f84f52..6120eca4c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagController.kt @@ -1,21 +1,20 @@ package eu.kanade.tachiyomi.ui.category.genre import androidx.compose.runtime.Composable -import eu.kanade.presentation.category.SortTagScreen -import eu.kanade.tachiyomi.ui.base.controller.FullComposeController +import androidx.compose.runtime.CompositionLocalProvider +import cafe.adriel.voyager.navigator.Navigator +import eu.kanade.presentation.util.LocalRouter +import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController /** * Controller to manage the categories for the users' library. */ -class SortTagController : FullComposeController() { - - override fun createPresenter() = SortTagPresenter() +class SortTagController : BasicFullComposeController() { @Composable override fun ComposeContent() { - SortTagScreen( - presenter = presenter, - navigateUp = router::popCurrentController, - ) + CompositionLocalProvider(LocalRouter provides router) { + Navigator(screen = SortTagScreen()) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagPresenter.kt deleted file mode 100644 index fa1bed38e..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagPresenter.kt +++ /dev/null @@ -1,85 +0,0 @@ -package eu.kanade.tachiyomi.ui.category.genre - -import android.os.Bundle -import eu.kanade.domain.manga.interactor.CreateSortTag -import eu.kanade.domain.manga.interactor.DeleteSortTag -import eu.kanade.domain.manga.interactor.GetSortTag -import eu.kanade.domain.manga.interactor.ReorderSortTag -import eu.kanade.presentation.category.SortTagState -import eu.kanade.presentation.category.SortTagStateImpl -import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter -import eu.kanade.tachiyomi.util.lang.launchIO -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.consumeAsFlow -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -/** - * Presenter of [SortTagController]. Used to manage the categories of the library. - */ -class SortTagPresenter( - private val state: SortTagStateImpl = SortTagState() as SortTagStateImpl, - private val getSortTag: GetSortTag = Injekt.get(), - private val createSortTag: CreateSortTag = Injekt.get(), - private val deleteSortTag: DeleteSortTag = Injekt.get(), - private val reorderSortTag: ReorderSortTag = Injekt.get(), -) : BasePresenter(), SortTagState by state { - - private val _events: Channel = Channel(Int.MAX_VALUE) - val events = _events.consumeAsFlow() - - override fun onCreate(savedState: Bundle?) { - super.onCreate(savedState) - presenterScope.launchIO { - getSortTag.subscribe() - .collectLatest { - state.isLoading = false - state.tags = it - } - } - } - - fun createTag(name: String) { - presenterScope.launchIO { - when (createSortTag.await(name)) { - is CreateSortTag.Result.TagExists -> _events.send(Event.TagExists) - else -> {} - } - } - } - - fun delete(tag: String) { - presenterScope.launchIO { - deleteSortTag.await(tag) - } - } - - fun moveUp(tag: String, index: Int) { - presenterScope.launchIO { - when (reorderSortTag.await(tag, index - 1)) { - is ReorderSortTag.Result.InternalError -> _events.send(Event.InternalError) - else -> {} - } - } - } - - fun moveDown(tag: String, index: Int) { - presenterScope.launchIO { - when (reorderSortTag.await(tag, index + 1)) { - is ReorderSortTag.Result.InternalError -> _events.send(Event.InternalError) - else -> {} - } - } - } - - sealed class Event { - object TagExists : Event() - object InternalError : Event() - } - - sealed class Dialog { - object Create : Dialog() - data class Delete(val tag: String) : Dialog() - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagScreen.kt new file mode 100644 index 000000000..ef186b326 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagScreen.kt @@ -0,0 +1,74 @@ +package eu.kanade.tachiyomi.ui.category.genre + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.category.components.CategoryCreateDialog +import eu.kanade.presentation.category.components.CategoryDeleteDialog +import eu.kanade.presentation.components.LoadingScreen +import eu.kanade.presentation.util.LocalRouter +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.util.system.toast +import kotlinx.coroutines.flow.collectLatest + +class SortTagScreen : Screen { + + @Composable + override fun Content() { + val context = LocalContext.current + val router = LocalRouter.currentOrThrow + val screenModel = rememberScreenModel { SortTagScreenModel() } + + val state by screenModel.state.collectAsState() + + if (state is SortTagScreenState.Loading) { + LoadingScreen() + return + } + + val successState = state as SortTagScreenState.Success + + eu.kanade.presentation.category.SortTagScreen( + state = successState, + onClickCreate = { screenModel.showDialog(SortTagDialog.Create) }, + onClickDelete = { screenModel.showDialog(SortTagDialog.Delete(it)) }, + onClickMoveUp = screenModel::moveUp, + onClickMoveDown = screenModel::moveDown, + navigateUp = router::popCurrentController, + ) + + when (val dialog = successState.dialog) { + null -> {} + SortTagDialog.Create -> { + CategoryCreateDialog( + onDismissRequest = screenModel::dismissDialog, + onCreate = { screenModel.createTag(it) }, + title = stringResource(R.string.add_tag), + extraMessage = stringResource(R.string.action_add_tags_message), + ) + } + is SortTagDialog.Delete -> { + CategoryDeleteDialog( + onDismissRequest = screenModel::dismissDialog, + onDelete = { screenModel.delete(dialog.tag) }, + title = stringResource(R.string.delete_tag), + text = stringResource(R.string.delete_tag_confirmation, dialog.tag), + ) + } + } + + LaunchedEffect(Unit) { + screenModel.events.collectLatest { event -> + if (event is SortTagEvent.LocalizedMessage) { + context.toast(event.stringRes) + } + } + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagScreenModel.kt new file mode 100644 index 000000000..808786250 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/genre/SortTagScreenModel.kt @@ -0,0 +1,123 @@ +package eu.kanade.tachiyomi.ui.category.genre + +import androidx.annotation.StringRes +import androidx.compose.runtime.Immutable +import cafe.adriel.voyager.core.model.StateScreenModel +import cafe.adriel.voyager.core.model.coroutineScope +import eu.kanade.domain.manga.interactor.CreateSortTag +import eu.kanade.domain.manga.interactor.DeleteSortTag +import eu.kanade.domain.manga.interactor.GetSortTag +import eu.kanade.domain.manga.interactor.ReorderSortTag +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.util.lang.launchIO +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.update +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +/** + * Presenter of [SortTagController]. Used to manage the categories of the library. + */ +class SortTagScreenModel( + private val getSortTag: GetSortTag = Injekt.get(), + private val createSortTag: CreateSortTag = Injekt.get(), + private val deleteSortTag: DeleteSortTag = Injekt.get(), + private val reorderSortTag: ReorderSortTag = Injekt.get(), +) : StateScreenModel(SortTagScreenState.Loading) { + + private val _events: Channel = Channel(Int.MAX_VALUE) + val events = _events.consumeAsFlow() + + init { + coroutineScope.launchIO { + getSortTag.subscribe() + .collectLatest { tags -> + mutableState.update { + SortTagScreenState.Success( + tags = tags, + ) + } + } + } + } + + fun createTag(name: String) { + coroutineScope.launchIO { + when (createSortTag.await(name)) { + is CreateSortTag.Result.TagExists -> _events.send(SortTagEvent.TagExists) + else -> {} + } + } + } + + fun delete(tag: String) { + coroutineScope.launchIO { + deleteSortTag.await(tag) + } + } + + fun moveUp(tag: String, index: Int) { + coroutineScope.launchIO { + when (reorderSortTag.await(tag, index - 1)) { + is ReorderSortTag.Result.InternalError -> _events.send(SortTagEvent.InternalError) + else -> {} + } + } + } + + fun moveDown(tag: String, index: Int) { + coroutineScope.launchIO { + when (reorderSortTag.await(tag, index + 1)) { + is ReorderSortTag.Result.InternalError -> _events.send(SortTagEvent.InternalError) + else -> {} + } + } + } + + fun showDialog(dialog: SortTagDialog) { + mutableState.update { + when (it) { + SortTagScreenState.Loading -> it + is SortTagScreenState.Success -> it.copy(dialog = dialog) + } + } + } + + fun dismissDialog() { + mutableState.update { + when (it) { + SortTagScreenState.Loading -> it + is SortTagScreenState.Success -> it.copy(dialog = null) + } + } + } +} + +sealed class SortTagEvent { + sealed class LocalizedMessage(@StringRes val stringRes: Int) : SortTagEvent() + object TagExists : LocalizedMessage(R.string.error_tag_exists) + object InternalError : LocalizedMessage(R.string.internal_error) +} + +sealed class SortTagDialog { + object Create : SortTagDialog() + data class Delete(val tag: String) : SortTagDialog() +} + +sealed class SortTagScreenState { + + @Immutable + object Loading : SortTagScreenState() + + @Immutable + data class Success( + val tags: List, + val dialog: SortTagDialog? = null, + ) : SortTagScreenState() { + + val isEmpty: Boolean + get() = tags.isEmpty() + } +}