From a21aa8125e63b9e0d4377aa9c97d6614ee4acf7f Mon Sep 17 00:00:00 2001 From: Andreas Date: Fri, 15 Jul 2022 23:35:19 +0200 Subject: [PATCH] Add Stable interface for Category state (#7539) --- .../presentation/category/CategoryScreen.kt | 30 +++++++++---------- .../presentation/category/CategoryState.kt | 28 +++++++++++++++++ .../category/components/CategoryContent.kt | 18 +++++++---- .../category/components/CategoryListItem.kt | 13 ++++---- .../ui/category/CategoryPresenter.kt | 25 +++++++++++----- 5 files changed, 80 insertions(+), 34 deletions(-) create mode 100644 app/src/main/java/eu/kanade/presentation/category/CategoryState.kt diff --git a/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt b/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt index 8fa137553..df260cee8 100644 --- a/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt @@ -7,7 +7,6 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarScrollState 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.input.nestedscroll.nestedScroll @@ -19,6 +18,7 @@ import eu.kanade.presentation.category.components.CategoryFloatingActionButton import eu.kanade.presentation.category.components.CategoryRenameDialog import eu.kanade.presentation.category.components.CategoryTopAppBar 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 @@ -50,25 +50,25 @@ fun CategoryScreen( floatingActionButton = { CategoryFloatingActionButton( lazyListState = lazyListState, - onCreate = { presenter.dialog = CategoryPresenter.Dialog.Create }, + onCreate = { presenter.dialog = Dialog.Create }, ) }, ) { paddingValues -> val context = LocalContext.current - val categories by presenter.categories.collectAsState(initial = emptyList()) - if (categories.isEmpty()) { - EmptyScreen(textResource = R.string.information_empty_category) - } else { - CategoryContent( - categories = categories, - lazyListState = lazyListState, - paddingValues = paddingValues + topPaddingValues + PaddingValues(horizontal = horizontalPadding), - onMoveUp = { presenter.moveUp(it) }, - onMoveDown = { presenter.moveDown(it) }, - onRename = { presenter.dialog = Dialog.Rename(it) }, - onDelete = { presenter.dialog = Dialog.Delete(it) }, - ) + when { + presenter.isLoading -> LoadingScreen() + presenter.isEmpty -> EmptyScreen(textResource = R.string.information_empty_category) + else -> { + CategoryContent( + state = presenter, + lazyListState = lazyListState, + paddingValues = paddingValues + topPaddingValues + PaddingValues(horizontal = horizontalPadding), + onMoveUp = { presenter.moveUp(it) }, + onMoveDown = { presenter.moveDown(it) }, + ) + } } + val onDismissRequest = { presenter.dialog = null } when (val dialog = presenter.dialog) { Dialog.Create -> { diff --git a/app/src/main/java/eu/kanade/presentation/category/CategoryState.kt b/app/src/main/java/eu/kanade/presentation/category/CategoryState.kt new file mode 100644 index 000000000..5873f6bde --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/category/CategoryState.kt @@ -0,0 +1,28 @@ +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.domain.category.model.Category +import eu.kanade.tachiyomi.ui.category.CategoryPresenter + +@Stable +interface CategoryState { + val isLoading: Boolean + var dialog: CategoryPresenter.Dialog? + val categories: List + val isEmpty: Boolean +} + +fun CategoryState(): CategoryState { + return CategoryStateImpl() +} + +class CategoryStateImpl : CategoryState { + override var isLoading: Boolean by mutableStateOf(true) + override var dialog: CategoryPresenter.Dialog? by mutableStateOf(null) + override var categories: List by mutableStateOf(emptyList()) + override val isEmpty: Boolean by derivedStateOf { categories.isEmpty() } +} diff --git a/app/src/main/java/eu/kanade/presentation/category/components/CategoryContent.kt b/app/src/main/java/eu/kanade/presentation/category/components/CategoryContent.kt index 191609e42..0b0f24852 100644 --- a/app/src/main/java/eu/kanade/presentation/category/components/CategoryContent.kt +++ b/app/src/main/java/eu/kanade/presentation/category/components/CategoryContent.kt @@ -5,34 +5,40 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.lazy.LazyListState 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.domain.category.model.Category +import eu.kanade.presentation.category.CategoryState import eu.kanade.presentation.components.LazyColumn +import eu.kanade.tachiyomi.ui.category.CategoryPresenter.Dialog @Composable fun CategoryContent( - categories: List, + state: CategoryState, lazyListState: LazyListState, paddingValues: PaddingValues, onMoveUp: (Category) -> Unit, onMoveDown: (Category) -> Unit, - onRename: (Category) -> Unit, - onDelete: (Category) -> Unit, ) { + val categories = state.categories LazyColumn( state = lazyListState, contentPadding = paddingValues, verticalArrangement = Arrangement.spacedBy(8.dp), ) { - itemsIndexed(categories) { index, category -> + itemsIndexed( + items = categories, + key = { _, category -> category.id }, + ) { index, category -> CategoryListItem( + modifier = Modifier.animateItemPlacement(), category = category, canMoveUp = index != 0, canMoveDown = index != categories.lastIndex, onMoveUp = onMoveUp, onMoveDown = onMoveDown, - onRename = onRename, - onDelete = onDelete, + onRename = { state.dialog = Dialog.Rename(category) }, + onDelete = { state.dialog = Dialog.Delete(category) }, ) } } diff --git a/app/src/main/java/eu/kanade/presentation/category/components/CategoryListItem.kt b/app/src/main/java/eu/kanade/presentation/category/components/CategoryListItem.kt index ff0ed8807..21b192b72 100644 --- a/app/src/main/java/eu/kanade/presentation/category/components/CategoryListItem.kt +++ b/app/src/main/java/eu/kanade/presentation/category/components/CategoryListItem.kt @@ -21,15 +21,18 @@ import eu.kanade.presentation.util.horizontalPadding @Composable fun CategoryListItem( + modifier: Modifier = Modifier, category: Category, canMoveUp: Boolean, canMoveDown: Boolean, onMoveUp: (Category) -> Unit, onMoveDown: (Category) -> Unit, - onRename: (Category) -> Unit, - onDelete: (Category) -> Unit, + onRename: () -> Unit, + onDelete: () -> Unit, ) { - ElevatedCard { + ElevatedCard( + modifier = modifier, + ) { Row( modifier = Modifier .padding(start = horizontalPadding, top = horizontalPadding, end = horizontalPadding), @@ -52,10 +55,10 @@ fun CategoryListItem( Icon(imageVector = Icons.Outlined.ArrowDropDown, contentDescription = "") } Spacer(modifier = Modifier.weight(1f)) - IconButton(onClick = { onRename(category) }) { + IconButton(onClick = onRename) { Icon(imageVector = Icons.Outlined.Edit, contentDescription = "") } - IconButton(onClick = { onDelete(category) }) { + IconButton(onClick = onDelete) { Icon(imageVector = Icons.Outlined.Delete, contentDescription = "") } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt index 66926bb07..b24c81ec9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt @@ -1,36 +1,45 @@ package eu.kanade.tachiyomi.ui.category -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue +import android.os.Bundle import eu.kanade.domain.category.interactor.CreateCategoryWithName import eu.kanade.domain.category.interactor.DeleteCategory import eu.kanade.domain.category.interactor.GetCategories import eu.kanade.domain.category.interactor.RenameCategory import eu.kanade.domain.category.interactor.ReorderCategory import eu.kanade.domain.category.model.Category +import eu.kanade.presentation.category.CategoryState +import eu.kanade.presentation.category.CategoryStateImpl 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 class CategoryPresenter( + private val state: CategoryStateImpl = CategoryState() as CategoryStateImpl, private val getCategories: GetCategories = Injekt.get(), private val createCategoryWithName: CreateCategoryWithName = Injekt.get(), private val renameCategory: RenameCategory = Injekt.get(), private val reorderCategory: ReorderCategory = Injekt.get(), private val deleteCategory: DeleteCategory = Injekt.get(), -) : BasePresenter() { - - var dialog: Dialog? by mutableStateOf(null) - - val categories = getCategories.subscribe() +) : BasePresenter(), CategoryState by state { private val _events: Channel = Channel(Int.MAX_VALUE) val events = _events.consumeAsFlow() + override fun onCreate(savedState: Bundle?) { + super.onCreate(savedState) + presenterScope.launchIO { + getCategories.subscribe() + .collectLatest { + state.isLoading = false + state.categories = it + } + } + } + fun createCategory(name: String) { presenterScope.launchIO { when (createCategoryWithName.await(name)) {