Skip to content

Commit

Permalink
Merge pull request #173 from 2rabs/rt/refactor-schedule-detail
Browse files Browse the repository at this point in the history
♻️ スケジュール詳細画面のリファクタリング
  • Loading branch information
tatsutakein authored Jan 3, 2024
2 parents dd67966 + 7537bb7 commit d301543
Show file tree
Hide file tree
Showing 19 changed files with 444 additions and 271 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package club.nito.ios.combined
import club.nito.core.data.AuthRepository
import club.nito.core.data.ScheduleRepository
import club.nito.core.domain.AuthStatusStreamUseCase
import club.nito.core.domain.FetchMyParticipantStatusUseCase
import club.nito.core.domain.MyParticipantStatusStreamUseCase
import club.nito.core.domain.FetchParticipantScheduleByIdUseCase
import club.nito.core.domain.GetParticipantScheduleListUseCase
import club.nito.core.domain.GetRecentScheduleUseCase
Expand Down Expand Up @@ -45,6 +45,6 @@ class EntryPointTest {
assertNotNull(kmpEntryPoint.get<GetRecentScheduleUseCase>())
assertNotNull(kmpEntryPoint.get<GetParticipantScheduleListUseCase>())
assertNotNull(kmpEntryPoint.get<ParticipateUseCase>())
assertNotNull(kmpEntryPoint.get<FetchMyParticipantStatusUseCase>())
assertNotNull(kmpEntryPoint.get<MyParticipantStatusStreamUseCase>())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,10 @@ public struct ComposeScheduleDetailScreen: UIViewControllerRepresentable {
return ScheduleDetailScreen_iosKt.ScheduleDetailRouteViewController(
id: scheduleId,
stateMachine: ScheduleDetailStateMachine(
id: scheduleId,
fetchParticipantScheduleById: Container.shared.get(
type: FetchParticipantScheduleByIdUseCase.self
),
fetchMyParticipantStatus: Container.shared.get(
type: FetchMyParticipantStatusUseCase.self
),
scheduleId: scheduleId,
scheduleStream: Container.shared.get(type: ScheduleStreamUseCase.self),
scheduleParticipantsStream: Container.shared.get(type: ScheduleParticipantsStreamUseCase.self),
myParticipantStatusStream: Container.shared.get(type: MyParticipantStatusStreamUseCase.self),
participate: Container.shared.get(type: ParticipateUseCase.self),
userMessageStateHolder: Container.shared.get(type: UserMessageStateHolder.self),
dateTimeFormatter: Container.shared.get(type: CommonNitoDateFormatter.self)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,29 @@ public class DefaultParticipantRepository(
override suspend fun fetchParticipantStatus(scheduleId: ScheduleId, userId: String): ParticipantStatus =
remoteDataSource.fetchParticipantStatus(scheduleId = scheduleId, userId = userId)

override suspend fun insertParticipate(declaration: ParticipantDeclaration): Participant {
val participant = remoteDataSource.insertParticipate(declaration = declaration)
dao.upsert(participant)
return participant
}
override fun participantStatusStream(scheduleId: ScheduleId, userId: String): Flow<ParticipantStatus> =
dao.participantStatusStream(
scheduleId = scheduleId,
userId = userId,
)

override suspend fun upsertLocalParticipate(participant: Participant): Unit = dao.upsert(participant)

override suspend fun upsertParticipate(participant: Participant): Participant = remoteDataSource
.upsertParticipate(participant = participant)
.also {
dao.upsert(it)
}

override suspend fun updateParticipate(declaration: ParticipantDeclaration): Participant {
dao.upsert(
entity = Participant(
scheduleId = declaration.scheduleId,
userId = declaration.userId,
status = declaration.status,
),
)

val participant = remoteDataSource.updateParticipate(declaration = declaration)
dao.upsert(participant)
return participant
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,28 @@ public sealed interface ParticipantRepository {
*/
public suspend fun fetchParticipantStatus(scheduleId: ScheduleId, userId: String): ParticipantStatus

/**
* 該当の予定の対象のユーザーの参加情報を取得する
*
* @param scheduleId 参加情報を取得するスケジュールID
* @param userId 対象のユーザーID
* @return 参加情報
*/
public fun participantStatusStream(scheduleId: ScheduleId, userId: String): Flow<ParticipantStatus>

/**
* 該当スケジュールへの参加状況を追加する
*
* @param declaration 参加表明データ
* @param participant 参加表明データ
*/
public suspend fun upsertLocalParticipate(participant: Participant)

/**
* 該当スケジュールへの参加状況を追加する
*
* @param participant 参加表明データ
*/
public suspend fun insertParticipate(declaration: ParticipantDeclaration): Participant
public suspend fun upsertParticipate(participant: Participant): Participant

/**
* 該当スケジュールへの参加状況を更新する
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package club.nito.core.domain

import club.nito.core.data.AuthRepository
import club.nito.core.data.ParticipantRepository
import club.nito.core.model.participant.ParticipantStatus
import club.nito.core.model.schedule.ScheduleId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

/**
* 自身の参加状況を取得するユースケース
*/
public sealed interface MyParticipantStatusStreamUseCase {
public operator fun invoke(id: ScheduleId): Flow<ParticipantStatus>
}

public class FetchMyParticipantStatusExecutor(
private val authRepository: AuthRepository,
private val participantRepository: ParticipantRepository,
) : MyParticipantStatusStreamUseCase {
override fun invoke(id: ScheduleId): Flow<ParticipantStatus> = flow {
val currentUserId = authRepository.currentUser().id
participantRepository.participantStatusStream(
scheduleId = id,
userId = currentUserId,
).collect {
emit(it)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,66 @@ import club.nito.core.data.AuthRepository
import club.nito.core.data.ParticipantRepository
import club.nito.core.model.ExecuteResult
import club.nito.core.model.participant.Participant
import club.nito.core.model.participant.ParticipantDeclaration
import club.nito.core.model.participant.ParticipantStatus
import club.nito.core.model.runExecuting
import club.nito.core.model.toNitoError
import kotlin.coroutines.cancellation.CancellationException

/**
* 参加表明するユースケース
*/
public sealed interface ParticipateUseCase {
public suspend operator fun invoke(scheduleId: String, status: ParticipantStatus): ExecuteResult<Participant>
public suspend operator fun invoke(
scheduleId: String,
oldStatus: ParticipantStatus,
newStatus: ParticipantStatus,
): ExecuteResult<Participant>
}

public class ParticipateExecutor(
private val authRepository: AuthRepository,
private val participantRepository: ParticipantRepository,
) : ParticipateUseCase {
override suspend fun invoke(scheduleId: String, status: ParticipantStatus): ExecuteResult<Participant> = runExecuting {
override suspend fun invoke(
scheduleId: String,
oldStatus: ParticipantStatus,
newStatus: ParticipantStatus,
): ExecuteResult<Participant> {
val currentUserId = authRepository.currentUser().id
val exist = participantRepository.existParticipantByUserId(

// NOTE: 失敗時の復元用キャッシュ
val cachedParticipant = Participant(
scheduleId = scheduleId,
userId = currentUserId,
status = oldStatus,
)

if (exist) {
participantRepository.updateParticipate(
declaration = ParticipantDeclaration(
scheduleId = scheduleId,
userId = currentUserId,
status = status,
),
)
} else {
participantRepository.insertParticipate(
declaration = ParticipantDeclaration(
// NOTE: 成功可否に関わらず一旦選択した状態を反映する
participantRepository.upsertLocalParticipate(
participant = Participant(
scheduleId = scheduleId,
userId = currentUserId,
status = newStatus,
),
)

try {
val participant = participantRepository.upsertParticipate(
participant = Participant(
scheduleId = scheduleId,
userId = currentUserId,
status = status,
status = newStatus,
),
)
return ExecuteResult.Success(data = participant)
} catch (e: Exception) {
when (e) {
is CancellationException -> throw e
else -> {
// NOTE: 失敗時はキャッシュを復元する
participantRepository.upsertLocalParticipate(participant = cachedParticipant)
return ExecuteResult.Failure(error = e.toNitoError())
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package club.nito.core.domain

import club.nito.core.data.ParticipantRepository
import club.nito.core.model.FetchMultipleContentResult
import club.nito.core.model.participant.ParticipantUser
import club.nito.core.model.schedule.ScheduleId
import club.nito.core.model.toNitoError
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import kotlin.coroutines.cancellation.CancellationException

/**
* 該当スケジュールの参加者情報のストリームを取得するユースケース
*/
public sealed interface ScheduleParticipantsStreamUseCase {
public operator fun invoke(id: ScheduleId): Flow<FetchMultipleContentResult<ParticipantUser>>
}

public class ScheduleParticipantsStreamExecutor(
private val participantRepository: ParticipantRepository,
) : ScheduleParticipantsStreamUseCase {
override fun invoke(id: ScheduleId): Flow<FetchMultipleContentResult<ParticipantUser>> {
return participantRepository
.participantUsersStream(scheduleId = id)
.map {
if (it.isEmpty()) {
return@map FetchMultipleContentResult.NoContent
}

FetchMultipleContentResult.Success(it)
}
.catch { e ->
if (e is CancellationException) {
throw e
}

emit(FetchMultipleContentResult.Failure(e.toNitoError()))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package club.nito.core.domain

import club.nito.core.data.ScheduleRepository
import club.nito.core.model.FetchSingleContentResult
import club.nito.core.model.schedule.ScheduleId
import club.nito.core.model.schedule.ScheduleWithPlace
import club.nito.core.model.toNitoError
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import kotlin.coroutines.cancellation.CancellationException

/**
* スケジュールを取得するユースケース
*/
public sealed interface ScheduleStreamUseCase {
public operator fun invoke(id: ScheduleId): Flow<FetchSingleContentResult<ScheduleWithPlace>>
}

public class ScheduleStreamExecutor(
private val scheduleRepository: ScheduleRepository,
) : ScheduleStreamUseCase {
override fun invoke(id: ScheduleId): Flow<FetchSingleContentResult<ScheduleWithPlace>> {
return scheduleRepository
.scheduleWithPlaceStream(id = id)
.map {
it?.let { FetchSingleContentResult.Success(it) }
?: FetchSingleContentResult.NoContent
}
.catch { e ->
if (e is CancellationException) {
throw e
}

emit(FetchSingleContentResult.Failure(e.toNitoError()))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package club.nito.core.domain.di
import club.nito.core.domain.AuthStatusStreamExecutor
import club.nito.core.domain.AuthStatusStreamUseCase
import club.nito.core.domain.FetchMyParticipantStatusExecutor
import club.nito.core.domain.FetchMyParticipantStatusUseCase
import club.nito.core.domain.MyParticipantStatusStreamUseCase
import club.nito.core.domain.FetchParticipantScheduleByIdExecutor
import club.nito.core.domain.FetchParticipantScheduleByIdUseCase
import club.nito.core.domain.GetParticipantScheduleListExecutor
Expand All @@ -18,6 +18,10 @@ import club.nito.core.domain.ModifyPasswordExecutor
import club.nito.core.domain.ModifyPasswordUseCase
import club.nito.core.domain.ParticipateExecutor
import club.nito.core.domain.ParticipateUseCase
import club.nito.core.domain.ScheduleParticipantsStreamExecutor
import club.nito.core.domain.ScheduleParticipantsStreamUseCase
import club.nito.core.domain.ScheduleStreamExecutor
import club.nito.core.domain.ScheduleStreamUseCase
import org.koin.core.module.Module
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.bind
Expand All @@ -29,8 +33,10 @@ public val useCaseModule: Module = module {
singleOf(::ModifyPasswordExecutor) bind ModifyPasswordUseCase::class
singleOf(::LogoutExecutor) bind LogoutUseCase::class
singleOf(::FetchParticipantScheduleByIdExecutor) bind FetchParticipantScheduleByIdUseCase::class
singleOf(::ScheduleParticipantsStreamExecutor) bind ScheduleParticipantsStreamUseCase::class
singleOf(::ScheduleStreamExecutor) bind ScheduleStreamUseCase::class
singleOf(::GetRecentScheduleExecutor) bind GetRecentScheduleUseCase::class
singleOf(::GetParticipantScheduleListExecutor) bind GetParticipantScheduleListUseCase::class
singleOf(::ParticipateExecutor) bind ParticipateUseCase::class
singleOf(::FetchMyParticipantStatusExecutor) bind FetchMyParticipantStatusUseCase::class
singleOf(::FetchMyParticipantStatusExecutor) bind MyParticipantStatusStreamUseCase::class
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ public data object FakeParticipantRemoteDataSource : ParticipantRemoteDataSource
return ParticipantStatus.ATTENDANCE
}

override suspend fun insertParticipate(declaration: ParticipantDeclaration): Participant {
override suspend fun upsertParticipate(participant: Participant): Participant {
delay(1000)
return createFakeNetworkParticipant(
scheduleId = declaration.scheduleId,
userId = declaration.userId,
status = declaration.status.toNetworkModel(),
scheduleId = participant.scheduleId,
userId = participant.userId,
status = participant.status.toNetworkModel(),
).toParticipant()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ public sealed interface ParticipantRemoteDataSource {
/**
* 該当スケジュールへの参加状況を追加する
*
* @param declaration 参加表明データ
* @param participant 参加表明データ
*/
public suspend fun insertParticipate(declaration: ParticipantDeclaration): Participant
public suspend fun upsertParticipate(participant: Participant): Participant

/**
* 該当スケジュールへの参加状況を更新する
Expand Down
Loading

0 comments on commit d301543

Please sign in to comment.