Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Product edit #4

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions buildSrc/src/main/java/BuildValues.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import org.gradle.api.JavaVersion
* Created by MD on 18.10.23.
*/
object BuildValues {
val minSdk = 23
val targetSdk = 33
val compileSdk = 33
const val minSdk = 23
const val targetSdk = 33
const val compileSdk = 33

val javaVersion : JavaVersion = JavaVersion.VERSION_1_8
val jvmTarget = "1.8"
val kotlinCompilerExtensionVersion = "1.2.0"
const val jvmTarget = "1.8"
const val kotlinCompilerExtensionVersion = "1.2.0"
}
53 changes: 27 additions & 26 deletions buildSrc/src/main/java/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,55 @@
* Created by MD on 18.10.23.
*/
object Versions {
val composeVersion = "1.1.1"
val koinVersion = "3.4.0"
val roomVersion = "2.5.1"
const val composeVersion = "1.1.1"
const val koinVersion = "3.4.0"
const val roomVersion = "2.5.1"
}

object Dependencies {
val core_ktx = "androidx.core:core-ktx:1.10.0"
val activity_compose = "androidx.activity:activity-compose:1.7.1"
const val core_ktx = "androidx.core:core-ktx:1.10.0"
const val activity_compose = "androidx.activity:activity-compose:1.7.1"

val lifecycle_runtime = "androidx.lifecycle:lifecycle-runtime-ktx:2.6.1"
const val lifecycle_runtime = "androidx.lifecycle:lifecycle-runtime-ktx:2.6.1"
/**
* LIFECYCLE AWARE FLOW COLLECTION (for lifecycle aware viewstate collection in compose)
*/
val lifecycle_runtime_compose = "androidx.lifecycle:lifecycle-runtime-compose:2.6.1"
const val lifecycle_runtime_compose = "androidx.lifecycle:lifecycle-runtime-compose:2.6.1"

// COMPOSE
val compose_ui = "androidx.compose.ui:ui:${Versions.composeVersion}"
const val compose_ui = "androidx.compose.ui:ui:${Versions.composeVersion}"
// val compose_ui_tooling_preview = "androidx.compose.ui:ui-tooling-preview:${Versions.composeVersion}"
val compose_ui_tooling_preview = "androidx.compose.ui:ui-tooling-preview:1.4.2"
val compose_material = "androidx.compose.material:material:${Versions.composeVersion}"
val compose_ui_tooling = "androidx.compose.ui:ui-tooling:${Versions.composeVersion}"
val compose_ui_test_manifest = "androidx.compose.ui:ui-test-manifest:${Versions.composeVersion}"
val compose_navigation = "androidx.navigation:navigation-compose:2.5.3"
val compose_ui_test_junit = "androidx.compose.ui:ui-test-junit4:${Versions.composeVersion}"

const val compose_ui_tooling_preview = "androidx.compose.ui:ui-tooling-preview:1.4.2"
const val compose_material = "androidx.compose.material:material:${Versions.composeVersion}"
const val compose_ui_tooling = "androidx.compose.ui:ui-tooling:${Versions.composeVersion}"
const val compose_ui_test_manifest = "androidx.compose.ui:ui-test-manifest:${Versions.composeVersion}"
const val compose_navigation = "androidx.navigation:navigation-compose:2.5.3"
const val compose_ui_test_junit = "androidx.compose.ui:ui-test-junit4:${Versions.composeVersion}"

/**
* IMMUTABLE COLLECTIONS FOR KOTLIN (for composable methods stability)
*/
val collections_immutable = "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.5"
const val collections_immutable = "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.5"

// KOIN
val koin_core = "io.insert-koin:koin-core:${Versions.koinVersion}"
val koin_navigation = "io.insert-koin:koin-androidx-navigation:${Versions.koinVersion}"
val koin_compose = "io.insert-koin:koin-androidx-compose:${Versions.koinVersion}"
val koin_junit = "io.insert-koin:koin-test-junit4:${Versions.koinVersion}"
const val koin_core = "io.insert-koin:koin-core:${Versions.koinVersion}"
const val koin_navigation = "io.insert-koin:koin-androidx-navigation:${Versions.koinVersion}"
const val koin_compose = "io.insert-koin:koin-androidx-compose:${Versions.koinVersion}"
const val koin_junit = "io.insert-koin:koin-test-junit4:${Versions.koinVersion}"

// ROOM
val room_runtime = "androidx.room:room-runtime:${Versions.roomVersion}"
val room_compiler = "androidx.room:room-compiler:${Versions.roomVersion}"
val room_room = "androidx.room:room-ktx:${Versions.roomVersion}"
const val room_runtime = "androidx.room:room-runtime:${Versions.roomVersion}"
const val room_compiler = "androidx.room:room-compiler:${Versions.roomVersion}"
const val room_room = "androidx.room:room-ktx:${Versions.roomVersion}"

/**
* DESUGARING (e.g. for LocalDate manipulation on API < 26)
*/
val desugar = "com.android.tools:desugar_jdk_libs:1.2.2"
const val desugar = "com.android.tools:desugar_jdk_libs:1.2.2"

// TESTS
val test_junit = "junit:junit:4.13.2"
val test_ext_junit = "androidx.test.ext:junit:1.1.5"
val test_espresso = "androidx.test.espresso:espresso-core:3.5.1"
const val test_junit = "junit:junit:4.13.2"
const val test_ext_junit = "androidx.test.ext:junit:1.1.5"
const val test_espresso = "androidx.test.espresso:espresso-core:3.5.1"
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,16 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch

/**
* Created by MD on 29.12.22.
*/
abstract class BaseViewModel<STATE : ViewState, EVENT : ViewEvent, COMMAND : ViewCommand>(val initialState: STATE) : ViewModel() {
abstract class BaseViewModel<STATE : ViewStateDTO, EVENT : ViewEvent, COMMAND : ViewCommand>(val initialState: STATE) : ViewModel() {

// todo - use explicit backing fields when project is switched to Kotlin 2.0, see https://github.com/Kotlin/KEEP/blob/explicit-backing-fields-re/proposals/explicit-backing-fields.md

Expand All @@ -29,8 +28,8 @@ abstract class BaseViewModel<STATE : ViewState, EVENT : ViewEvent, COMMAND : Vie
val eventChannel = Channel<EVENT>(Channel.UNLIMITED)

// For handling commands from VM to UI
private val _commandFlow = MutableSharedFlow<COMMAND>()
val commandFlow: Flow<COMMAND> = _commandFlow.asSharedFlow()
private val commandChannel = Channel<COMMAND>()
val commandFlow: Flow<COMMAND> = commandChannel.receiveAsFlow()

/**
* This is the job for all coroutines started by this ViewModel.
Expand Down Expand Up @@ -89,8 +88,10 @@ abstract class BaseViewModel<STATE : ViewState, EVENT : ViewEvent, COMMAND : Vie
/**
* Post a command to the UI.
*/
protected suspend fun sendCommand(command : COMMAND) {
_commandFlow.emit(command)
protected fun sendCommand(command : COMMAND) {
defaultScope.launch {
commandChannel.send(command)
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cz.damat.thebeercounter.commonUI.base

import androidx.annotation.StringRes


/**
* Created by MD on 08.11.23.
*/
sealed class State {
object Loading : State()
object Content : State()
data class Error(@StringRes val message: Int) : State()
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ import androidx.compose.runtime.Immutable
* Created by MD on 23.04.23.
*/
@Immutable
interface ViewState
interface ViewStateDTO
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package cz.damat.thebeercounter.commonUI.compose.component

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.size
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import cz.damat.thebeercounter.commonUI.base.State


/**
* Created by MD on 08.11.23.
*/
@Composable
fun StateWrapper(
modifier: Modifier = Modifier,
state: State,
customLoading: @Composable (BoxScope.() -> Unit)? = null,
customError: @Composable (BoxScope.() -> Unit)? = null,
content: @Composable () -> Unit,
) {
Box(modifier = modifier) {
when (state) {
State.Content -> content()
is State.Error -> customError?.invoke(this) ?: Error(message = stringResource(id = state.message))
State.Loading -> customLoading?.invoke(this) ?: Loading()
}
}
}

@Composable
private fun BoxScope.Loading() {
CircularProgressIndicator(
modifier = Modifier
.align(Alignment.Center)
.size(64.dp),
)
}

@Composable
private fun BoxScope.Error(
message: String,
) {
Text(
modifier = Modifier.align(Alignment.Center),
text = message
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import androidx.lifecycle.flowWithLifecycle
import cz.damat.thebeercounter.commonUI.base.BaseViewModel
import cz.damat.thebeercounter.commonUI.base.ViewCommand
import cz.damat.thebeercounter.commonUI.base.ViewEvent
import cz.damat.thebeercounter.commonUI.base.ViewState
import cz.damat.thebeercounter.commonUI.base.ViewStateDTO
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
Expand All @@ -25,7 +26,7 @@ import kotlinx.coroutines.plus
* Collects the [BaseViewModel.stateFlow] by using [collectAsStateWithLifecycle] and the [BaseViewModel.initialState].
*/
@Composable
fun <T : ViewState> BaseViewModel<T, *, *>.collectStateWithLifecycle(): State<T> {
fun <T : ViewStateDTO> BaseViewModel<T, *, *>.collectStateWithLifecycle(): State<T> {
return stateFlow.collectAsStateWithLifecycle(initialValue = this.initialState)
}

Expand Down Expand Up @@ -58,9 +59,7 @@ fun <T : ViewCommand> BaseViewModel<*, *, T>.collectCommand(
) {
val lifecycleOwner = LocalLifecycleOwner.current

LaunchedEffect(key1 = Unit) {
// unchanging key makes sure that the collection is started only once

LaunchedEffect(lifecycleOwner.lifecycle, commandFlow) {
commandFlow.flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED)
.onEach(block) // since the command flow in not a StateFlow the block is called only at the time something is posted to the flow.
.launchIn(this + baseCoroutineExceptionHandler)
Expand Down
4 changes: 4 additions & 0 deletions commonUI/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<string name="history">History</string>
<string name="more">More</string>

<string name="action_edit">Edit</string>
<string name="action_reset">Reset</string>
<string name="action_delete">Delete</string>
<string name="action_set_count">Set count</string>
Expand All @@ -17,11 +18,14 @@

<string name="ok">OK</string>
<string name="cancel">Cancel</string>
<string name="back">Back</string>
<string name="clear">Clear</string>
<string name="expand">Expand</string>
<string name="collapse">Collapse</string>

<string name="name">Name</string>
<string name="beer">Beer</string>
<string name="clear_all_confirm_message">Do you really want to delete all items?</string>
<string name="edit_product">Edit product</string>
<string name="error_product_not_found">Product not found</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import cz.damat.thebeercounter.commonlib.room.entity.HistoryItemType
import cz.damat.thebeercounter.commonlib.room.entity.INITIAL_ITEM_ID
import cz.damat.thebeercounter.commonlib.room.entity.Product
import cz.damat.thebeercounter.componentCounter.domain.repository.ProductRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext


/**
Expand All @@ -18,6 +20,10 @@ import kotlinx.coroutines.flow.first

class ProductRepositoryImpl(private val db: AppDatabase, private val productDao: ProductDao, private val historyItemDao: HistoryItemDao) : ProductRepository {

override suspend fun getProduct(id: Int): Product? {
return productDao.getProduct(id)
}

override fun getShownProductsFlow() = productDao.getProductsFlow(true)

//todo - use for allowing the user to delete currently not shown products
Expand Down Expand Up @@ -64,6 +70,26 @@ class ProductRepositoryImpl(private val db: AppDatabase, private val productDao:
}
}

override suspend fun updateProductNameAndSetCount(id: Int, name: String, count: Int) {
db.withTransaction {
productDao.getProduct(id)?.let { product ->
val oldCount = product.count
productDao.upsert(product.copy(name = name, count = count))

if (oldCount != count) {
historyItemDao.upsert(
HistoryItem(
productId = id,
oldCount = oldCount,
newCount = count,
type = HistoryItemType.MANUAL
)
)
}
}
}
}

override suspend fun hideProduct(id: Int) {
db.withTransaction {
productDao.getProduct(id)?.let { product ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import org.koin.core.component.KoinComponent

interface ProductRepository : KoinComponent {

suspend fun getProduct(id: Int): Product?

fun getShownProductsFlow(): Flow<List<Product>>

//todo - use for allowing the user to delete currently not shown products
Expand All @@ -23,6 +25,8 @@ interface ProductRepository : KoinComponent {

suspend fun setProductCount(id: Int, count: Int, type: HistoryItemType)

suspend fun updateProductNameAndSetCount(id: Int, name: String, count: Int)

suspend fun hideProduct(id: Int)

suspend fun clearAllAndAddInitialProduct(initialItemName: String)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cz.damat.thebeercounter.featureCounter

import cz.damat.thebeercounter.featureCounter.scene.counter.CounterScreenViewModel
import cz.damat.thebeercounter.featureCounter.scene.edit.EditScreenViewModel
import cz.damat.thebeercounter.featureCounter.scene.history.HistoryViewModel
import org.koin.android.ext.koin.androidApplication
import org.koin.androidx.viewmodel.dsl.viewModel
Expand All @@ -14,6 +15,11 @@ val featureCounterKoinModule = module {
viewModel {
CounterScreenViewModel(get(), androidApplication().resources)
}

viewModel { (productId: Int) ->
EditScreenViewModel(productId, get())
}

viewModel {
HistoryViewModel(get())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ sealed class CounterCommand : ViewCommand {
object ShowAddNewDialog : CounterCommand()
object PerformHapticFeedback : CounterCommand()
data class ShowSetCountDialog(val product: Product) : CounterCommand()
data class OpenEdit(val productId: Int) : CounterCommand()
}
Loading
Loading