Implement shopping lists screen (#129)

* Initialize shopping lists feature

* Start shopping lists screen with Compose

* Add icon to shopping list name

* Add shopping lists to menu

* Set max size for the list

* Replace compose-adapter with accompanist

* Remove unused fields from shopping lists response

* Show list of shopping lists from BE

* Hide shopping lists if Mealie is 0.5.6

* Add shopping list item click listener

* Create material app theme for Compose

* Use shorter names

* Load shopping lists by pages and save to db

* Make page handling logic match recipes

* Add swipe to refresh to shopping lists

* Extract SwipeToRefresh Composable

* Make LazyPagingColumn generic

* Show refresh only when mediator is refreshing

* Do not refresh automatically

* Allow controlling Activity state from modules

* Implement navigating to shopping list screen

* Move Compose libraries setup to a plugin

* Implement loading full shopping list info

* Move Storage classes to database module

* Save shopping list items to DB

* Use separate names for separate ids

* Do only one DB version update

* Use unique names for all columns

* Display shopping list items

* Move OperationUiState to ui module

* Subscribe to shopping lists updates

* Indicate progress with progress bar

* Use strings from resources

* Format shopping list item quantities

* Hide unit/food/note/quantity if they are not set

* Implement updating shopping list item checked state

* Remove unnecessary null checks

* Disable checkbox when it is being updated

* Split shopping list screen into composables

* Show items immediately if they are saved

* Fix showing "list is empty" before the items

* Show Snackbar when error happens

* Reduce shopping list items paddings

* Remove shopping lists when URL is changed

* Add error/empty state handling to shopping lists

* Fix empty error state

* Fix tests compilation

* Add margin between text and button

* Add divider between checked and unchecked items

* Move divider to the item

* Refresh the shopping lists on authentication

* Use retry when necessary

* Remove excessive logging

* Fix pages bounds check

* Move FlowExtensionsTest

* Update Compose version

* Fix showing loading indicator for shopping lists

* Add Russian translation

* Fix SDK version lint check

* Rename parameter to match interface

* Add DB migration TODO

* Get rid of DB migrations

* Do not use pagination with shopping lists

* Cleanup after the pagination removal

* Load shopping list items

* Remove shopping lists storage

* Rethrow CancellationException in LoadingHelper

* Add pull-to-refresh on shopping list screen

* Extract LazyColumnWithLoadingState

* Split refresh errors and loading state

* Reuse LazyColumnWithLoadingState for shopping list items

* Remove paging-compose dependency

* Refresh shopping list items on authentication

* Disable missing translation lint check

* Update Compose and Kotlin versions

* Fix order of checked items

* Hide useless information from a shopping list item
This commit is contained in:
Kirill Kamakin
2023-07-03 15:07:19 +02:00
committed by GitHub
parent a40f9a78ea
commit 1e5e727e92
157 changed files with 3360 additions and 3715 deletions

1
ui/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

22
ui/build.gradle.kts Normal file
View File

@@ -0,0 +1,22 @@
plugins {
id("gq.kirmanak.mealient.library")
id("kotlin-kapt")
id("dagger.hilt.android.plugin")
}
android {
namespace = "gq.kirmanak.mealient.ui"
}
dependencies {
implementation(libs.google.dagger.hiltAndroid)
kapt(libs.google.dagger.hiltCompiler)
kaptTest(libs.google.dagger.hiltAndroidCompiler)
testImplementation(libs.google.dagger.hiltAndroidTesting)
testImplementation(libs.androidx.test.junit)
testImplementation(libs.google.truth)
testImplementation(libs.io.mockk)
}

View File

@@ -0,0 +1,21 @@
package gq.kirmanak.mealient.ui
data class ActivityUiState(
val isAuthorized: Boolean = false,
val navigationVisible: Boolean = false,
val searchVisible: Boolean = false,
val checkedMenuItem: CheckableMenuItem? = null,
val v1MenuItemsVisible: Boolean = false,
) {
val canShowLogin: Boolean get() = !isAuthorized
val canShowLogout: Boolean get() = isAuthorized
}
enum class CheckableMenuItem {
ShoppingLists,
RecipesList,
AddRecipe,
ChangeUrl,
Login
}

View File

@@ -0,0 +1,12 @@
package gq.kirmanak.mealient.ui
import kotlinx.coroutines.flow.StateFlow
interface ActivityUiStateController {
fun updateUiState(update: (ActivityUiState) -> ActivityUiState)
fun getUiState(): ActivityUiState
fun getUiStateFlow(): StateFlow<ActivityUiState>
}

View File

@@ -0,0 +1,21 @@
package gq.kirmanak.mealient.ui
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.getAndUpdate
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ActivityUiStateControllerImpl @Inject constructor() : ActivityUiStateController {
private val uiState = MutableStateFlow(ActivityUiState())
override fun updateUiState(update: (ActivityUiState) -> ActivityUiState) {
uiState.getAndUpdate(update)
}
override fun getUiState(): ActivityUiState = uiState.value
override fun getUiStateFlow(): StateFlow<ActivityUiState> = uiState.asStateFlow()
}

View File

@@ -0,0 +1,61 @@
package gq.kirmanak.mealient.ui
import android.widget.Button
import android.widget.ProgressBar
import androidx.core.view.isVisible
sealed class OperationUiState<T> {
val exceptionOrNull: Throwable?
get() = (this as? Failure)?.exception
val isSuccess: Boolean
get() = this is Success
val isProgress: Boolean
get() = this is Progress
val isFailure: Boolean
get() = this is Failure
fun updateButtonState(button: Button) {
button.isEnabled = !isProgress
button.isClickable = !isProgress
}
fun updateProgressState(progressBar: ProgressBar) {
progressBar.isVisible = isProgress
}
class Initial<T> : OperationUiState<T>() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return true
}
override fun hashCode(): Int {
return javaClass.hashCode()
}
}
class Progress<T> : OperationUiState<T>() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return true
}
override fun hashCode(): Int {
return javaClass.hashCode()
}
}
data class Failure<T>(val exception: Throwable) : OperationUiState<T>()
data class Success<T>(val value: T) : OperationUiState<T>()
companion object {
fun <T> fromResult(result: Result<T>) = result.fold({ Success(it) }, { Failure(it) })
}
}

View File

@@ -0,0 +1,16 @@
package gq.kirmanak.mealient.ui
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
interface UiModule {
@Binds
@Singleton
fun bindActivityUiStateController(impl: ActivityUiStateControllerImpl): ActivityUiStateController
}