Browse Source

Add Stable interface for Category state (#7539)

Andreas 2 years ago
parent
commit
a21aa8125e

+ 15 - 15
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 -> {

+ 28 - 0
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<Category>
+    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<Category> by mutableStateOf(emptyList())
+    override val isEmpty: Boolean by derivedStateOf { categories.isEmpty() }
+}

+ 12 - 6
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<Category>,
+    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) },
             )
         }
     }

+ 8 - 5
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 = "")
             }
         }

+ 17 - 8
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<CategoryController>() {
-
-    var dialog: Dialog? by mutableStateOf(null)
-
-    val categories = getCategories.subscribe()
+) : BasePresenter<CategoryController>(), CategoryState by state {
 
     private val _events: Channel<Event> = 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)) {