Skip to content

Commit

Permalink
✨ スケジュール一覧画面を実装
Browse files Browse the repository at this point in the history
  • Loading branch information
tatsutakein committed Nov 4, 2023
1 parent 6103f75 commit a190b5c
Show file tree
Hide file tree
Showing 12 changed files with 368 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package club.nito.core.common

import kotlinx.datetime.Instant
import kotlinx.datetime.toJavaInstant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Locale

sealed interface NitoDateTimeFormatter {
/**
Expand All @@ -21,3 +24,10 @@ class DefaultNitoDateTimeFormatter(

private fun DateTimeFormatter.format(instant: Instant): String = format(instant.toJavaInstant())
}

val previewNitoDateTimeFormatter = DefaultNitoDateTimeFormatter(
dateTimeFormatter = DateTimeFormatter
.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT)
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault()),
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ data object FakeParticipantRemoteDataSource : ParticipantRemoteDataSource {
}.map(NetworkParticipant::toParticipant)
}

override suspend fun getParticipants(scheduleIds: List<String>): List<Participant> {
delay(1000)
return (1..10).map {
createFakeNetworkParticipant(
userId = it.toString(),
)
}.map(NetworkParticipant::toParticipant)
}

override suspend fun participate(declaration: ParticipantDeclaration): Long {
delay(1000)
return DEFAULT_CHANGED_COUNT
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package club.nito.feature.top.component
package club.nito.core.ui

import androidx.compose.material3.AlertDialog
import androidx.compose.material3.MaterialTheme
Expand All @@ -10,7 +10,7 @@ import club.nito.core.designsystem.component.Text
import club.nito.core.domain.model.ParticipantSchedule

@Composable
internal fun ConfirmParticipateDialog(
fun ConfirmParticipateDialog(
schedule: ParticipantSchedule,
dateTimeFormatter: NitoDateTimeFormatter,
onParticipateRequest: (schedule: ParticipantSchedule) -> Unit,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package club.nito.core.ui

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
Expand All @@ -18,9 +16,7 @@ fun ProfileImagesRow(
modifier: Modifier = Modifier,
) {
LazyRow(
modifier = modifier
.fillMaxWidth()
.padding(8.dp),
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy((-24).dp, Alignment.End),
reverseLayout = true,
verticalAlignment = Alignment.CenterVertically,
Expand Down
2 changes: 2 additions & 0 deletions feature/schedule/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ kotlin {
sourceSets {
commonMain {
dependencies {
implementation(projects.core.common)
implementation(projects.core.model)
implementation(projects.core.domain)
implementation(projects.core.ui)
implementation(projects.core.designsystem)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package club.nito.feature.schedule

sealed class ScheduleListEvent {
data class NavigateToScheduleDetail(val scheduleId: String) : ScheduleListEvent()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package club.nito.feature.schedule

import club.nito.core.domain.model.ParticipantSchedule

sealed class ScheduleListIntent {
data class ClickShowConfirmParticipateDialog(val schedule: ParticipantSchedule) : ScheduleListIntent()
data class ClickParticipateSchedule(val schedule: ParticipantSchedule) : ScheduleListIntent()
data object ClickDismissConfirmParticipateDialog : ScheduleListIntent()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package club.nito.feature.schedule

import club.nito.core.common.NitoDateTimeFormatter
import club.nito.core.domain.model.ParticipantSchedule
import club.nito.core.model.FetchMultipleContentResult

data class ScheduleListScreenUiState(
val dateTimeFormatter: NitoDateTimeFormatter,
val scheduleList: FetchMultipleContentResult<ParticipantSchedule>,
val confirmParticipateDialog: ConfirmParticipateDialogUiState,
)

sealed class ConfirmParticipateDialogUiState {
data class Show(val schedule: ParticipantSchedule) : ConfirmParticipateDialogUiState()
data object Hide : ConfirmParticipateDialogUiState()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package club.nito.feature.schedule

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import club.nito.core.common.NitoDateTimeFormatter
import club.nito.core.domain.GetParticipantScheduleListUseCase
import club.nito.core.domain.model.ParticipantSchedule
import club.nito.core.model.FetchMultipleContentResult
import club.nito.core.ui.buildUiState
import club.nito.core.ui.message.UserMessageStateHolder
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class ScheduleListViewModel @Inject constructor(
getParticipantScheduleListUseCase: GetParticipantScheduleListUseCase,
val userMessageStateHolder: UserMessageStateHolder,
private val dateTimeFormatter: NitoDateTimeFormatter,
) : ViewModel(),
UserMessageStateHolder by userMessageStateHolder {
private val showConfirmParticipateSchedule = MutableStateFlow<ParticipantSchedule?>(null)

private val scheduleList = getParticipantScheduleListUseCase().stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = FetchMultipleContentResult.Loading,
)

val uiState: StateFlow<ScheduleListScreenUiState> = buildUiState(
showConfirmParticipateSchedule,
scheduleList,
) { showConfirmParticipateSchedule, scheduleList ->
ScheduleListScreenUiState(
dateTimeFormatter = dateTimeFormatter,
scheduleList = scheduleList,
confirmParticipateDialog = showConfirmParticipateSchedule
?.let(ConfirmParticipateDialogUiState::Show)
?: ConfirmParticipateDialogUiState.Hide,
)
}

private val _events = MutableStateFlow<List<ScheduleListEvent>>(emptyList())
val event: Flow<ScheduleListEvent?> = _events.map { it.firstOrNull() }

fun dispatch(intent: ScheduleListIntent) {
viewModelScope.launch {
when (intent) {
is ScheduleListIntent.ClickShowConfirmParticipateDialog -> showConfirmParticipateSchedule.emit(intent.schedule)
is ScheduleListIntent.ClickParticipateSchedule -> {
showConfirmParticipateSchedule.emit(null)

val scheduledAt = dateTimeFormatter.formatDateTimeString(intent.schedule.scheduledAt)
userMessageStateHolder.showMessage("$scheduledAt に参加登録しました 🎉")
}

ScheduleListIntent.ClickDismissConfirmParticipateDialog -> showConfirmParticipateSchedule.emit(null)
}
}
}

fun consume(event: ScheduleListEvent) {
viewModelScope.launch {
_events.emit(_events.value.filterNot { it == event })
}
}
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,124 @@
package club.nito.feature.schedule

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.hilt.navigation.compose.hiltViewModel
import club.nito.core.common.previewNitoDateTimeFormatter
import club.nito.core.designsystem.component.CenterAlignedTopAppBar
import club.nito.core.designsystem.component.Scaffold
import club.nito.core.designsystem.component.Text
import club.nito.core.designsystem.theme.NitoTheme
import club.nito.core.model.FetchMultipleContentResult
import club.nito.core.ui.ConfirmParticipateDialog
import club.nito.core.ui.message.SnackbarMessageEffect
import club.nito.feature.schedule.component.ScheduleListSection

@Composable
fun ScheduleRoute() {
ScheduleScreen()
fun ScheduleRoute(
viewModel: ScheduleListViewModel = hiltViewModel(),
) {
viewModel.event.collectAsState(initial = null).value?.let {
LaunchedEffect(it.hashCode()) {
when (it) {
is ScheduleListEvent.NavigateToScheduleDetail -> {}
}
viewModel.consume(it)
}
}

val uiState by viewModel.uiState.collectAsState()
val snackbarHostState = remember { SnackbarHostState() }

SnackbarMessageEffect(
snackbarHostState = snackbarHostState,
userMessageStateHolder = viewModel.userMessageStateHolder,
)

ScheduleScreen(
uiState = uiState,
snackbarHostState = snackbarHostState,
dispatch = viewModel::dispatch,
)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ScheduleScreen() {
private fun ScheduleScreen(
uiState: ScheduleListScreenUiState,
snackbarHostState: SnackbarHostState,
dispatch: (ScheduleListIntent) -> Unit = {},
) {
val confirmParticipateDialog = uiState.confirmParticipateDialog

Scaffold(
topBar = {
CenterAlignedTopAppBar(
title = {
Text(
text = "Schedule",
text = "スケジュール一覧",
)
},
)
},
content = { padding ->
Text(
text = "Schedule",
modifier = Modifier.padding(padding),
)
snackbarHost = { SnackbarHost(snackbarHostState) },
content = { innerPadding ->
if (confirmParticipateDialog is ConfirmParticipateDialogUiState.Show) {
ConfirmParticipateDialog(
schedule = confirmParticipateDialog.schedule,
dateTimeFormatter = uiState.dateTimeFormatter,
onParticipateRequest = { dispatch(ScheduleListIntent.ClickParticipateSchedule(it)) },
onDismissRequest = { dispatch(ScheduleListIntent.ClickDismissConfirmParticipateDialog) },
)
}

Column(
modifier = Modifier.padding(innerPadding),
) {
ScheduleListSection(
scheduleList = uiState.scheduleList,
dateTimeFormatter = uiState.dateTimeFormatter,
modifier = Modifier.fillMaxSize(),
onScheduleClick = { dispatch(ScheduleListIntent.ClickShowConfirmParticipateDialog(it)) },
)
}
},
)
}

private class ScheduleListScreenUiStatePreviewParameterProvider :
PreviewParameterProvider<ScheduleListScreenUiState> {
private val dateTimeFormatter = previewNitoDateTimeFormatter
override val values: Sequence<ScheduleListScreenUiState> = sequenceOf(
ScheduleListScreenUiState(
dateTimeFormatter = dateTimeFormatter,
scheduleList = FetchMultipleContentResult.Loading,
confirmParticipateDialog = ConfirmParticipateDialogUiState.Hide,
),
)
}

@Preview
@Composable
fun PreviewScheduleScreen(
@PreviewParameter(ScheduleListScreenUiStatePreviewParameterProvider::class) uiState: ScheduleListScreenUiState,
) {
NitoTheme {
ScheduleScreen(
uiState = uiState,
snackbarHostState = SnackbarHostState(),
)
}
}
Loading

0 comments on commit a190b5c

Please sign in to comment.