From 108083bf8ee2fbfb01eca9907c449eb7798aa519 Mon Sep 17 00:00:00 2001 From: Carlton Whitehead Date: Mon, 18 Nov 2024 19:47:04 -0500 Subject: [PATCH] SNAPSHOT Migrate Club entity in the domain and presentation layer --- .../util/ConerTrailerCliProcessFactory.kt | 2 +- .../admin/util/SubcommandArgumentsFactory.kt | 1 + .../kotlin/tech/coner/trailer/TestClubs.kt | 2 + core/pom.xml | 8 ++ .../main/kotlin/tech/coner/trailer/Policy.kt | 1 + .../coner/trailer/{ => domain/entity}/Club.kt | 2 +- .../trailer/domain/validation/ClubFeedback.kt | 18 +++++ .../domain/validation/ClubValidator.kt | 13 +++ .../io/constraint/ClubPersistConstraints.kt | 2 +- .../coner/trailer/io/mapper/ClubMapper.kt | 2 +- .../coner/trailer/io/mapper/PolicyMapper.kt | 2 +- .../coner/trailer/io/service/ClubService.kt | 22 +++-- .../presentation/adapter/ClubAdapters.kt | 25 ------ .../presentation/di/adapter/AdapterModule.kt | 6 +- .../di/presenter/PresenterModule.kt | 29 +++---- .../trailer/presentation/model/ClubModel.kt | 18 ----- .../model/club/ClubDetailItemModel.kt | 21 +++++ .../model/club/ClubDetailModel.kt | 7 ++ .../model/club/ClubDetailModelFeedback.kt | 16 ++++ .../model/club/ClubEntityModelAdapter.kt | 17 ++++ .../presenter/club/ClubDetailPresenter.kt | 80 +++++++++++++++++++ .../presenter/club/ClubPresenter.kt | 32 -------- .../club/SecondDraftClubPresenter.kt | 13 --- .../presentation/state/ClubPresenterState.kt | 10 --- .../state/club/ClubDetailState.kt | 12 +++ .../presentation/presenter/BasePresenter.kt | 6 -- .../presentation/presenter/Presenter.kt | 22 ----- .../presentation/state/LoadableState.kt | 9 ++- .../fooapp/domain/validation/FooFeedback.kt | 1 - 29 files changed, 232 insertions(+), 167 deletions(-) rename core/src/main/kotlin/tech/coner/trailer/{ => domain/entity}/Club.kt (73%) create mode 100644 core/src/main/kotlin/tech/coner/trailer/domain/validation/ClubFeedback.kt create mode 100644 core/src/main/kotlin/tech/coner/trailer/domain/validation/ClubValidator.kt delete mode 100644 presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/adapter/ClubAdapters.kt delete mode 100644 presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/ClubModel.kt create mode 100644 presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/club/ClubDetailItemModel.kt create mode 100644 presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/club/ClubDetailModel.kt create mode 100644 presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/club/ClubDetailModelFeedback.kt create mode 100644 presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/club/ClubEntityModelAdapter.kt create mode 100644 presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/presenter/club/ClubDetailPresenter.kt delete mode 100644 presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/presenter/club/ClubPresenter.kt delete mode 100644 presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/presenter/club/SecondDraftClubPresenter.kt delete mode 100644 presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/state/ClubPresenterState.kt create mode 100644 presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/state/club/ClubDetailState.kt delete mode 100644 toolkit/presentation/presentation/src/main/kotlin/tech/coner/trailer/toolkit/presentation/presenter/BasePresenter.kt delete mode 100644 toolkit/presentation/presentation/src/main/kotlin/tech/coner/trailer/toolkit/presentation/presenter/Presenter.kt diff --git a/admin/src/integration-test/kotlin/tech/coner/trailer/app/admin/util/ConerTrailerCliProcessFactory.kt b/admin/src/integration-test/kotlin/tech/coner/trailer/app/admin/util/ConerTrailerCliProcessFactory.kt index 7ee802f09..8dca0cd41 100644 --- a/admin/src/integration-test/kotlin/tech/coner/trailer/app/admin/util/ConerTrailerCliProcessFactory.kt +++ b/admin/src/integration-test/kotlin/tech/coner/trailer/app/admin/util/ConerTrailerCliProcessFactory.kt @@ -1,6 +1,6 @@ package tech.coner.trailer.app.admin.util -import tech.coner.trailer.Club +import tech.coner.trailer.domain.entity.Club import tech.coner.trailer.Event import tech.coner.trailer.Participant import tech.coner.trailer.Person diff --git a/admin/src/integration-test/kotlin/tech/coner/trailer/app/admin/util/SubcommandArgumentsFactory.kt b/admin/src/integration-test/kotlin/tech/coner/trailer/app/admin/util/SubcommandArgumentsFactory.kt index 62d218268..f75cc1855 100644 --- a/admin/src/integration-test/kotlin/tech/coner/trailer/app/admin/util/SubcommandArgumentsFactory.kt +++ b/admin/src/integration-test/kotlin/tech/coner/trailer/app/admin/util/SubcommandArgumentsFactory.kt @@ -5,6 +5,7 @@ import tech.coner.trailer.eventresults.EventResultsType import java.nio.file.Path import java.time.LocalDate import java.util.* +import tech.coner.trailer.domain.entity.Club class SubcommandArgumentsFactory( private val snoozleDir: Path, diff --git a/core-test/src/main/kotlin/tech/coner/trailer/TestClubs.kt b/core-test/src/main/kotlin/tech/coner/trailer/TestClubs.kt index 3df1e64c9..864fe054f 100644 --- a/core-test/src/main/kotlin/tech/coner/trailer/TestClubs.kt +++ b/core-test/src/main/kotlin/tech/coner/trailer/TestClubs.kt @@ -1,5 +1,7 @@ package tech.coner.trailer +import tech.coner.trailer.domain.entity.Club + object TestClubs { val lscc = Club( name = "Local Sports Car Club" diff --git a/core/pom.xml b/core/pom.xml index 382c99b2d..7a192552c 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -11,4 +11,12 @@ 4.0.0 core + + + + tech.coner.trailer + toolkit-validation + 0.1.0-SNAPSHOT + + \ No newline at end of file diff --git a/core/src/main/kotlin/tech/coner/trailer/Policy.kt b/core/src/main/kotlin/tech/coner/trailer/Policy.kt index 27da54665..3e2839d21 100644 --- a/core/src/main/kotlin/tech/coner/trailer/Policy.kt +++ b/core/src/main/kotlin/tech/coner/trailer/Policy.kt @@ -4,6 +4,7 @@ import tech.coner.trailer.eventresults.EventResultsType import tech.coner.trailer.eventresults.FinalScoreStyle import tech.coner.trailer.eventresults.PaxTimeStyle import java.util.* +import tech.coner.trailer.domain.entity.Club data class Policy( val id: UUID, diff --git a/core/src/main/kotlin/tech/coner/trailer/Club.kt b/core/src/main/kotlin/tech/coner/trailer/domain/entity/Club.kt similarity index 73% rename from core/src/main/kotlin/tech/coner/trailer/Club.kt rename to core/src/main/kotlin/tech/coner/trailer/domain/entity/Club.kt index 2b4ac8033..68d3c3336 100644 --- a/core/src/main/kotlin/tech/coner/trailer/Club.kt +++ b/core/src/main/kotlin/tech/coner/trailer/domain/entity/Club.kt @@ -1,4 +1,4 @@ -package tech.coner.trailer +package tech.coner.trailer.domain.entity data class Club( val name: String diff --git a/core/src/main/kotlin/tech/coner/trailer/domain/validation/ClubFeedback.kt b/core/src/main/kotlin/tech/coner/trailer/domain/validation/ClubFeedback.kt new file mode 100644 index 000000000..548d76632 --- /dev/null +++ b/core/src/main/kotlin/tech/coner/trailer/domain/validation/ClubFeedback.kt @@ -0,0 +1,18 @@ +package tech.coner.trailer.domain.validation + +import tech.coner.trailer.domain.entity.Club +import tech.coner.trailer.toolkit.validation.Feedback +import tech.coner.trailer.toolkit.validation.Severity + +sealed class ClubFeedback : Feedback { + + override val severity = Severity.Error + + data object NameMustNotBeBlank : ClubFeedback() { + override val property = Club::name + } + + data object NameMustNotExceedMaxLength : ClubFeedback() { + override val property = Club::name + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/tech/coner/trailer/domain/validation/ClubValidator.kt b/core/src/main/kotlin/tech/coner/trailer/domain/validation/ClubValidator.kt new file mode 100644 index 000000000..3f5cc4bf5 --- /dev/null +++ b/core/src/main/kotlin/tech/coner/trailer/domain/validation/ClubValidator.kt @@ -0,0 +1,13 @@ +package tech.coner.trailer.domain.validation + +import tech.coner.trailer.domain.entity.Club +import tech.coner.trailer.domain.validation.ClubFeedback.NameMustNotBeBlank +import tech.coner.trailer.domain.validation.ClubFeedback.NameMustNotExceedMaxLength +import tech.coner.trailer.toolkit.validation.Validator + +typealias ClubValidator = Validator + +fun ClubValidator() = Validator { + Club::name { name -> NameMustNotBeBlank.takeUnless { name.isNotBlank() } } + Club::name { name -> NameMustNotExceedMaxLength.takeUnless { name.length <= Club.NAME_MAX_LENGTH } } +} diff --git a/io/src/main/kotlin/tech/coner/trailer/io/constraint/ClubPersistConstraints.kt b/io/src/main/kotlin/tech/coner/trailer/io/constraint/ClubPersistConstraints.kt index 89bce8fd2..9efff2ef3 100644 --- a/io/src/main/kotlin/tech/coner/trailer/io/constraint/ClubPersistConstraints.kt +++ b/io/src/main/kotlin/tech/coner/trailer/io/constraint/ClubPersistConstraints.kt @@ -1,6 +1,6 @@ package tech.coner.trailer.io.constraint -import tech.coner.trailer.Club +import tech.coner.trailer.domain.entity.Club import tech.coner.trailer.toolkit.konstraints.CompositeConstraint class ClubPersistConstraints : CompositeConstraint() { diff --git a/io/src/main/kotlin/tech/coner/trailer/io/mapper/ClubMapper.kt b/io/src/main/kotlin/tech/coner/trailer/io/mapper/ClubMapper.kt index 706ea2163..810a514f4 100644 --- a/io/src/main/kotlin/tech/coner/trailer/io/mapper/ClubMapper.kt +++ b/io/src/main/kotlin/tech/coner/trailer/io/mapper/ClubMapper.kt @@ -1,6 +1,6 @@ package tech.coner.trailer.io.mapper -import tech.coner.trailer.Club +import tech.coner.trailer.domain.entity.Club import tech.coner.trailer.datasource.snoozle.entity.ClubEntity class ClubMapper { diff --git a/io/src/main/kotlin/tech/coner/trailer/io/mapper/PolicyMapper.kt b/io/src/main/kotlin/tech/coner/trailer/io/mapper/PolicyMapper.kt index ef080410e..6a54e4bc5 100644 --- a/io/src/main/kotlin/tech/coner/trailer/io/mapper/PolicyMapper.kt +++ b/io/src/main/kotlin/tech/coner/trailer/io/mapper/PolicyMapper.kt @@ -28,7 +28,7 @@ class PolicyMapper( fun toCore(snoozle: PolicyEntity): Policy { return Policy( id = snoozle.id, - club = clubService.get(), + club = clubService.get().getOrThrow().getOrNull()!!, name = snoozle.name, conePenaltySeconds = snoozle.conePenaltySeconds, paxTimeStyle = PaxTimeStyle.valueOf(snoozle.paxTimeStyle), diff --git a/io/src/main/kotlin/tech/coner/trailer/io/service/ClubService.kt b/io/src/main/kotlin/tech/coner/trailer/io/service/ClubService.kt index 7d7f9b52c..0f3638071 100644 --- a/io/src/main/kotlin/tech/coner/trailer/io/service/ClubService.kt +++ b/io/src/main/kotlin/tech/coner/trailer/io/service/ClubService.kt @@ -1,7 +1,9 @@ package tech.coner.trailer.io.service +import arrow.core.Either +import arrow.core.raise.either import tech.coner.snoozle.db.entity.EntityIoException -import tech.coner.trailer.Club +import tech.coner.trailer.domain.entity.Club import tech.coner.trailer.datasource.snoozle.ClubResource import tech.coner.trailer.datasource.snoozle.entity.ClubEntity import tech.coner.trailer.io.constraint.ClubPersistConstraints @@ -13,16 +15,20 @@ class ClubService( private val mapper: ClubMapper ) { - fun get(): Club { - return try { - mapper.toCore(resource.read(ClubEntity.Key)) - } catch (notFoundException: EntityIoException.NotFound) { - throw NotFoundException(message = "Club not found.", cause = notFoundException) - } catch (readFailure: EntityIoException.ReadFailure) { - throw ReadException(message = "Failed to read contents of Club from storage.", cause = readFailure) + fun get(): Result> = runCatching { + either { + try { + mapper.toCore(resource.read(ClubEntity.Key)) + } catch (nFE: NotFoundException) { + raise(GetFailure.NotFound) + } } } + sealed interface GetFailure { + data object NotFound : GetFailure + } + fun createOrUpdate( name: String ): Result = runCatching { diff --git a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/adapter/ClubAdapters.kt b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/adapter/ClubAdapters.kt deleted file mode 100644 index 51bb8ab5d..000000000 --- a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/adapter/ClubAdapters.kt +++ /dev/null @@ -1,25 +0,0 @@ -package tech.coner.trailer.presentation.adapter - -import tech.coner.trailer.Club -import tech.coner.trailer.io.constraint.ClubPersistConstraints -import tech.coner.trailer.presentation.model.ClubModel - -class ClubNameStringFieldAdapter : tech.coner.trailer.presentation.library.adapter.StringFieldAdapter { - override operator fun invoke(model: Club): String { - return model.name - } -} - -class ClubModelAdapter( - val name: ClubNameStringFieldAdapter, - private val clubPersistConstraints: ClubPersistConstraints -) : tech.coner.trailer.presentation.library.adapter.Adapter { - override fun invoke(model: Club): ClubModel { - return ClubModel( - item = model, - constraints = clubPersistConstraints, - adapter = this - ) - } - -} \ No newline at end of file diff --git a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/di/adapter/AdapterModule.kt b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/di/adapter/AdapterModule.kt index 1976ebc33..31734497e 100644 --- a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/di/adapter/AdapterModule.kt +++ b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/di/adapter/AdapterModule.kt @@ -3,6 +3,7 @@ package tech.coner.trailer.presentation.di.adapter import org.kodein.di.* import tech.coner.trailer.* import tech.coner.trailer.di.DataSessionScope +import tech.coner.trailer.domain.entity.Club import tech.coner.trailer.io.model.PolicyCollection import tech.coner.trailer.presentation.adapter.* import tech.coner.trailer.presentation.adapter.eventresults.* @@ -31,11 +32,6 @@ val presentationAdapterModule = DI.Module("tech.coner.trailer.presentation.adapt bindSingleton { new(::ClassParentModelAdapter) } bindSingleton> { instance() } - // Club - bind { scoped(DataSessionScope).singleton { new(::ClubModelAdapter) } } - bind> { scoped(DataSessionScope).singleton { instance() } } - bindSingleton { new(::ClubNameStringFieldAdapter) } - // Event bindSingleton { new(::EventIdStringFieldAdapter) } bindSingleton { new(::EventNameStringFieldAdapter) } diff --git a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/di/presenter/PresenterModule.kt b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/di/presenter/PresenterModule.kt index b5cd2650f..d4d3442e6 100644 --- a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/di/presenter/PresenterModule.kt +++ b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/di/presenter/PresenterModule.kt @@ -1,34 +1,25 @@ package tech.coner.trailer.presentation.di.presenter -import org.kodein.di.* -import tech.coner.trailer.Club +import org.kodein.di.DI +import org.kodein.di.bind +import org.kodein.di.instance +import org.kodein.di.multiton +import org.kodein.di.scoped +import org.kodein.di.singleton import tech.coner.trailer.di.DataSessionScope import tech.coner.trailer.presentation.di.constraint.presentationConstraintModule -import tech.coner.trailer.presentation.library.adapter.LoadableItemAdapter -import tech.coner.trailer.presentation.library.presenter.Presenter -import tech.coner.trailer.presentation.library.presenter.PresenterCoroutineScope -import tech.coner.trailer.presentation.library.state.LoadableItem -import tech.coner.trailer.presentation.presenter.club.ClubPresenter -import tech.coner.trailer.presentation.presenter.club.SecondDraftClubPresenter +import tech.coner.trailer.presentation.presenter.club.ClubDetailPresenter import tech.coner.trailer.presentation.presenter.person.PersonDetailPresenter import tech.coner.trailer.presentation.presenter.run.EventRunLatestPresenter -import tech.coner.trailer.presentation.state.ClubPresenterState +import tech.coner.trailer.toolkit.presentation.presenter.PresenterCoroutineScope val presenterModule = DI.Module("tech.coner.trailer.presentation.presenter") { importOnce(presentationConstraintModule) // Club bind { - scoped(DataSessionScope).multiton { arg: Presenter.Argument.Nothing -> - ClubPresenter(arg, instance(), instance(), instance()) - } - } - bind { - scoped(DataSessionScope).multiton { initialState: ClubPresenterState -> - SecondDraftClubPresenter( - initialState = initialState, - adapter = tech.coner.trailer.presentation.library.adapter.LoadableItemAdapter(instance()) - ) + scoped(DataSessionScope).singleton { + ClubDetailPresenter() } } diff --git a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/ClubModel.kt b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/ClubModel.kt deleted file mode 100644 index db0dac4c7..000000000 --- a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/ClubModel.kt +++ /dev/null @@ -1,18 +0,0 @@ -package tech.coner.trailer.presentation.model - -import tech.coner.trailer.Club -import tech.coner.trailer.io.constraint.ClubPersistConstraints -import tech.coner.trailer.presentation.adapter.ClubModelAdapter -import tech.coner.trailer.presentation.library.model.BaseItemModel - -class ClubModel( - override val initialItem: Club, - override val constraints: ClubPersistConstraints, - private val adapter: ClubModelAdapter -) : BaseItemModel() { - - val nameValidated = validatedPropertyFlow(Club::name) { adapter.name(it) } - var name - get() = adapter.name(pendingItem) - set(value) = mutatePendingItem { it.copy(name = value) } -} \ No newline at end of file diff --git a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/club/ClubDetailItemModel.kt b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/club/ClubDetailItemModel.kt new file mode 100644 index 000000000..6291f5069 --- /dev/null +++ b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/club/ClubDetailItemModel.kt @@ -0,0 +1,21 @@ +package tech.coner.trailer.presentation.model.club + +import tech.coner.trailer.domain.validation.ClubValidator +import tech.coner.trailer.toolkit.presentation.model.BaseItemModel +import tech.coner.trailer.toolkit.validation.Validator + +class ClubDetailItemModel( + override val initialItem: ClubDetailModel, + private val adapter: ClubEntityModelAdapter = ClubEntityModelAdapter() +) : BaseItemModel() { + + override val validator: Validator = Validator { + input( + otherTypeValidator = ClubValidator(), + mapContextFn = {}, + mapInputFn = { adapter.modelToEntityAdapter(it) }, + mapFeedbackObjectFn = { ClubDetailModelFeedback(it) } + ) + } + override val validatorContext = Unit +} \ No newline at end of file diff --git a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/club/ClubDetailModel.kt b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/club/ClubDetailModel.kt new file mode 100644 index 000000000..153d955e0 --- /dev/null +++ b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/club/ClubDetailModel.kt @@ -0,0 +1,7 @@ +package tech.coner.trailer.presentation.model.club + +import tech.coner.trailer.toolkit.presentation.model.Model + +data class ClubDetailModel( + val name: String +) : Model \ No newline at end of file diff --git a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/club/ClubDetailModelFeedback.kt b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/club/ClubDetailModelFeedback.kt new file mode 100644 index 000000000..dd1372e8c --- /dev/null +++ b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/club/ClubDetailModelFeedback.kt @@ -0,0 +1,16 @@ +package tech.coner.trailer.presentation.model.club + +import tech.coner.trailer.domain.entity.Club +import tech.coner.trailer.domain.validation.ClubFeedback +import tech.coner.trailer.toolkit.validation.Feedback +import tech.coner.trailer.toolkit.validation.FeedbackDelegate +import tech.coner.trailer.toolkit.validation.adapter.propertyAdapterOf + +data class ClubDetailModelFeedback( + val source: ClubFeedback +) : Feedback by FeedbackDelegate( + feedback = source, + propertyAdapter = propertyAdapterOf( + Club::name to ClubDetailModel::name + ) +) diff --git a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/club/ClubEntityModelAdapter.kt b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/club/ClubEntityModelAdapter.kt new file mode 100644 index 000000000..834497536 --- /dev/null +++ b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/model/club/ClubEntityModelAdapter.kt @@ -0,0 +1,17 @@ +package tech.coner.trailer.presentation.model.club + +import tech.coner.trailer.domain.entity.Club +import tech.coner.trailer.toolkit.presentation.adapter.EntityModelAdapter + +class ClubEntityModelAdapter : EntityModelAdapter() { + override val entityToModelAdapter: (Club) -> ClubDetailModel = { + ClubDetailModel( + name = it.name + ) + } + override val modelToEntityAdapter: (ClubDetailModel) -> Club = { + Club( + name = it.name + ) + } +} \ No newline at end of file diff --git a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/presenter/club/ClubDetailPresenter.kt b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/presenter/club/ClubDetailPresenter.kt new file mode 100644 index 000000000..ef6fc0dff --- /dev/null +++ b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/presenter/club/ClubDetailPresenter.kt @@ -0,0 +1,80 @@ +package tech.coner.trailer.presentation.presenter.club + +import arrow.core.Either +import arrow.core.raise.either +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.StateFlow +import tech.coner.trailer.io.service.ClubService +import tech.coner.trailer.io.util.runSuspendCatching +import tech.coner.trailer.presentation.model.club.ClubDetailItemModel +import tech.coner.trailer.presentation.model.club.ClubDetailModel +import tech.coner.trailer.presentation.model.club.ClubEntityModelAdapter +import tech.coner.trailer.presentation.state.club.ClubDetailState +import tech.coner.trailer.toolkit.presentation.model.Loadable +import tech.coner.trailer.toolkit.presentation.model.whenLoadedSuccess +import tech.coner.trailer.toolkit.presentation.presenter.ItemModelPresenter +import tech.coner.trailer.toolkit.presentation.presenter.LoadablePresenter +import tech.coner.trailer.toolkit.presentation.presenter.PresenterCoroutineScope +import tech.coner.trailer.toolkit.presentation.presenter.StatefulPresenter +import tech.coner.trailer.toolkit.presentation.state.StateContainer +import tech.coner.trailer.toolkit.presentation.state.mutableLoadedProperty + +class ClubDetailPresenter( + initialState: ClubDetailState = ClubDetailState(loadable = Loadable.Empty()), + private val adapter: ClubEntityModelAdapter, + private val service: ClubService, + coroutineScope: PresenterCoroutineScope +) : LoadablePresenter, + StatefulPresenter, + ItemModelPresenter, + CoroutineScope by coroutineScope { + + private val stateContainer = StateContainer(initialState) + override val state: ClubDetailState get() = stateContainer.state + override val stateFlow: StateFlow get() = stateContainer.stateFlow + + override suspend fun load(): Deferred>> = coroutineScope { + async { + runSuspendCatching { + stateContainer.update { it.copy(loadable = Loadable.Loading()) } + either { + service.get().getOrThrow() + .map { club -> + ClubDetailItemModel( + initialItem = adapter.entityToModelAdapter(club), + adapter = adapter + ) + } + .onLeft { failure -> stateContainer.update { it.copy(loadable = Loadable.Loaded(Either.Left(failure))) } } + .bind() + + } + } + .onFailure { throwable -> + stateContainer.update { it.copy(loadable = Loadable.FailedExceptionally(throwable)) } + } + } + } + + + override suspend fun commit() { + state.loadable.whenLoadedSuccess { it.commit() } + } + + override suspend fun validate() { + state.loadable.whenLoadedSuccess { it.validate() } + } + + override suspend fun reset() { + state.loadable.whenLoadedSuccess { it.reset() } + } + + val name = stateContainer.mutableLoadedProperty( + getFn = { name }, + updateFn = { copy(name = it) }, + property = ClubDetailModel::name + ) +} \ No newline at end of file diff --git a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/presenter/club/ClubPresenter.kt b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/presenter/club/ClubPresenter.kt deleted file mode 100644 index 9375f1408..000000000 --- a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/presenter/club/ClubPresenter.kt +++ /dev/null @@ -1,32 +0,0 @@ -package tech.coner.trailer.presentation.presenter.club - -import kotlinx.coroutines.CoroutineScope -import tech.coner.trailer.Club -import tech.coner.trailer.io.service.ClubService -import tech.coner.trailer.io.util.runSuspendCatching -import tech.coner.trailer.presentation.adapter.ClubModelAdapter -import tech.coner.trailer.presentation.model.ClubModel -import tech.coner.trailer.presentation.library.presenter.BaseItemPresenter -import tech.coner.trailer.presentation.library.presenter.Presenter -import tech.coner.trailer.presentation.library.presenter.PresenterCoroutineScope - -class ClubPresenter( - override val argument: Presenter.Argument.Nothing, - coroutineScope: PresenterCoroutineScope, - private val service: ClubService, - override val adapter: ClubModelAdapter -) : BaseItemPresenter(), - CoroutineScope by coroutineScope { - - override val entityDefault = Club("") - - override suspend fun performLoad(): Result = runSuspendCatching { - service.get() - } - - suspend fun createOrUpdate() = runSuspendCatching { - service.createOrUpdate(name = itemModel.pendingItem.name).getOrThrow() - } -} - -typealias ClubPresenterFactory = (Presenter.Argument.Nothing) -> ClubPresenter \ No newline at end of file diff --git a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/presenter/club/SecondDraftClubPresenter.kt b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/presenter/club/SecondDraftClubPresenter.kt deleted file mode 100644 index b80ce05d6..000000000 --- a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/presenter/club/SecondDraftClubPresenter.kt +++ /dev/null @@ -1,13 +0,0 @@ -package tech.coner.trailer.presentation.presenter.club - -import tech.coner.trailer.Club -import tech.coner.trailer.presentation.library.adapter.LoadableItemAdapter -import tech.coner.trailer.presentation.library.presenter.LoadableItemPresenter -import tech.coner.trailer.presentation.model.ClubModel -import tech.coner.trailer.presentation.state.ClubPresenterState - -class SecondDraftClubPresenter( - override val initialState: ClubPresenterState, - override val adapter: tech.coner.trailer.presentation.library.adapter.LoadableItemAdapter -) : LoadableItemPresenter() { -} \ No newline at end of file diff --git a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/state/ClubPresenterState.kt b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/state/ClubPresenterState.kt deleted file mode 100644 index 60af7cc5c..000000000 --- a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/state/ClubPresenterState.kt +++ /dev/null @@ -1,10 +0,0 @@ -package tech.coner.trailer.presentation.state - -import tech.coner.trailer.Club -import tech.coner.trailer.presentation.library.state.LoadableItem -import tech.coner.trailer.presentation.library.state.LoadableItemState - -data class ClubPresenterState( - override val loadable: LoadableItem -) : LoadableItemState { -} \ No newline at end of file diff --git a/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/state/club/ClubDetailState.kt b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/state/club/ClubDetailState.kt new file mode 100644 index 000000000..dc2f664b4 --- /dev/null +++ b/presentation/presentation/src/main/kotlin/tech/coner/trailer/presentation/state/club/ClubDetailState.kt @@ -0,0 +1,12 @@ +package tech.coner.trailer.presentation.state.club + +import tech.coner.trailer.io.service.ClubService +import tech.coner.trailer.presentation.model.club.ClubDetailItemModel +import tech.coner.trailer.presentation.model.club.ClubDetailModel +import tech.coner.trailer.presentation.model.club.ClubDetailModelFeedback +import tech.coner.trailer.toolkit.presentation.model.Loadable +import tech.coner.trailer.toolkit.presentation.state.LoadableState + +data class ClubDetailState( + override val loadable: Loadable = Loadable.Empty() +) : LoadableState diff --git a/toolkit/presentation/presentation/src/main/kotlin/tech/coner/trailer/toolkit/presentation/presenter/BasePresenter.kt b/toolkit/presentation/presentation/src/main/kotlin/tech/coner/trailer/toolkit/presentation/presenter/BasePresenter.kt deleted file mode 100644 index b7a01d039..000000000 --- a/toolkit/presentation/presentation/src/main/kotlin/tech/coner/trailer/toolkit/presentation/presenter/BasePresenter.kt +++ /dev/null @@ -1,6 +0,0 @@ -package tech.coner.trailer.toolkit.presentation.presenter - -abstract class BasePresenter : Presenter { - - protected abstract val argument: ARGUMENT -} \ No newline at end of file diff --git a/toolkit/presentation/presentation/src/main/kotlin/tech/coner/trailer/toolkit/presentation/presenter/Presenter.kt b/toolkit/presentation/presentation/src/main/kotlin/tech/coner/trailer/toolkit/presentation/presenter/Presenter.kt deleted file mode 100644 index 6baae6303..000000000 --- a/toolkit/presentation/presentation/src/main/kotlin/tech/coner/trailer/toolkit/presentation/presenter/Presenter.kt +++ /dev/null @@ -1,22 +0,0 @@ -package tech.coner.trailer.toolkit.presentation.presenter - -@Deprecated("First draft presenter to be replaced with second draft presenter (naming TBD)") -interface Presenter { - - interface Argument { - object Nothing : Argument { - - 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() - } - } - } - - -} diff --git a/toolkit/presentation/presentation/src/main/kotlin/tech/coner/trailer/toolkit/presentation/state/LoadableState.kt b/toolkit/presentation/presentation/src/main/kotlin/tech/coner/trailer/toolkit/presentation/state/LoadableState.kt index 050e370b1..74965489f 100644 --- a/toolkit/presentation/presentation/src/main/kotlin/tech/coner/trailer/toolkit/presentation/state/LoadableState.kt +++ b/toolkit/presentation/presentation/src/main/kotlin/tech/coner/trailer/toolkit/presentation/state/LoadableState.kt @@ -1,14 +1,17 @@ package tech.coner.trailer.toolkit.presentation.state -import kotlinx.coroutines.flow.* +import kotlin.reflect.KProperty1 +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import tech.coner.trailer.toolkit.presentation.model.ItemModel import tech.coner.trailer.toolkit.presentation.model.Loadable import tech.coner.trailer.toolkit.presentation.model.letLoadedSuccess import tech.coner.trailer.toolkit.presentation.model.whenLoadedSuccess import tech.coner.trailer.toolkit.validation.Feedback -import kotlin.reflect.KProperty1 -interface LoadableState, FEEDBACK : Feedback>: State { +interface LoadableState, FEEDBACK : Feedback> : State { val loadable: Loadable } diff --git a/toolkit/samples/fooapp/fooapp-common/src/main/kotlin/tech/coner/trailer/toolkit/sample/fooapp/domain/validation/FooFeedback.kt b/toolkit/samples/fooapp/fooapp-common/src/main/kotlin/tech/coner/trailer/toolkit/sample/fooapp/domain/validation/FooFeedback.kt index b515b1bb5..a01b2abe6 100644 --- a/toolkit/samples/fooapp/fooapp-common/src/main/kotlin/tech/coner/trailer/toolkit/sample/fooapp/domain/validation/FooFeedback.kt +++ b/toolkit/samples/fooapp/fooapp-common/src/main/kotlin/tech/coner/trailer/toolkit/sample/fooapp/domain/validation/FooFeedback.kt @@ -3,7 +3,6 @@ package tech.coner.trailer.toolkit.sample.fooapp.domain.validation import tech.coner.trailer.toolkit.sample.fooapp.domain.entity.Foo import tech.coner.trailer.toolkit.validation.Feedback import tech.coner.trailer.toolkit.validation.Severity -import kotlin.reflect.KProperty1 sealed class FooFeedback : Feedback {