-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SNAPSHOT dmvapp sample presentation layer
- Loading branch information
1 parent
e01d017
commit 75139f4
Showing
18 changed files
with
212 additions
and
132 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 29 additions & 40 deletions
69
...esentation/src/main/kotlin/tech/coner/trailer/toolkit/presentation/model/BaseItemModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,76 +1,65 @@ | ||
package tech.coner.trailer.toolkit.presentation.model | ||
|
||
import kotlin.reflect.KProperty1 | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.asStateFlow | ||
import kotlinx.coroutines.flow.map | ||
import kotlinx.coroutines.flow.update | ||
import tech.coner.trailer.toolkit.konstraints.CompositeConstraint | ||
import tech.coner.trailer.toolkit.konstraints.ConstraintViolationException | ||
import tech.coner.trailer.toolkit.validation.Feedback | ||
import tech.coner.trailer.toolkit.validation.ValidationResult | ||
import tech.coner.trailer.toolkit.validation.Validator | ||
|
||
abstract class BaseItemModel<I, C : CompositeConstraint<I>> : ItemModel<I> { | ||
abstract val constraints: C | ||
abstract class BaseItemModel<ITEM, VALIDATOR_CONTEXT, VALIDATOR_FEEDBACK> | ||
: ItemModel<ITEM, VALIDATOR_FEEDBACK> | ||
where VALIDATOR_FEEDBACK : Feedback { | ||
|
||
abstract val initialItem: I | ||
abstract val validator: Validator<VALIDATOR_CONTEXT, ITEM, VALIDATOR_FEEDBACK> | ||
abstract val validatorContext: VALIDATOR_CONTEXT | ||
|
||
abstract val initialItem: ITEM | ||
private val _itemFlow by lazy { MutableStateFlow(initialItem) } | ||
final override val itemFlow by lazy { _itemFlow.asStateFlow() } | ||
final override val item: I | ||
final override val item: ITEM | ||
get() = _itemFlow.value | ||
|
||
private val _pendingItemFlow by lazy { MutableStateFlow(initialItem) } | ||
final override val pendingItemFlow by lazy { _pendingItemFlow.asStateFlow() } | ||
final override var pendingItem: I | ||
final override var pendingItem: ITEM | ||
get() = pendingItemFlow.value | ||
set(value) = mutatePendingItem { value } | ||
private val _pendingItemValidationFlow by lazy { MutableStateFlow(emptyList<ValidationContent>()) } | ||
|
||
private val _pendingItemValidationFlow by lazy { | ||
MutableStateFlow<ValidationResult<ITEM, VALIDATOR_FEEDBACK>>( | ||
ValidationResult(emptyMap()) | ||
) | ||
} | ||
final override val pendingItemValidationFlow by lazy { _pendingItemValidationFlow.asStateFlow() } | ||
final override val pendingItemValidation get() = _pendingItemValidationFlow.value | ||
|
||
final override fun mutatePendingItem(forceValidate: Boolean?, mutatePendingItemFn: (I) -> I) { | ||
final override fun mutatePendingItem(forceValidate: Boolean?, mutatePendingItemFn: (ITEM) -> ITEM) { | ||
_pendingItemFlow.update { pending -> mutatePendingItemFn(pending) } | ||
if (forceValidate == true) { | ||
validate() | ||
} | ||
} | ||
|
||
override fun <P> validatedPropertyFlow(property: KProperty1<I, *>, fn: (I) -> P): Flow<Validated<P>> { | ||
val constraints = constraints.propertyConstraints[property] | ||
?: throw Exception("propertyConstraints does not contain any constraints for property: $property") | ||
return pendingItemFlow.map { item -> | ||
Validated( | ||
fn(item), | ||
constraints | ||
.mapNotNull { constraint -> constraint(item).exceptionOrNull() as ConstraintViolationException? } | ||
.map { Violation(it) } | ||
) | ||
} | ||
} | ||
|
||
final override val isPendingItemValid | ||
get() = _pendingItemValidationFlow.value.isValid() | ||
get() = _pendingItemValidationFlow.value.isValid | ||
|
||
final override val isPendingItemDirty | ||
get() = item != pendingItem | ||
|
||
final override fun validate(): List<ValidationContent> { | ||
return constraints.all | ||
.mapNotNull { it.invoke(pendingItem).exceptionOrNull() as? ConstraintViolationException } | ||
.map { ValidationContent.Error(it.message ?: "Invalid" /* TODO not hard-code english string */) } | ||
final override fun validate(): ValidationResult<ITEM, VALIDATOR_FEEDBACK> { | ||
return validator(validatorContext, pendingItem) | ||
.also { _pendingItemValidationFlow.value = it } | ||
} | ||
|
||
override fun commit(forceValidate: Boolean): Result<I> { | ||
if (forceValidate) { | ||
validate() | ||
.also { | ||
if (!it.isValid()) { | ||
return Result.failure(ModelNotReadyToCommitException()) | ||
} | ||
} | ||
override fun commit(requireValid: Boolean, successFn: (ITEM) -> Unit) { | ||
if (requireValid) { | ||
if (!validate().isValid) { | ||
return | ||
} | ||
} | ||
return pendingItem | ||
pendingItem | ||
.also { _itemFlow.value = it } | ||
.let { Result.success(it) } | ||
.also { successFn(it) } | ||
} | ||
} |
30 changes: 17 additions & 13 deletions
30
...n/presentation/src/main/kotlin/tech/coner/trailer/toolkit/presentation/model/ItemModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,27 @@ | ||
package tech.coner.trailer.toolkit.presentation.model | ||
|
||
import kotlin.reflect.KProperty1 | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.StateFlow | ||
import tech.coner.trailer.toolkit.validation.Feedback | ||
import tech.coner.trailer.toolkit.validation.ValidationResult | ||
|
||
interface ItemModel<ITEM, FEEDBACK : Feedback> : Model { | ||
val itemFlow: StateFlow<ITEM> | ||
val item: ITEM | ||
|
||
val pendingItemFlow: StateFlow<ITEM> | ||
var pendingItem: ITEM | ||
|
||
interface ItemModel<I> : Model { | ||
val itemFlow: StateFlow<I> | ||
val item: I | ||
val pendingItemFlow: StateFlow<I> | ||
var pendingItem: I | ||
val pendingItemValidationFlow: Flow<List<ValidationContent>> | ||
val pendingItemValidation: List<ValidationContent> | ||
val isPendingItemValid: Boolean | ||
val isPendingItemDirty: Boolean | ||
|
||
fun mutatePendingItem(forceValidate: Boolean? = null, mutatePendingItemFn: (I) -> I) | ||
fun <P> validatedPropertyFlow(property: KProperty1<I, *>, fn: (I) -> P): Flow<Validated<P>> | ||
val pendingItemValidationFlow: Flow<ValidationResult<ITEM, FEEDBACK>> | ||
val pendingItemValidation: ValidationResult<ITEM, FEEDBACK> | ||
|
||
val isPendingItemValid: Boolean | ||
|
||
fun mutatePendingItem(forceValidate: Boolean? = null, mutatePendingItemFn: (ITEM) -> ITEM) | ||
|
||
fun validate(): List<ValidationContent> | ||
fun validate(): ValidationResult<ITEM, FEEDBACK> | ||
|
||
fun commit(forceValidate: Boolean = true): Result<I> | ||
fun commit(requireValid: Boolean = true, successFn: (ITEM) -> Unit) | ||
} |
5 changes: 0 additions & 5 deletions
5
...in/kotlin/tech/coner/trailer/toolkit/presentation/model/ModelNotReadyToCommitException.kt
This file was deleted.
Oops, something went wrong.
6 changes: 0 additions & 6 deletions
6
...src/main/kotlin/tech/coner/trailer/toolkit/presentation/model/ModelValidationException.kt
This file was deleted.
Oops, something went wrong.
7 changes: 0 additions & 7 deletions
7
...n/presentation/src/main/kotlin/tech/coner/trailer/toolkit/presentation/model/Validated.kt
This file was deleted.
Oops, something went wrong.
28 changes: 0 additions & 28 deletions
28
...tation/src/main/kotlin/tech/coner/trailer/toolkit/presentation/model/ValidationContent.kt
This file was deleted.
Oops, something went wrong.
5 changes: 0 additions & 5 deletions
5
...n/presentation/src/main/kotlin/tech/coner/trailer/toolkit/presentation/model/Violation.kt
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
...src/main/kotlin/tech/coner/trailer/toolkit/presentation/presenter/SecondDraftPresenter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package tech.coner.trailer.toolkit.presentation.presenter | ||
|
||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.flow.asStateFlow | ||
import kotlinx.coroutines.flow.update | ||
import kotlinx.coroutines.sync.Mutex | ||
import kotlinx.coroutines.sync.withLock | ||
import tech.coner.trailer.toolkit.presentation.model.Model | ||
import tech.coner.trailer.toolkit.presentation.state.State | ||
|
||
abstract class SecondDraftPresenter<STATE> | ||
where STATE : State { | ||
|
||
abstract val initialState: STATE | ||
private val _stateMutex = Mutex() | ||
private val _stateFlow: MutableStateFlow<STATE> by lazy { MutableStateFlow(initialState) } | ||
val stateFlow: StateFlow<STATE> by lazy { _stateFlow.asStateFlow() } | ||
|
||
protected suspend fun update(reduceFn: (old: STATE) -> STATE) { | ||
_stateMutex.withLock { | ||
_stateFlow.update(reduceFn) | ||
} | ||
} | ||
|
||
interface WithArgument<ARGUMENT, ARGUMENT_MODEL : Model> { | ||
|
||
val argument: ARGUMENT | ||
val argumentModel: ARGUMENT_MODEL | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
...ch/coner/trailer/toolkit/sample/dmvapp/presentation/DriversLicenseApplicationPresenter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package tech.coner.trailer.toolkit.sample.dmvapp.presentation | ||
|
||
import tech.coner.trailer.toolkit.presentation.presenter.SecondDraftPresenter | ||
import tech.coner.trailer.toolkit.sample.dmvapp.presentation.model.DriversLicenseApplicationItemModel | ||
import tech.coner.trailer.toolkit.sample.dmvapp.presentation.model.DriversLicenseApplicationModel | ||
import tech.coner.trailer.toolkit.sample.dmvapp.presentation.state.DriversLicenseApplicationState | ||
|
||
class DriversLicenseApplicationPresenter( | ||
initialState: DriversLicenseApplicationState? = null | ||
) : SecondDraftPresenter<DriversLicenseApplicationState>() { | ||
|
||
override val initialState = initialState | ||
?: DriversLicenseApplicationState( | ||
model = DriversLicenseApplicationItemModel( | ||
|
||
) | ||
) | ||
} |
Oops, something went wrong.