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:
1
ui/.gitignore
vendored
Normal file
1
ui/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
22
ui/build.gradle.kts
Normal file
22
ui/build.gradle.kts
Normal 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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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) })
|
||||
}
|
||||
}
|
||||
16
ui/src/main/kotlin/gq/kirmanak/mealient/ui/UiModule.kt
Normal file
16
ui/src/main/kotlin/gq/kirmanak/mealient/ui/UiModule.kt
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user