From 095bc493518e6ade2737312a789300267f537f89 Mon Sep 17 00:00:00 2001 From: Giovanni Junseo Kim Date: Mon, 22 Apr 2024 14:00:25 +0900 Subject: [PATCH 1/7] Attendance screen compose setting (#676) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: implement NewAttendanceActivity * feat: implement NewAttendanceViewModel * chore: add compose-lifecycle dependency * feat: define AttendanceAction * feat: implement screens * feat: use SoptTheme in designsystem * chore: data class -> class로 변경 * chore: 람다 프로퍼티 이름 명시 * chore: SoptTheme darkTheme 기본값 사용 * chore: 필요없는 함수 제거 * chore: 구현 안 된 함수에 TODO 삽입 * chore: 동작하지 않는 Preview 제거 * chore: AttendanceAction 내 뷰모델 참조 제거 * chore: code format 변경 --- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 5 +- .../attendance/NewAttendanceActivity.kt | 20 +++++++ .../attendance/NewAttendanceViewModel.kt | 28 +++++++++ .../compose/AttendanceLoadingScreen.kt | 20 +++++++ .../attendance/compose/AttendanceRoute.kt | 58 +++++++++++++++++++ .../attendance/compose/AttendanceScreen.kt | 21 +++++++ .../attendance/model/AttendanceAction.kt | 5 ++ .../attendance/model/AttendanceUiState.kt | 11 ++++ gradle/libs.versions.toml | 1 + 10 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceActivity.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceLoadingScreen.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceScreen.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceAction.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceUiState.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 14776ca16..1e00da893 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -148,6 +148,7 @@ dependencies { implementation(libs.kotlin.coroutines.google.play) implementation(platform(libs.compose.bom)) implementation(libs.bundles.compose) + implementation(libs.compose.lifecycle) implementation(libs.startup) implementation(libs.swipe.refresh.layout) debugImplementation(libs.compose.ui.tooling) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5f00b1dca..1f4fb8f74 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -153,5 +153,8 @@ + - \ No newline at end of file + diff --git a/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceActivity.kt b/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceActivity.kt new file mode 100644 index 000000000..2b96b443e --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceActivity.kt @@ -0,0 +1,20 @@ +package org.sopt.official.feature.attendance + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import dagger.hilt.android.AndroidEntryPoint +import org.sopt.official.designsystem.SoptTheme +import org.sopt.official.feature.attendance.compose.AttendanceRoute + +@AndroidEntryPoint +class NewAttendanceActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + SoptTheme { + AttendanceRoute() + } + } + } +} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt b/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt new file mode 100644 index 000000000..4a179fa04 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt @@ -0,0 +1,28 @@ +package org.sopt.official.feature.attendance + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import org.sopt.official.domain.repository.attendance.AttendanceRepository +import org.sopt.official.feature.attendance.model.AttendanceUiState +import javax.inject.Inject + +@HiltViewModel +class NewAttendanceViewModel @Inject constructor( + private val attendanceRepository: AttendanceRepository +) : ViewModel() { + + private val _uiState: MutableStateFlow = MutableStateFlow(AttendanceUiState.Loading) + val uiState: StateFlow = _uiState + + private var fakeTitle: String = "" + + fun updateUiState() { + viewModelScope.launch { + _uiState.emit(AttendanceUiState.Success(fakeTitle)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceLoadingScreen.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceLoadingScreen.kt new file mode 100644 index 000000000..f9857220b --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceLoadingScreen.kt @@ -0,0 +1,20 @@ +package org.sopt.official.feature.attendance.compose + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +@Composable +fun AttendanceLoadingScreen() { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text("Loading") + } +} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt new file mode 100644 index 000000000..dbbcdf43c --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt @@ -0,0 +1,58 @@ +package org.sopt.official.feature.attendance.compose + +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.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import org.sopt.official.feature.attendance.NewAttendanceViewModel +import org.sopt.official.feature.attendance.model.AttendanceAction +import org.sopt.official.feature.attendance.model.AttendanceUiState + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AttendanceRoute() { + val viewModel: NewAttendanceViewModel = viewModel() + + val state by viewModel.uiState.collectAsStateWithLifecycle() + val action = viewModel.rememberAttendanceActions() + + Scaffold( + topBar = { + TopAppBar(title = { Text(text = "출석") }) + } + ) { innerPaddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(innerPaddingValues) + ) { + when (state) { + AttendanceUiState.Loading -> { + AttendanceLoadingScreen() + } + + is AttendanceUiState.Failure -> {} + AttendanceUiState.NetworkError -> {} + is AttendanceUiState.Success -> { + AttendanceScreen(state = state as AttendanceUiState.Success, action = action) + } + } + } + } +} + +@Composable +fun NewAttendanceViewModel.rememberAttendanceActions(): AttendanceAction = remember(this) { + AttendanceAction( + onFakeClick = this::updateUiState + ) +} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceScreen.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceScreen.kt new file mode 100644 index 000000000..c686dcff4 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceScreen.kt @@ -0,0 +1,21 @@ +package org.sopt.official.feature.attendance.compose + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import org.sopt.official.feature.attendance.model.AttendanceAction +import org.sopt.official.feature.attendance.model.AttendanceUiState + +@Composable +fun AttendanceScreen(state: AttendanceUiState.Success, action: AttendanceAction) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + // TODO + } +} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceAction.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceAction.kt new file mode 100644 index 000000000..58ea0182b --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceAction.kt @@ -0,0 +1,5 @@ +package org.sopt.official.feature.attendance.model + +class AttendanceAction( + val onFakeClick: () -> Unit +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceUiState.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceUiState.kt new file mode 100644 index 000000000..04ee074fb --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceUiState.kt @@ -0,0 +1,11 @@ +package org.sopt.official.feature.attendance.model + +sealed interface AttendanceUiState { + data object Loading : AttendanceUiState + data class Success( + val fakeTitle: String + ) : AttendanceUiState + + data class Failure(val error: Throwable?) : AttendanceUiState + data object NetworkError : AttendanceUiState +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 237c56d83..501295aa2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -121,6 +121,7 @@ compose-lottie = { module = "com.airbnb.android:lottie-compose", version.ref = " compose-paging = { module = "androidx.paging:paging-compose", version.ref = "androidx-paging" } # For accesss of ComposeCompilerGradlePluginExtension compose-compiler-extension = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" } +compose-lifecycle = { module = "androidx.lifecycle:lifecycle-runtime-compose" } coil-core = { module = "io.coil-kt:coil", version.ref = "coil" } coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } From 85c82662390ebc3162ec8ca8f73b9f3c7f0c081d Mon Sep 17 00:00:00 2001 From: Giovanni Junseo Kim Date: Thu, 30 May 2024 10:25:22 +0900 Subject: [PATCH 2/7] Attendance top app bar (#716) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: core 모듈의 designsystem에 NoRippleClickable 추가 * feat: AttendanceTopAppBar 생성 * chore: String 리소스 추출 * chore: TODO : fetchData 구현 * chore: AttendanceTopAppBar 적용 * chore: Modifier 인자 추가 * chore: extract string resource * chore: change style and delete action class * chore: change icon * chore: delete NoRippleClickable * chore: use clickable --- .../attendance/NewAttendanceActivity.kt | 4 +- .../attendance/NewAttendanceViewModel.kt | 17 +++++ .../attendance/compose/AttendanceRoute.kt | 15 ++-- .../compose/component/AttendanceTopAppBar.kt | 73 +++++++++++++++++++ app/src/main/res/values/strings.xml | 6 +- 5 files changed, 104 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceTopAppBar.kt diff --git a/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceActivity.kt b/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceActivity.kt index 2b96b443e..dddd4b839 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceActivity.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceActivity.kt @@ -13,7 +13,9 @@ class NewAttendanceActivity : ComponentActivity() { super.onCreate(savedInstanceState) setContent { SoptTheme { - AttendanceRoute() + AttendanceRoute( + onClickBackIcon = { if (!isFinishing) finish() } + ) } } } diff --git a/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt b/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt index 4a179fa04..5e5ae164b 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt @@ -15,6 +15,10 @@ class NewAttendanceViewModel @Inject constructor( private val attendanceRepository: AttendanceRepository ) : ViewModel() { + init { + fetchData() + } + private val _uiState: MutableStateFlow = MutableStateFlow(AttendanceUiState.Loading) val uiState: StateFlow = _uiState @@ -25,4 +29,17 @@ class NewAttendanceViewModel @Inject constructor( _uiState.emit(AttendanceUiState.Success(fakeTitle)) } } + + fun fetchData() { + fetchSoptEvent() + fetchAttendanceHistory() + } + + private fun fetchSoptEvent() { + // TODO + } + + private fun fetchAttendanceHistory() { + // TODO + } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt index dbbcdf43c..a86c6e6e1 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt @@ -3,10 +3,7 @@ package org.sopt.official.feature.attendance.compose 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.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -14,21 +11,23 @@ import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import org.sopt.official.feature.attendance.NewAttendanceViewModel +import org.sopt.official.feature.attendance.compose.component.AttendanceTopAppBar import org.sopt.official.feature.attendance.model.AttendanceAction import org.sopt.official.feature.attendance.model.AttendanceUiState -@OptIn(ExperimentalMaterial3Api::class) @Composable -fun AttendanceRoute() { +fun AttendanceRoute(onClickBackIcon: () -> Unit) { val viewModel: NewAttendanceViewModel = viewModel() - val state by viewModel.uiState.collectAsStateWithLifecycle() val action = viewModel.rememberAttendanceActions() Scaffold( topBar = { - TopAppBar(title = { Text(text = "출석") }) - } + AttendanceTopAppBar( + onClickBackIcon = onClickBackIcon, + onClickRefreshIcon = viewModel::fetchData, + ) + }, ) { innerPaddingValues -> Column( modifier = Modifier diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceTopAppBar.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceTopAppBar.kt new file mode 100644 index 000000000..e7afbc7dd --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceTopAppBar.kt @@ -0,0 +1,73 @@ +package org.sopt.official.feature.attendance.compose.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarColors +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.sopt.official.R +import org.sopt.official.designsystem.SoptTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AttendanceTopAppBar( + onClickBackIcon: () -> Unit, + onClickRefreshIcon: () -> Unit, + modifier: Modifier = Modifier +) { + CenterAlignedTopAppBar( + title = { + Text( + text = stringResource(R.string.attendance_app_bar_title), + color = SoptTheme.colors.onSurface10, + style = SoptTheme.typography.body16M + ) + }, + modifier = modifier, + navigationIcon = { + Icon( + painter = painterResource(id = R.drawable.btn_arrow_left), + contentDescription = stringResource(R.string.go_back), + modifier = Modifier + .padding(start = 20.dp) + .clickable(onClick = onClickBackIcon) + ) + }, + actions = { + Icon( + painter = painterResource(id = R.drawable.ic_refresh), + contentDescription = stringResource(R.string.refresh), + modifier = Modifier + .padding(end = 20.dp) + .clickable(onClick = onClickRefreshIcon) + ) + }, + colors = TopAppBarColors( + containerColor = SoptTheme.colors.background, + scrolledContainerColor = SoptTheme.colors.background, + navigationIconContentColor = SoptTheme.colors.onBackground, + titleContentColor = SoptTheme.colors.onBackground, + actionIconContentColor = SoptTheme.colors.onBackground, + ) + ) +} + +@Preview +@Composable +private fun AttendanceTopAppBarPreview() { + SoptTheme { + AttendanceTopAppBar( + onClickBackIcon = {}, + onClickRefreshIcon = {}, + ) + } +} + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 30b3b3418..e135e4c65 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,4 @@ - - 알림 From 7cd1dfad7ae0b2a17e5fe28194fa51af96a14ed2 Mon Sep 17 00:00:00 2001 From: Giovanni Junseo Kim Date: Fri, 31 May 2024 13:44:15 +0900 Subject: [PATCH 3/7] Today attendance card (#717) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: icon 리소스 추가 * feat: MidtermAttendanceCard 구현 * feat: FinalAttendanceCard 구현 * feat: AttendanceProgressBar 구현 * feat: AttendanceHistoryCard 구현 * chore: string 리소스 추출 * chore: rename * chore: change class name * chore: add text style * chore: add AttendanceHistoryCard radius * feat: implement TodayNoAttendanceCard * feat: implement TodayNoScheduleCard * chore: extract string resource * chore: separate model * chore: use PreviewParameter * chore: apply spotlessApply * chore: change class, objects in sealed to data class, data objects --- .../attendance/NewAttendanceViewModel.kt | 5 +- .../compose/AttendanceProgressBar.kt | 101 +++++++++++++++ .../attendance/compose/FinalAttendanceCard.kt | 57 +++++++++ .../compose/MidtermAttendanceCard.kt | 55 +++++++++ .../attendance/compose/TodayAttendanceCard.kt | 115 ++++++++++++++++++ .../compose/TodayNoAttendanceCard.kt | 105 ++++++++++++++++ .../attendance/compose/TodayNoScheduleCard.kt | 43 +++++++ .../compose/component/AttendanceTopAppBar.kt | 1 - .../attendance/model/AttendanceAction.kt | 2 +- .../attendance/model/AttendanceType.kt | 6 + .../attendance/model/AttendanceUiState.kt | 2 +- .../attendance/model/FinalAttendance.kt | 31 +++++ .../attendance/model/MidtermAttendance.kt | 28 +++++ .../model/state/AttendanceProgressBarState.kt | 10 ++ .../model/state/TodayAttendanceCardState.kt | 13 ++ .../model/state/TodayNoAttendanceCardState.kt | 7 ++ .../ic_attendance_state_absence_black.xml | 51 ++++++++ .../ic_attendance_state_absence_white.xml | 51 ++++++++ .../res/drawable/ic_attendance_state_done.xml | 44 +++++++ .../res/drawable/ic_attendance_state_late.xml | 51 ++++++++ .../drawable/ic_attendance_state_nothing.xml | 34 ++++++ .../res/drawable/ic_attendance_state_yes.xml | 44 +++++++ app/src/main/res/values/strings.xml | 3 + 23 files changed, 854 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceProgressBar.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/FinalAttendanceCard.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/MidtermAttendanceCard.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/TodayAttendanceCard.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/TodayNoAttendanceCard.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/TodayNoScheduleCard.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceType.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/FinalAttendance.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/MidtermAttendance.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/state/AttendanceProgressBarState.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/state/TodayAttendanceCardState.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/state/TodayNoAttendanceCardState.kt create mode 100644 app/src/main/res/drawable/ic_attendance_state_absence_black.xml create mode 100644 app/src/main/res/drawable/ic_attendance_state_absence_white.xml create mode 100644 app/src/main/res/drawable/ic_attendance_state_done.xml create mode 100644 app/src/main/res/drawable/ic_attendance_state_late.xml create mode 100644 app/src/main/res/drawable/ic_attendance_state_nothing.xml create mode 100644 app/src/main/res/drawable/ic_attendance_state_yes.xml diff --git a/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt b/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt index 5e5ae164b..31daba16c 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt @@ -19,7 +19,8 @@ class NewAttendanceViewModel @Inject constructor( fetchData() } - private val _uiState: MutableStateFlow = MutableStateFlow(AttendanceUiState.Loading) + private val _uiState: MutableStateFlow = + MutableStateFlow(AttendanceUiState.Loading) val uiState: StateFlow = _uiState private var fakeTitle: String = "" @@ -42,4 +43,4 @@ class NewAttendanceViewModel @Inject constructor( private fun fetchAttendanceHistory() { // TODO } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceProgressBar.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceProgressBar.kt new file mode 100644 index 000000000..2f2087a13 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceProgressBar.kt @@ -0,0 +1,101 @@ +package org.sopt.official.feature.attendance.compose + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.runtime.Composable +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.compose.ui.unit.dp +import org.sopt.official.designsystem.SoptTheme +import org.sopt.official.feature.attendance.model.AttendanceType +import org.sopt.official.feature.attendance.model.FinalAttendance +import org.sopt.official.feature.attendance.model.MidtermAttendance +import org.sopt.official.feature.attendance.model.state.AttendanceProgressBarState + +@Composable +fun AttendanceProgressBar(state: AttendanceProgressBarState, modifier: Modifier = Modifier) { + Box(modifier = modifier) { + LinearProgressIndicator( + progress = { + calculateAttendanceProgress( + firstAttendance = state.firstAttendance, + secondAttendance = state.secondAttendance + ) + }, + modifier = Modifier + .fillMaxWidth() + .height(1.dp) + .padding(top = 24.dp, start = 36.dp, end = 36.dp), + color = SoptTheme.colors.onSurface10, + trackColor = SoptTheme.colors.onSurface400, + ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + MidtermAttendanceCard( + midtermAttendance = state.firstAttendance, + ) + MidtermAttendanceCard( + midtermAttendance = state.secondAttendance, + ) + FinalAttendanceCard( + finalAttendance = state.finalAttendance, + ) + } + } +} + +fun calculateAttendanceProgress( + firstAttendance: MidtermAttendance, + secondAttendance: MidtermAttendance +): Float { + if (!firstAttendance.isFinished) return 0f + if (!secondAttendance.isFinished) { + return 0.5f + } else { + return 1f + } +} + +class AttendanceProgressBarPreviewParameterProvider( + override val values: Sequence = sequenceOf( + AttendanceProgressBarState( + firstAttendance = MidtermAttendance.NotYet(AttendanceType.FIRST), + secondAttendance = MidtermAttendance.NotYet(AttendanceType.SECOND), + finalAttendance = FinalAttendance.NOT_YET, + ), + AttendanceProgressBarState( + firstAttendance = MidtermAttendance.Present(attendanceAt = "14:00"), + secondAttendance = MidtermAttendance.Absent, + finalAttendance = FinalAttendance.LATE, + ), + AttendanceProgressBarState( + firstAttendance = MidtermAttendance.Present(attendanceAt = "14:00"), + secondAttendance = MidtermAttendance.Present(attendanceAt = "16:00"), + finalAttendance = FinalAttendance.PRESENT, + ), + AttendanceProgressBarState( + firstAttendance = MidtermAttendance.Absent, + secondAttendance = MidtermAttendance.Absent, + finalAttendance = FinalAttendance.ABSENT, + ), + ) +) : PreviewParameterProvider + +@Preview(showBackground = true) +@Composable +private fun AttendanceProgressBarPreview( + @PreviewParameter(AttendanceProgressBarPreviewParameterProvider::class) attendanceProgressBarState: AttendanceProgressBarState +) { + SoptTheme { + AttendanceProgressBar(state = attendanceProgressBarState) + } +} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/FinalAttendanceCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/FinalAttendanceCard.kt new file mode 100644 index 000000000..89291a346 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/FinalAttendanceCard.kt @@ -0,0 +1,57 @@ +package org.sopt.official.feature.attendance.compose + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import org.sopt.official.designsystem.SoptTheme +import org.sopt.official.feature.attendance.model.FinalAttendance + +@Composable +fun FinalAttendanceCard(finalAttendance: FinalAttendance, modifier: Modifier = Modifier) { + Column( + modifier = modifier.padding(horizontal = 12.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Image( + painter = painterResource(finalAttendance.imageResId), + contentDescription = null, + ) + Text( + text = finalAttendance.result, + color = if (finalAttendance.isFinished) SoptTheme.colors.onSurface10 else SoptTheme.colors.onSurface500, + style = SoptTheme.typography.label14SB + ) + } +} + +class FinalAttendanceCardPreviewParameterProvider( + override val values: Sequence = sequenceOf( + FinalAttendance.NOT_YET, + FinalAttendance.PRESENT, + FinalAttendance.LATE, + FinalAttendance.ABSENT, + ) +) : PreviewParameterProvider + +@Preview +@Composable +private fun FinalAttendanceCardPreview( + @PreviewParameter(FinalAttendanceCardPreviewParameterProvider::class) finalAttendance: FinalAttendance +) { + SoptTheme { + FinalAttendanceCard( + finalAttendance = finalAttendance, + modifier = Modifier.background(color = SoptTheme.colors.onSurface800) + ) + } +} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/MidtermAttendanceCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/MidtermAttendanceCard.kt new file mode 100644 index 000000000..128b4aaf2 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/MidtermAttendanceCard.kt @@ -0,0 +1,55 @@ +package org.sopt.official.feature.attendance.compose + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import org.sopt.official.designsystem.SoptTheme +import org.sopt.official.feature.attendance.model.AttendanceType +import org.sopt.official.feature.attendance.model.MidtermAttendance + +@Composable +fun MidtermAttendanceCard(midtermAttendance: MidtermAttendance, modifier: Modifier = Modifier) { + Column( + modifier = modifier.padding(horizontal = 12.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Image( + painter = painterResource(midtermAttendance.imageResId), + contentDescription = null, + ) + Text( + text = midtermAttendance.description, + color = if (midtermAttendance.isFinished) SoptTheme.colors.onSurface10 else SoptTheme.colors.onSurface500, + style = SoptTheme.typography.label14SB + ) + } +} + +class MidtermAttendanceCardPreviewParameterProvider( + override val values: Sequence = sequenceOf( + MidtermAttendance.NotYet(attendanceType = AttendanceType.FIRST), + MidtermAttendance.Present(attendanceAt = "14:00"), + MidtermAttendance.Absent, + ) +) : PreviewParameterProvider + +@Preview(showBackground = true) +@Composable +private fun MidtermAttendanceCardPreview( + @PreviewParameter(MidtermAttendanceCardPreviewParameterProvider::class) midtermAttendance: MidtermAttendance, +) { + SoptTheme { + MidtermAttendanceCard( + midtermAttendance = midtermAttendance + ) + } +} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/TodayAttendanceCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/TodayAttendanceCard.kt new file mode 100644 index 000000000..cd660aded --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/TodayAttendanceCard.kt @@ -0,0 +1,115 @@ +package org.sopt.official.feature.attendance.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.sopt.official.R +import org.sopt.official.designsystem.SoptTheme +import org.sopt.official.feature.attendance.model.FinalAttendance +import org.sopt.official.feature.attendance.model.MidtermAttendance +import org.sopt.official.feature.attendance.model.state.AttendanceProgressBarState +import org.sopt.official.feature.attendance.model.state.TodayAttendanceCardState + +@Composable +fun TodayAttendanceCard(state: TodayAttendanceCardState, modifier: Modifier = Modifier) { + Column( + modifier = + modifier + .background( + color = SoptTheme.colors.onSurface800, + shape = RoundedCornerShape(16.dp) + ) + .padding(horizontal = 24.dp, vertical = 32.dp), + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + painter = painterResource(id = R.drawable.ic_attendance_event_date), + contentDescription = null, + tint = SoptTheme.colors.onSurface300, + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = state.eventDate, + color = SoptTheme.colors.onSurface300, + style = SoptTheme.typography.body14M + ) + } + Spacer(modifier = Modifier.height(7.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + painter = painterResource(id = R.drawable.ic_attendance_event_location), + contentDescription = null, + tint = SoptTheme.colors.onSurface300, + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = state.eventLocation, + color = SoptTheme.colors.onSurface300, + style = SoptTheme.typography.body14M + ) + } + Spacer(modifier = Modifier.height(16.dp)) + Row { + Text( + text = stringResource(R.string.attendance_event_info_prefix), + color = SoptTheme.colors.onSurface10, + style = SoptTheme.typography.body18M + ) + Text( + text = state.eventName, + color = SoptTheme.colors.onSurface10, + style = SoptTheme.typography.body18M.copy(fontWeight = FontWeight.ExtraBold) + ) + Text( + text = stringResource(R.string.attendance_event_info_suffix), + color = SoptTheme.colors.onSurface10, + style = SoptTheme.typography.body18M + ) + } + Spacer(modifier = Modifier.height(12.dp)) + AttendanceProgressBar( + state = AttendanceProgressBarState( + firstAttendance = state.firstAttendance, + secondAttendance = state.secondAttendance, + finalAttendance = state.finalAttendance, + ) + ) + } +} + +@Preview +@Composable +private fun AttendanceHistoryCardPreview() { + SoptTheme { + Column( + modifier = Modifier.background(color = SoptTheme.colors.background) + ) { + TodayAttendanceCard( + state = + TodayAttendanceCardState( + eventDate = "3월 23일 토요일 14:00 - 18:00", + eventLocation = "건국대학교 꽥꽥오리관", + eventName = "2차 세미나", + firstAttendance = MidtermAttendance.Present(attendanceAt = "14:00"), + secondAttendance = MidtermAttendance.Absent, + finalAttendance = FinalAttendance.LATE, + ), + ) + } + } +} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/TodayNoAttendanceCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/TodayNoAttendanceCard.kt new file mode 100644 index 000000000..231e8c125 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/TodayNoAttendanceCard.kt @@ -0,0 +1,105 @@ +package org.sopt.official.feature.attendance.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.sopt.official.R +import org.sopt.official.designsystem.SoptTheme +import org.sopt.official.feature.attendance.model.state.TodayNoAttendanceCardState + +@Composable +fun TodayNoAttendanceCard(state: TodayNoAttendanceCardState, modifier: Modifier = Modifier) { + Column( + modifier = + modifier + .background( + color = SoptTheme.colors.onSurface800, + shape = RoundedCornerShape(16.dp) + ) + .padding(horizontal = 24.dp, vertical = 32.dp), + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + painter = painterResource(id = R.drawable.ic_attendance_event_date), + contentDescription = null, + tint = SoptTheme.colors.onSurface300, + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = state.eventDate, + color = SoptTheme.colors.onSurface300, + style = SoptTheme.typography.body14M + ) + } + Spacer(modifier = Modifier.height(7.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + painter = painterResource(id = R.drawable.ic_attendance_event_location), + contentDescription = null, + tint = SoptTheme.colors.onSurface300, + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = state.eventLocation, + color = SoptTheme.colors.onSurface300, + style = SoptTheme.typography.body14M + ) + } + Spacer(modifier = Modifier.height(16.dp)) + Row { + Text( + text = stringResource(R.string.attendance_event_info_prefix), + color = SoptTheme.colors.onSurface10, + style = SoptTheme.typography.body18M + ) + Text( + text = state.eventName, + color = SoptTheme.colors.onSurface10, + style = SoptTheme.typography.body18M.copy(fontWeight = FontWeight.ExtraBold) + ) + Text( + text = stringResource(R.string.attendance_event_info_suffix), + color = SoptTheme.colors.onSurface10, + style = SoptTheme.typography.body18M + ) + } + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = stringResource(id = R.string.text_not_awarded), + color = SoptTheme.colors.onSurface100, + style = SoptTheme.typography.body14M, + ) + } +} + +@Preview +@Composable +private fun TodayNoAttendanceCardPreview() { + SoptTheme { + Column(modifier = Modifier.background(color = SoptTheme.colors.background)) { + TodayNoAttendanceCard( + state = + TodayNoAttendanceCardState( + eventDate = "5월 12일 일요일 14:00 - 18:00", + eventLocation = "배달의민족주문~", + eventName = "데모데이", + ), + ) + } + } +} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/TodayNoScheduleCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/TodayNoScheduleCard.kt new file mode 100644 index 000000000..22e8c2cf2 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/TodayNoScheduleCard.kt @@ -0,0 +1,43 @@ +package org.sopt.official.feature.attendance.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.sopt.official.R +import org.sopt.official.designsystem.SoptTheme + +@Composable +fun TodayNoScheduleCard(modifier: Modifier = Modifier) { + Column( + modifier = + modifier + .background( + color = SoptTheme.colors.onSurface800, + shape = RoundedCornerShape(16.dp) + ) + .padding(horizontal = 24.dp, vertical = 32.dp), + ) { + Text( + text = stringResource(id = R.string.text_no_schedule), + color = SoptTheme.colors.onSurface10, + style = SoptTheme.typography.title16SB, + ) + } +} + +@Preview +@Composable +private fun TodayNoScheduleCardPreview() { + SoptTheme { + Column(modifier = Modifier.background(color = SoptTheme.colors.background)) { + TodayNoScheduleCard() + } + } +} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceTopAppBar.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceTopAppBar.kt index e7afbc7dd..b209c5a9a 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceTopAppBar.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceTopAppBar.kt @@ -70,4 +70,3 @@ private fun AttendanceTopAppBarPreview() { ) } } - diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceAction.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceAction.kt index 58ea0182b..6fdba104b 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceAction.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceAction.kt @@ -2,4 +2,4 @@ package org.sopt.official.feature.attendance.model class AttendanceAction( val onFakeClick: () -> Unit -) \ No newline at end of file +) diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceType.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceType.kt new file mode 100644 index 000000000..f3fcb23b6 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceType.kt @@ -0,0 +1,6 @@ +package org.sopt.official.feature.attendance.model + +enum class AttendanceType(val type: String) { + FIRST("1차 출석"), + SECOND("2차 출석") +} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceUiState.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceUiState.kt index 04ee074fb..366dedcde 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceUiState.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceUiState.kt @@ -8,4 +8,4 @@ sealed interface AttendanceUiState { data class Failure(val error: Throwable?) : AttendanceUiState data object NetworkError : AttendanceUiState -} \ No newline at end of file +} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/FinalAttendance.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/FinalAttendance.kt new file mode 100644 index 000000000..26b21d5c9 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/model/FinalAttendance.kt @@ -0,0 +1,31 @@ +package org.sopt.official.feature.attendance.model + +import androidx.annotation.DrawableRes +import org.sopt.official.R + +enum class FinalAttendance( + @DrawableRes val imageResId: Int, + val isFinished: Boolean, + val result: String, +) { + NOT_YET( + imageResId = R.drawable.ic_attendance_state_nothing, + isFinished = false, + result = "출석 전" + ), + PRESENT( + imageResId = R.drawable.ic_attendance_state_done, + isFinished = true, + result = "출석완료!" + ), + LATE( + imageResId = R.drawable.ic_attendance_state_late, + isFinished = true, + result = "지각" + ), + ABSENT( + imageResId = R.drawable.ic_attendance_state_absence_black, + isFinished = true, + result = "결석" + ) +} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/MidtermAttendance.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/MidtermAttendance.kt new file mode 100644 index 000000000..699a7b641 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/model/MidtermAttendance.kt @@ -0,0 +1,28 @@ +package org.sopt.official.feature.attendance.model + +import androidx.annotation.DrawableRes +import org.sopt.official.R + +sealed class MidtermAttendance( + @DrawableRes val imageResId: Int, + val isFinished: Boolean, + val description: String +) { + data class NotYet(val attendanceType: AttendanceType) : MidtermAttendance( + imageResId = R.drawable.ic_attendance_state_nothing, + isFinished = false, + description = attendanceType.type + ) + + data class Present(val attendanceAt: String) : MidtermAttendance( + imageResId = R.drawable.ic_attendance_state_yes, + isFinished = true, + description = attendanceAt + ) + + data object Absent : MidtermAttendance( + imageResId = R.drawable.ic_attendance_state_absence_white, + isFinished = true, + description = "-" + ) +} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/state/AttendanceProgressBarState.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/state/AttendanceProgressBarState.kt new file mode 100644 index 000000000..4c638b06a --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/model/state/AttendanceProgressBarState.kt @@ -0,0 +1,10 @@ +package org.sopt.official.feature.attendance.model.state + +import org.sopt.official.feature.attendance.model.FinalAttendance +import org.sopt.official.feature.attendance.model.MidtermAttendance + +class AttendanceProgressBarState( + val firstAttendance: MidtermAttendance, + val secondAttendance: MidtermAttendance, + val finalAttendance: FinalAttendance, +) diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/state/TodayAttendanceCardState.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/state/TodayAttendanceCardState.kt new file mode 100644 index 000000000..0b2898a08 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/model/state/TodayAttendanceCardState.kt @@ -0,0 +1,13 @@ +package org.sopt.official.feature.attendance.model.state + +import org.sopt.official.feature.attendance.model.FinalAttendance +import org.sopt.official.feature.attendance.model.MidtermAttendance + +class TodayAttendanceCardState( + val eventDate: String, + val eventLocation: String, + val eventName: String, + val firstAttendance: MidtermAttendance, + val secondAttendance: MidtermAttendance, + val finalAttendance: FinalAttendance, +) diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/state/TodayNoAttendanceCardState.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/state/TodayNoAttendanceCardState.kt new file mode 100644 index 000000000..4b1646867 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/model/state/TodayNoAttendanceCardState.kt @@ -0,0 +1,7 @@ +package org.sopt.official.feature.attendance.model.state + +class TodayNoAttendanceCardState( + val eventDate: String, + val eventLocation: String, + val eventName: String, +) diff --git a/app/src/main/res/drawable/ic_attendance_state_absence_black.xml b/app/src/main/res/drawable/ic_attendance_state_absence_black.xml new file mode 100644 index 000000000..479a35f76 --- /dev/null +++ b/app/src/main/res/drawable/ic_attendance_state_absence_black.xml @@ -0,0 +1,51 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_attendance_state_absence_white.xml b/app/src/main/res/drawable/ic_attendance_state_absence_white.xml new file mode 100644 index 000000000..98be99fa3 --- /dev/null +++ b/app/src/main/res/drawable/ic_attendance_state_absence_white.xml @@ -0,0 +1,51 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_attendance_state_done.xml b/app/src/main/res/drawable/ic_attendance_state_done.xml new file mode 100644 index 000000000..f84c9c4be --- /dev/null +++ b/app/src/main/res/drawable/ic_attendance_state_done.xml @@ -0,0 +1,44 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_attendance_state_late.xml b/app/src/main/res/drawable/ic_attendance_state_late.xml new file mode 100644 index 000000000..c9e280985 --- /dev/null +++ b/app/src/main/res/drawable/ic_attendance_state_late.xml @@ -0,0 +1,51 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_attendance_state_nothing.xml b/app/src/main/res/drawable/ic_attendance_state_nothing.xml new file mode 100644 index 000000000..9d73b8f05 --- /dev/null +++ b/app/src/main/res/drawable/ic_attendance_state_nothing.xml @@ -0,0 +1,34 @@ + + + + diff --git a/app/src/main/res/drawable/ic_attendance_state_yes.xml b/app/src/main/res/drawable/ic_attendance_state_yes.xml new file mode 100644 index 000000000..b0b278219 --- /dev/null +++ b/app/src/main/res/drawable/ic_attendance_state_yes.xml @@ -0,0 +1,44 @@ + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e135e4c65..82e9df4e5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -88,6 +88,7 @@ 참여 나의 출결 현황 출석점수를 체크하지 않습니다 + 오늘은 일정이 없는 날이에요 1차 출석 2차 출석 출석 전 @@ -126,4 +127,6 @@ 접근할 수 없는 버튼이에요. 솝템프는 솝트 회원들에게만 제공되는 기능이에요. 확인 + "오늘은 " + " 날이에요" \ No newline at end of file From 0dd4036fd021b067d019e847b3af1cf5e5b02b7a Mon Sep 17 00:00:00 2001 From: Giovanni Junseo Kim Date: Sun, 16 Jun 2024 19:00:02 +0900 Subject: [PATCH 4/7] =?UTF-8?q?Attendance=20history=20card=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#733)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: AttendanceHistoryCard 구현 * chore: string 리소스 추출 * rebase * feat: AttendanceHistoryCard * feat: AttendanceHistorySummaryCard * feat: AttendanceHistoryUserInfoCard * chore: add dependency for collectAsStateWithLifecycle * chore: add text style to AttendanceHistoryUserInfoCard * feat: implement AttendanceHistoryUserInfoCard * feat: implement AttendanceHistorySummaryCard * feat: implement AttendanceCountCard * feat: implement AttendanceResultType * chore: move AttendanceHistoryCardState to model package * chore: move components to component package * feat: implement AttendanceHistoryCard * chore: change access modifier * feat: replace List with ImmutableList * chore: remove dependency --- .../compose/component/AttendanceCountCard.kt | 63 +++++++++++++ .../component/AttendanceHistoryCard.kt | 91 +++++++++++++++++++ .../component/AttendanceHistoryListCard.kt | 91 +++++++++++++++++++ .../component/AttendanceHistorySummaryCard.kt | 40 ++++++++ .../AttendanceHistoryUserInfoCard.kt | 71 +++++++++++++++ .../{ => component}/AttendanceProgressBar.kt | 2 +- .../{ => component}/FinalAttendanceCard.kt | 2 +- .../{ => component}/MidtermAttendanceCard.kt | 2 +- .../{ => component}/TodayAttendanceCard.kt | 4 +- .../{ => component}/TodayNoAttendanceCard.kt | 2 +- .../{ => component}/TodayNoScheduleCard.kt | 2 +- .../attendance/model/AttendanceHistory.kt | 7 ++ .../attendance/model/AttendanceResultType.kt | 8 ++ app/src/main/res/values/strings.xml | 1 + 14 files changed, 379 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCountCard.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryCard.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryListCard.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistorySummaryCard.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryUserInfoCard.kt rename app/src/main/java/org/sopt/official/feature/attendance/compose/{ => component}/AttendanceProgressBar.kt (98%) rename app/src/main/java/org/sopt/official/feature/attendance/compose/{ => component}/FinalAttendanceCard.kt (96%) rename app/src/main/java/org/sopt/official/feature/attendance/compose/{ => component}/MidtermAttendanceCard.kt (96%) rename app/src/main/java/org/sopt/official/feature/attendance/compose/{ => component}/TodayAttendanceCard.kt (97%) rename app/src/main/java/org/sopt/official/feature/attendance/compose/{ => component}/TodayNoAttendanceCard.kt (98%) rename app/src/main/java/org/sopt/official/feature/attendance/compose/{ => component}/TodayNoScheduleCard.kt (95%) create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceHistory.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceResultType.kt diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCountCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCountCard.kt new file mode 100644 index 000000000..624de66f9 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCountCard.kt @@ -0,0 +1,63 @@ +package org.sopt.official.feature.attendance.compose.component + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.material3.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.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.sopt.official.R +import org.sopt.official.designsystem.SoptTheme + +@Composable +fun AttendanceCountCard(resultType: String, count: Int, modifier: Modifier = Modifier) { + Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = resultType, + color = SoptTheme.colors.onSurface300, + style = SoptTheme.typography.label12SB + ) + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = count.toFormattedNumber(), + color = SoptTheme.colors.onSurface50, + style = SoptTheme.typography.body14M + ) + } +} + +private fun Int.toFormattedNumber(): String = "%02d회".format(this) + +@Preview +@Composable +private fun AttendanceCountCardPreview() { + SoptTheme { + Row { + AttendanceCountCard( + resultType = stringResource(id = R.string.title_attendance_count_all), + count = 0 + ) + Spacer(Modifier.width(10.dp)) + AttendanceCountCard( + resultType = stringResource(id = R.string.title_attendance_count_normal), + count = 0 + ) + Spacer(Modifier.width(10.dp)) + AttendanceCountCard( + resultType = stringResource(id = R.string.title_attendance_count_late), + count = 0 + ) + Spacer(Modifier.width(10.dp)) + AttendanceCountCard( + resultType = stringResource(id = R.string.title_attendance_count_abnormal), + count = 0 + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryCard.kt new file mode 100644 index 000000000..b0f71e546 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryCard.kt @@ -0,0 +1,91 @@ +package org.sopt.official.feature.attendance.compose.component + +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.google.common.collect.ImmutableList +import org.sopt.official.designsystem.SoptTheme +import org.sopt.official.feature.attendance.model.AttendanceHistory +import org.sopt.official.feature.attendance.model.AttendanceResultType + +@Composable +fun AttendanceHistoryCard( + userTitle: String, + attendanceScore: Int, + totalAttendanceResult: Map, + attendanceHistoryList: ImmutableList, + scrollState: ScrollState, modifier: Modifier = Modifier +) { + Column( + modifier = modifier + .background(color = SoptTheme.colors.onSurface800) + .padding(all = 32.dp) + ) { + AttendanceHistoryUserInfoCard( + userTitle = userTitle, + attendanceScore = attendanceScore + ) + Spacer(modifier = Modifier.height(24.dp)) + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier + .fillMaxWidth() + .background(color = SoptTheme.colors.onSurface700, shape = RoundedCornerShape(8.dp)) + .padding(horizontal = 24.dp, vertical = 16.dp), + ) { + totalAttendanceResult.forEach { attendanceResult -> + AttendanceCountCard( + resultType = attendanceResult.key.type, + count = attendanceResult.value + ) + } + } + Spacer(modifier = Modifier.height(24.dp)) + AttendanceHistoryListCard( + attendanceHistoryList = attendanceHistoryList, + scrollState = scrollState, + modifier = Modifier.fillMaxWidth() + ) + } +} + +@Preview +@Composable +private fun AttendanceHistoryCardPreview() { + SoptTheme { + AttendanceHistoryCard( + userTitle = "32기 디자인파트 김솝트", + attendanceScore = 1, + totalAttendanceResult = mapOf( + Pair(AttendanceResultType.ALL, 16), + Pair(AttendanceResultType.PRESENT, 5), + Pair(AttendanceResultType.LATE, 0), + Pair(AttendanceResultType.ABSENT, 11) + ), + attendanceHistoryList = ImmutableList.of( + AttendanceHistory( + status = "출석", eventName = "1차 세미나", date = "00월 00일" + ), + AttendanceHistory( + status = "출석", eventName = "1차 세미나", date = "00월 00일" + ), + AttendanceHistory( + status = "출석", eventName = "1차 세미나", date = "00월 00일" + ), + ), + scrollState = rememberScrollState() + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryListCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryListCard.kt new file mode 100644 index 000000000..737dfb273 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryListCard.kt @@ -0,0 +1,91 @@ +package org.sopt.official.feature.attendance.compose.component + +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.scrollable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.google.common.collect.ImmutableList +import org.sopt.official.designsystem.SoptTheme +import org.sopt.official.feature.attendance.model.AttendanceHistory + +@Composable +fun AttendanceHistoryListCard( + attendanceHistoryList: ImmutableList, + scrollState: ScrollState, + modifier: Modifier = Modifier +) { + Column(modifier = modifier) { + Text( + text = "나의 출결 현황", + color = SoptTheme.colors.onSurface300, + style = SoptTheme.typography.body14M, + ) + Spacer(Modifier.height(25.dp)) + Column( + Modifier.scrollable( + state = scrollState, + orientation = Orientation.Vertical + ) + ) { + attendanceHistoryList.forEachIndexed { index, attendanceHistory -> + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = attendanceHistory.status, + style = SoptTheme.typography.body14R, + color = SoptTheme.colors.onSurface100, + ) + Spacer(Modifier.width(8.dp)) + Text( + text = attendanceHistory.eventName, + style = SoptTheme.typography.label16SB, + color = SoptTheme.colors.onSurface10, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f) + ) + Spacer(Modifier.width(8.dp)) + Text( + text = attendanceHistory.date, + style = SoptTheme.typography.body14R, + color = SoptTheme.colors.onSurface100, + ) + } + if (index < attendanceHistoryList.lastIndex) { + Spacer(modifier = Modifier.height(16.dp)) + } + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun AttendanceHistoryListCardPreview() { + SoptTheme { + AttendanceHistoryListCard( + attendanceHistoryList = ImmutableList.of( + AttendanceHistory( + status = "출석", eventName = "1차 세미나", date = "00월 00일" + ), + AttendanceHistory( + status = "출석", eventName = "1차 세미나", date = "00월 00일" + ), + AttendanceHistory( + status = "출석", eventName = "1차 세미나", date = "00월 00일" + ), + ), + scrollState = rememberScrollState() + ) + } +} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistorySummaryCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistorySummaryCard.kt new file mode 100644 index 000000000..2966befc5 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistorySummaryCard.kt @@ -0,0 +1,40 @@ +package org.sopt.official.feature.attendance.compose.component + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.sopt.official.designsystem.SoptTheme + +@Composable +fun AttendanceHistorySummaryCard(modifier: Modifier = Modifier) { + Row(modifier) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = "전체", + color = SoptTheme.colors.onSurface300, + style = SoptTheme.typography.label12SB + ) + Spacer(Modifier.height(8.dp)) + Text( + text = "asdf", + color = SoptTheme.colors.onSurface50, + style = SoptTheme.typography.body14M + ) + } + } +} + +@Preview +@Composable +fun AttendanceHistorySummaryCardPreview() { + SoptTheme { + AttendanceHistorySummaryCard() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryUserInfoCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryUserInfoCard.kt new file mode 100644 index 000000000..edb08a6d0 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryUserInfoCard.kt @@ -0,0 +1,71 @@ +package org.sopt.official.feature.attendance.compose.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.sopt.official.R +import org.sopt.official.designsystem.Orange400 +import org.sopt.official.designsystem.SoptTheme + +@Composable +fun AttendanceHistoryUserInfoCard( + userTitle: String, + attendanceScore: Int, + modifier: Modifier = Modifier +) { + Column(modifier = modifier) { + Text( + text = userTitle, + color = SoptTheme.colors.onSurface300, + style = SoptTheme.typography.body14M + ) + Spacer(modifier = Modifier.height(8.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = "현재 출석점수는 ", + color = SoptTheme.colors.onSurface10, + style = SoptTheme.typography.body18M + ) + Text( + text = "${attendanceScore}점", + color = Orange400, + style = SoptTheme.typography.title20SB + ) + Text( + text = " 입니다!", + color = SoptTheme.colors.onSurface10, + style = SoptTheme.typography.body18M + ) + Spacer(modifier = Modifier.weight(1f)) + Icon( + modifier = Modifier.padding(end = 2.dp), + painter = painterResource(id = R.drawable.ic_attendance_point_info), + contentDescription = null, + tint = SoptTheme.colors.onSurface10 + ) + } + } +} + +@Preview +@Composable +fun AttendanceHistoryUserInfoCardPreview() { + SoptTheme { + AttendanceHistoryUserInfoCard( + userTitle = "32기 디자인파트 김솝트", + attendanceScore = 1, + modifier = Modifier.background(color = SoptTheme.colors.onSurface800) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceProgressBar.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceProgressBar.kt similarity index 98% rename from app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceProgressBar.kt rename to app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceProgressBar.kt index 2f2087a13..217e21267 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceProgressBar.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceProgressBar.kt @@ -1,4 +1,4 @@ -package org.sopt.official.feature.attendance.compose +package org.sopt.official.feature.attendance.compose.component import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/FinalAttendanceCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/FinalAttendanceCard.kt similarity index 96% rename from app/src/main/java/org/sopt/official/feature/attendance/compose/FinalAttendanceCard.kt rename to app/src/main/java/org/sopt/official/feature/attendance/compose/component/FinalAttendanceCard.kt index 89291a346..8260be5dc 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/FinalAttendanceCard.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/FinalAttendanceCard.kt @@ -1,4 +1,4 @@ -package org.sopt.official.feature.attendance.compose +package org.sopt.official.feature.attendance.compose.component import androidx.compose.foundation.Image import androidx.compose.foundation.background diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/MidtermAttendanceCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/MidtermAttendanceCard.kt similarity index 96% rename from app/src/main/java/org/sopt/official/feature/attendance/compose/MidtermAttendanceCard.kt rename to app/src/main/java/org/sopt/official/feature/attendance/compose/component/MidtermAttendanceCard.kt index 128b4aaf2..9d93552eb 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/MidtermAttendanceCard.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/MidtermAttendanceCard.kt @@ -1,4 +1,4 @@ -package org.sopt.official.feature.attendance.compose +package org.sopt.official.feature.attendance.compose.component import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/TodayAttendanceCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayAttendanceCard.kt similarity index 97% rename from app/src/main/java/org/sopt/official/feature/attendance/compose/TodayAttendanceCard.kt rename to app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayAttendanceCard.kt index cd660aded..1957b5bea 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/TodayAttendanceCard.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayAttendanceCard.kt @@ -1,4 +1,4 @@ -package org.sopt.official.feature.attendance.compose +package org.sopt.official.feature.attendance.compose.component import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column @@ -94,7 +94,7 @@ fun TodayAttendanceCard(state: TodayAttendanceCardState, modifier: Modifier = Mo @Preview @Composable -private fun AttendanceHistoryCardPreview() { +private fun TodayAttendanceCardPreview() { SoptTheme { Column( modifier = Modifier.background(color = SoptTheme.colors.background) diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/TodayNoAttendanceCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayNoAttendanceCard.kt similarity index 98% rename from app/src/main/java/org/sopt/official/feature/attendance/compose/TodayNoAttendanceCard.kt rename to app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayNoAttendanceCard.kt index 231e8c125..448b418fd 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/TodayNoAttendanceCard.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayNoAttendanceCard.kt @@ -1,4 +1,4 @@ -package org.sopt.official.feature.attendance.compose +package org.sopt.official.feature.attendance.compose.component import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/TodayNoScheduleCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayNoScheduleCard.kt similarity index 95% rename from app/src/main/java/org/sopt/official/feature/attendance/compose/TodayNoScheduleCard.kt rename to app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayNoScheduleCard.kt index 22e8c2cf2..38a219c0b 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/TodayNoScheduleCard.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayNoScheduleCard.kt @@ -1,4 +1,4 @@ -package org.sopt.official.feature.attendance.compose +package org.sopt.official.feature.attendance.compose.component import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceHistory.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceHistory.kt new file mode 100644 index 000000000..cc4744112 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceHistory.kt @@ -0,0 +1,7 @@ +package org.sopt.official.feature.attendance.model + +data class AttendanceHistory( + val status: String, + val eventName: String, + val date: String, +) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceResultType.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceResultType.kt new file mode 100644 index 000000000..4da216854 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceResultType.kt @@ -0,0 +1,8 @@ +package org.sopt.official.feature.attendance.model + +enum class AttendanceResultType(val type: String) { + ALL(type = "전체"), + PRESENT(type = "출석"), + LATE(type = "지각"), + ABSENT(type = "결석"); +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 82e9df4e5..0e620642f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -82,6 +82,7 @@ 출석 + 전체 출석 지각 결석 From 04d414d75f2499005b8e11500c518962f83699ad Mon Sep 17 00:00:00 2001 From: Giovanni Junseo Kim Date: Thu, 26 Sep 2024 14:38:20 +0900 Subject: [PATCH 5/7] =?UTF-8?q?AttendanceCodeDialog=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=20(#852)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Attendance screen compose setting (#676) * feat: implement NewAttendanceActivity * feat: implement NewAttendanceViewModel * chore: add compose-lifecycle dependency * feat: define AttendanceAction * feat: implement screens * feat: use SoptTheme in designsystem * chore: data class -> class로 변경 * chore: 람다 프로퍼티 이름 명시 * chore: SoptTheme darkTheme 기본값 사용 * chore: 필요없는 함수 제거 * chore: 구현 안 된 함수에 TODO 삽입 * chore: 동작하지 않는 Preview 제거 * chore: AttendanceAction 내 뷰모델 참조 제거 * chore: code format 변경 * chore: make stamp design system internal * chore: extract string resource * feat: implement AttendanceCodeCard * feat: implement AttendanceCodeCardList * chore: change logic * feat: implement AttendanceCodeDialog * feat: implement attendance button * chore: string resource 추출 * chore: change parameter List to ImmutableList * chore: reformat codee # Conflicts: # core/designsystem/src/main/java/org/sopt/official/designsystem/Color.kt --- app/build.gradle.kts | 1 + .../attendance/NewAttendanceViewModel.kt | 3 +- .../compose/AttendanceCodeDialog.kt | 155 ++++++++++++++++++ .../attendance/compose/AttendanceRoute.kt | 6 +- .../compose/component/AttendanceCodeCard.kt | 68 ++++++++ .../component/AttendanceCodeCardList.kt | 42 +++++ app/src/main/res/values/strings.xml | 4 + .../org/sopt/official/designsystem/Color.kt | 1 + .../stamp/designsystem/style/Theme.kt | 14 +- 9 files changed, 286 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceCodeDialog.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCodeCard.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCodeCardList.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1e00da893..847e091b3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -178,6 +178,7 @@ dependencies { implementation(libs.coil.core) implementation(libs.profileinstaller) implementation(libs.firebase.messaging.lifecycle.ktx) + implementation(libs.kotlin.collections.immutable) } secrets { diff --git a/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt b/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt index 31daba16c..2ed6b2d57 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt @@ -19,8 +19,7 @@ class NewAttendanceViewModel @Inject constructor( fetchData() } - private val _uiState: MutableStateFlow = - MutableStateFlow(AttendanceUiState.Loading) + private val _uiState: MutableStateFlow = MutableStateFlow(AttendanceUiState.Loading) val uiState: StateFlow = _uiState private var fakeTitle: String = "" diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceCodeDialog.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceCodeDialog.kt new file mode 100644 index 000000000..e8ece72ff --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceCodeDialog.kt @@ -0,0 +1,155 @@ +package org.sopt.official.feature.attendance.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import org.sopt.official.R +import org.sopt.official.designsystem.Black40 +import org.sopt.official.designsystem.Gray60 +import org.sopt.official.designsystem.SoptTheme +import org.sopt.official.feature.attendance.compose.component.AttendanceCodeCardList +import org.sopt.official.feature.attendance.model.AttendanceType + +@Composable +fun AttendanceCodeDialog( + codes: ImmutableList, + inputCodes: ImmutableList, + attendanceType: AttendanceType, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, +) { + Dialog(onDismissRequest = onDismissRequest) { + Column( + modifier + .background( + color = SoptTheme.colors.onSurface700, + shape = RoundedCornerShape(size = 10.dp) + ) + .padding(all = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + painter = painterResource(id = R.drawable.ic_close), + contentDescription = stringResource(id = R.string.close), + tint = SoptTheme.colors.onSurface10, + modifier = Modifier + .align(Alignment.End) + .clickable(onClick = onDismissRequest) + ) + Text( + text = stringResource(R.string.attendance_do, attendanceType.type), + style = SoptTheme.typography.heading18B, + color = SoptTheme.colors.onSurface10 + ) + Spacer(modifier = Modifier.height(10.dp)) + Text( + text = stringResource(R.string.attendance_code_description), + style = SoptTheme.typography.body13M, + color = SoptTheme.colors.onSurface300 + ) + Spacer(modifier = Modifier.height(24.dp)) + AttendanceCodeCardList( + codes = inputCodes, + onTextChange = {}, + onTextFieldFull = {}, + ) + if (codes != inputCodes) { + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = stringResource(R.string.attendance_code_does_not_match), + style = SoptTheme.typography.label12SB, + color = SoptTheme.colors.error + ) + } + Spacer(modifier = Modifier.height(32.dp)) + Button( + onClick = { /*TODO*/ }, + modifier = Modifier + .fillMaxWidth(), + shape = RoundedCornerShape(size = 6.dp), + colors = ButtonColors( + containerColor = SoptTheme.colors.onSurface10, + contentColor = SoptTheme.colors.onSurface950, + disabledContainerColor = Black40, + disabledContentColor = Gray60, + ), + enabled = codes == inputCodes + ) { + Text( + text = stringResource(R.string.attendance_dialog_button), + style = SoptTheme.typography.body13M, + ) + } + } + } +} + +@Preview +@Composable +private fun AttendanceCodeDialogPreview( + @PreviewParameter(AttendanceCodeDialogPreviewParameterProvider::class) parameter: AttendanceCodeDialogPreviewParameter, +) { + SoptTheme { + AttendanceCodeDialog( + codes = parameter.codes, + inputCodes = parameter.inputCodes, + attendanceType = parameter.attendanceType, + modifier = Modifier.fillMaxWidth(), + onDismissRequest = {} + ) + } +} + +data class AttendanceCodeDialogPreviewParameter( + val codes: ImmutableList, + val inputCodes: ImmutableList, + val attendanceType: AttendanceType, +) + +class AttendanceCodeDialogPreviewParameterProvider : + PreviewParameterProvider { + override val values: Sequence = + sequenceOf( + AttendanceCodeDialogPreviewParameter( + codes = persistentListOf("1", "2", "3", "4", "5"), + inputCodes = persistentListOf("1", "2", "3", null, null), + AttendanceType.FIRST, + ), + AttendanceCodeDialogPreviewParameter( + codes = persistentListOf("1", "2", "3", "4", "5"), + inputCodes = persistentListOf("1", "2", "3", "4", "5"), + AttendanceType.FIRST, + ), + AttendanceCodeDialogPreviewParameter( + codes = persistentListOf("1", "2", "3", "4", "5"), + inputCodes = persistentListOf("1", "2", "3", null, null), + AttendanceType.SECOND, + ), + AttendanceCodeDialogPreviewParameter( + codes = persistentListOf("1", "2", "3", "4", "5"), + inputCodes = persistentListOf("1", "2", "3", "4", "5"), + AttendanceType.SECOND, + ), + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt index a86c6e6e1..97e18ea45 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt @@ -3,7 +3,10 @@ package org.sopt.official.feature.attendance.compose 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.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -15,6 +18,7 @@ import org.sopt.official.feature.attendance.compose.component.AttendanceTopAppBa import org.sopt.official.feature.attendance.model.AttendanceAction import org.sopt.official.feature.attendance.model.AttendanceUiState +@OptIn(ExperimentalMaterial3Api::class) @Composable fun AttendanceRoute(onClickBackIcon: () -> Unit) { val viewModel: NewAttendanceViewModel = viewModel() @@ -27,7 +31,7 @@ fun AttendanceRoute(onClickBackIcon: () -> Unit) { onClickBackIcon = onClickBackIcon, onClickRefreshIcon = viewModel::fetchData, ) - }, + } ) { innerPaddingValues -> Column( modifier = Modifier diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCodeCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCodeCard.kt new file mode 100644 index 000000000..c7eb86b7b --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCodeCard.kt @@ -0,0 +1,68 @@ +package org.sopt.official.feature.attendance.compose.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.runtime.Composable +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.compose.ui.unit.dp +import org.sopt.official.designsystem.SoptTheme + +@Composable +fun AttendanceCodeCard( + text: String, + onTextChange: (String) -> Unit, + onTextFieldFull: () -> Unit, + modifier: Modifier = Modifier, + textMaxLength: Int = 1, +) { + BasicTextField( + value = text, + onValueChange = { newText: String -> + if (newText.length < textMaxLength) { + onTextChange(newText) + } else { + onTextFieldFull() + } + }, + modifier = modifier + .background( + color = if (text.isEmpty()) SoptTheme.colors.onSurface600 + else SoptTheme.colors.onSurface800, + shape = RoundedCornerShape(8.dp) + ) + .border( + width = 1.dp, + color = if (text.isEmpty()) SoptTheme.colors.onSurface500 + else SoptTheme.colors.primary, + shape = RoundedCornerShape(8.dp) + ) + .padding(horizontal = 17.dp, vertical = 18.dp) + .width(10.dp), + textStyle = SoptTheme.typography.heading16B.copy(color = SoptTheme.colors.primary) + ) +} + +@Preview +@Composable +private fun AttendanceCodeCardPreview( + @PreviewParameter(AttendanceCodeCardPreviewParameterProvider::class) text: String, +) { + SoptTheme { + AttendanceCodeCard( + text = text, + onTextChange = {}, + onTextFieldFull = {} + ) + } +} + +private class AttendanceCodeCardPreviewParameterProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf("", "8") +} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCodeCardList.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCodeCardList.kt new file mode 100644 index 000000000..b7e442c7f --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCodeCardList.kt @@ -0,0 +1,42 @@ +package org.sopt.official.feature.attendance.compose.component + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.sopt.official.designsystem.SoptTheme + +@Composable +fun AttendanceCodeCardList( + codes: List, + onTextChange: (newText: String) -> Unit, + onTextFieldFull: () -> Unit, + modifier: Modifier = Modifier, +) { + Row(modifier = modifier) { + repeat(codes.size) { index -> + AttendanceCodeCard( + text = codes[index] ?: "", + onTextChange = onTextChange, + onTextFieldFull = onTextFieldFull + ) + if (index < codes.size) { + Spacer(modifier = Modifier.width(width = 12.dp)) + } + } + } +} + +@Preview +@Composable +private fun AttendanceCodeCardListPreview() { + SoptTheme { + AttendanceCodeCardList( + codes = listOf("8", "8", "8", null, null), + onTextChange = {}, + onTextFieldFull = {}) + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0e620642f..12e16e67e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -27,6 +27,7 @@ SOPT를 알차게 즐기고 싶다면? SOPT-AMP! 콕 찌르기 + 닫기 안녕하세요,\nSOPT의 열정이 되어주세요! @@ -102,6 +103,9 @@ 뒤로 가기 새로고침 출석 조회하기 + %1$s하기 + 출석 코드 다섯 자리를 입력해주세요. + 코드가 일치하지 않아요! 알림 diff --git a/core/designsystem/src/main/java/org/sopt/official/designsystem/Color.kt b/core/designsystem/src/main/java/org/sopt/official/designsystem/Color.kt index 3e2bd0b50..bc991c545 100644 --- a/core/designsystem/src/main/java/org/sopt/official/designsystem/Color.kt +++ b/core/designsystem/src/main/java/org/sopt/official/designsystem/Color.kt @@ -40,6 +40,7 @@ val White = Color(0xFFFFFFFF) val Black = Color(0xFF000000) val Black80 = Color(0xFF1C1D1E) val Black60 = Color(0xFF2C2D2E) +val Black40 = Color(0xFF3C3D40) val Gray950 = Color(0xFF0F1012) val Gray900 = Color(0xFF17181C) val Gray800 = Color(0xFF202025) diff --git a/feature/soptamp/src/main/java/org/sopt/official/stamp/designsystem/style/Theme.kt b/feature/soptamp/src/main/java/org/sopt/official/stamp/designsystem/style/Theme.kt index c391a03fd..840c7c041 100644 --- a/feature/soptamp/src/main/java/org/sopt/official/stamp/designsystem/style/Theme.kt +++ b/feature/soptamp/src/main/java/org/sopt/official/stamp/designsystem/style/Theme.kt @@ -66,7 +66,7 @@ class SoptColors( onSurface20: Color, onSurface10: Color, onSurface5: Color, - isLight: Boolean + isLight: Boolean, ) { var white by mutableStateOf(white) private set @@ -217,7 +217,7 @@ fun soptLightColors( onSurface30: Color = Gray300, onSurface20: Color = Gray200, onSurface10: Color = Gray100, - onSurface5: Color = Gray50 + onSurface5: Color = Gray50, ) = SoptColors( white, black, @@ -278,7 +278,7 @@ fun soptDarkColors( onSurface30: Color = Gray300, onSurface20: Color = Gray200, onSurface10: Color = Gray100, - onSurface5: Color = Gray50 + onSurface5: Color = Gray50, ) = SoptColors( white, black, @@ -325,14 +325,18 @@ private val LocalSoptTypography = staticCompositionLocalOf { * Color에 접근하고 싶을때 SoptTheme.colors.primary 이런식으로 접근하면 됩니다. * Typo를 변경하고 싶다면 SoptTheme.typography.h1 이런식으로 접근하면 됩니다. * */ -object SoptTheme { +internal object SoptTheme { val colors: SoptColors @Composable get() = LocalSoptColors.current val typography: SoptTypography @Composable get() = LocalSoptTypography.current } @Composable -fun ProvideSoptColorsAndTypography(colors: SoptColors, typography: SoptTypography, content: @Composable () -> Unit) { +fun ProvideSoptColorsAndTypography( + colors: SoptColors, + typography: SoptTypography, + content: @Composable () -> Unit, +) { val provideColors = remember { colors.copy() } provideColors.update(colors) val provideTypography = remember { typography.copy() } From d2b9c0dd374d74ed23ce5c8e40a3f649a7c4dd81 Mon Sep 17 00:00:00 2001 From: Giovanni Junseo Kim Date: Sat, 30 Nov 2024 22:55:44 +0900 Subject: [PATCH 6/7] Attendance screen configuration (#985) * chore: rename AttendanceType -> AttendanceSession * chore: delete state for components * chore: use immutable collection * chore: remove mock * feat: add AttendanceDayType.kt * feat: implement AttendanceScreen * feat: adjust layout * chore: move background setting to component * chore: merge state class into UiState class * chore: move package * chore: keep convention * chore: separate AttendanceGradientBox * chore: rename ProgressBarState to AttendanceProgressBarPreviewParameter * chore: keep convention --- .../attendance/NewAttendanceViewModel.kt | 14 +- .../attendance/compose/AttendanceRoute.kt | 4 +- .../attendance/compose/AttendanceScreen.kt | 200 +++++++++++++++++- .../{ => component}/AttendanceCodeDialog.kt | 21 +- .../compose/component/AttendanceCountCard.kt | 6 +- .../component/AttendanceGradientBox.kt | 36 ++++ .../component/AttendanceHistoryCard.kt | 17 +- .../component/AttendanceHistoryListCard.kt | 9 +- .../component/AttendanceProgressBar.kt | 59 ++++-- .../compose/component/FinalAttendanceCard.kt | 7 +- .../component/MidtermAttendanceCard.kt | 13 +- .../compose/component/TodayAttendanceCard.kt | 48 ++--- .../component/TodayNoAttendanceCard.kt | 26 +-- .../attendance/model/AttendanceHistory.kt | 7 - .../attendance/model/AttendanceResultType.kt | 8 - .../attendance/model/AttendanceType.kt | 6 - .../attendance/model/AttendanceUiState.kt | 104 ++++++++- .../attendance/model/FinalAttendance.kt | 31 --- .../attendance/model/MidtermAttendance.kt | 28 --- .../model/state/AttendanceProgressBarState.kt | 10 - .../model/state/TodayAttendanceCardState.kt | 13 -- .../model/state/TodayNoAttendanceCardState.kt | 7 - 22 files changed, 454 insertions(+), 220 deletions(-) rename app/src/main/java/org/sopt/official/feature/attendance/compose/{ => component}/AttendanceCodeDialog.kt (91%) create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceGradientBox.kt delete mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceHistory.kt delete mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceResultType.kt delete mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceType.kt delete mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/FinalAttendance.kt delete mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/MidtermAttendance.kt delete mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/state/AttendanceProgressBarState.kt delete mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/state/TodayAttendanceCardState.kt delete mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/state/TodayNoAttendanceCardState.kt diff --git a/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt b/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt index 2ed6b2d57..1a27461e7 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt @@ -1,34 +1,26 @@ package org.sopt.official.feature.attendance import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch import org.sopt.official.domain.repository.attendance.AttendanceRepository import org.sopt.official.feature.attendance.model.AttendanceUiState import javax.inject.Inject @HiltViewModel class NewAttendanceViewModel @Inject constructor( - private val attendanceRepository: AttendanceRepository + private val attendanceRepository: AttendanceRepository, ) : ViewModel() { init { fetchData() } - private val _uiState: MutableStateFlow = MutableStateFlow(AttendanceUiState.Loading) + private val _uiState: MutableStateFlow = + MutableStateFlow(AttendanceUiState.Loading) val uiState: StateFlow = _uiState - private var fakeTitle: String = "" - - fun updateUiState() { - viewModelScope.launch { - _uiState.emit(AttendanceUiState.Success(fakeTitle)) - } - } fun fetchData() { fetchSoptEvent() diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt index 97e18ea45..30583e37e 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt @@ -5,8 +5,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -56,6 +54,6 @@ fun AttendanceRoute(onClickBackIcon: () -> Unit) { @Composable fun NewAttendanceViewModel.rememberAttendanceActions(): AttendanceAction = remember(this) { AttendanceAction( - onFakeClick = this::updateUiState + onFakeClick = {} ) } diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceScreen.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceScreen.kt index c686dcff4..e74e2b9bf 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceScreen.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceScreen.kt @@ -1,21 +1,209 @@ package org.sopt.official.feature.attendance.compose -import androidx.compose.foundation.layout.Arrangement + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton 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.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentMapOf +import org.sopt.official.R +import org.sopt.official.designsystem.Black40 +import org.sopt.official.designsystem.Gray60 +import org.sopt.official.designsystem.SoptTheme +import org.sopt.official.feature.attendance.compose.component.AttendanceGradientBox +import org.sopt.official.feature.attendance.compose.component.AttendanceHistoryCard +import org.sopt.official.feature.attendance.compose.component.AttendanceTopAppBar +import org.sopt.official.feature.attendance.compose.component.TodayAttendanceCard +import org.sopt.official.feature.attendance.compose.component.TodayNoAttendanceCard +import org.sopt.official.feature.attendance.compose.component.TodayNoScheduleCard import org.sopt.official.feature.attendance.model.AttendanceAction import org.sopt.official.feature.attendance.model.AttendanceUiState +import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType +import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.FinalAttendance +import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.MidtermAttendance +import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceHistory +import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceResultType @Composable fun AttendanceScreen(state: AttendanceUiState.Success, action: AttendanceAction) { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center + val scrollState = rememberScrollState() + Box( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 20.dp), + contentAlignment = Alignment.BottomCenter ) { - // TODO + Column( + modifier = Modifier.fillMaxHeight(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(Modifier.height(16.dp)) + when (state.attendanceDayType) { + is AttendanceDayType.AttendanceDay -> { + TodayAttendanceCard( + modifier = Modifier.fillMaxWidth(), + eventDate = state.attendanceDayType.eventDate, + eventLocation = state.attendanceDayType.eventLocation, + eventName = state.attendanceDayType.eventName, + firstAttendance = state.attendanceDayType.firstAttendance, + secondAttendance = state.attendanceDayType.secondAttendance, + finalAttendance = state.attendanceDayType.finalAttendance, + ) + } + + is AttendanceDayType.Event -> { + TodayNoAttendanceCard( + modifier = Modifier.fillMaxWidth(), + eventDate = state.attendanceDayType.eventDate, + eventLocation = state.attendanceDayType.eventLocation, + eventName = state.attendanceDayType.eventLocation, + ) + } + + AttendanceDayType.None -> { + TodayNoScheduleCard( + modifier = Modifier.fillMaxWidth() + ) + } + } + Spacer(Modifier.height(20.dp)) + AttendanceHistoryCard( + userTitle = state.userTitle, + attendanceScore = state.attendanceScore, + totalAttendanceResult = state.totalAttendanceResult, + attendanceHistoryList = state.attendanceHistoryList, + scrollState = scrollState, + ) + Spacer(Modifier.height(36.dp)) + } + AttendanceGradientBox() + TextButton( + onClick = { /*TODO*/ }, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 9.dp), + shape = RoundedCornerShape(size = 6.dp), + colors = ButtonColors( + containerColor = SoptTheme.colors.onSurface10, + contentColor = SoptTheme.colors.onSurface950, + disabledContainerColor = Black40, + disabledContentColor = Gray60, + ), + contentPadding = PaddingValues(vertical = 16.dp) + ) { + Text( + text = stringResource(R.string.attendance_dialog_button), + style = SoptTheme.typography.label18SB, + ) + } } } + +@Preview +@Composable +private fun AttendanceScreenPreview(@PreviewParameter(AttendanceScreenPreviewParameterProvider::class) parameter: AttendanceScreenPreviewParameter) { + SoptTheme { + Scaffold( + topBar = { + AttendanceTopAppBar( + onClickBackIcon = { }, + onClickRefreshIcon = { } + ) + } + ) { innerPaddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .background(color = SoptTheme.colors.background) + .padding(innerPaddingValues) + ) { + AttendanceScreen( + state = AttendanceUiState.Success( + attendanceDayType = parameter.attendanceDayType, + userTitle = "32기 디자인파트 김솝트", + attendanceScore = 1, + totalAttendanceResult = persistentMapOf( + Pair(AttendanceResultType.ALL, 16), + Pair(AttendanceResultType.PRESENT, 10), + Pair(AttendanceResultType.LATE, 4), + Pair(AttendanceResultType.ABSENT, 2) + ), + attendanceHistoryList = persistentListOf( + AttendanceHistory( + status = "출석", eventName = "1차 세미나", date = "00월 00일" + ), + AttendanceHistory( + status = "출석", eventName = "2차 세미나", date = "00월 00일" + ), + AttendanceHistory( + status = "출석", eventName = "3차 세미나", date = "00월 00일" + ), + AttendanceHistory( + status = "출석", eventName = "4차 세미나", date = "00월 00일" + ), + AttendanceHistory( + status = "출석", eventName = "5차 세미나", date = "00월 00일" + ), + AttendanceHistory( + status = "출석", eventName = "6차 세미나", date = "00월 00일" + ), + ), + ), + action = AttendanceAction(onFakeClick = {}) + ) + } + } + } +} + +data class AttendanceScreenPreviewParameter( + val attendanceDayType: AttendanceDayType, +) + + +class AttendanceScreenPreviewParameterProvider() : + PreviewParameterProvider { + + override val values: Sequence = sequenceOf( + AttendanceScreenPreviewParameter( + attendanceDayType = AttendanceDayType.AttendanceDay( + eventDate = "3월 23일 토요일 14:00 - 18:00", + eventLocation = "건국대학교 꽥꽥오리관", + eventName = "2차 세미나", + firstAttendance = MidtermAttendance.Present(attendanceAt = "14:00"), + secondAttendance = MidtermAttendance.Absent, + finalAttendance = FinalAttendance.LATE, + ) + ), AttendanceScreenPreviewParameter( + attendanceDayType = AttendanceDayType.Event( + eventDate = "3월 23일 토요일 14:00 - 18:00", + eventLocation = "건국대학교 꽥꽥오리관", + eventName = "2차 세미나", + ) + ), + AttendanceScreenPreviewParameter( + attendanceDayType = AttendanceDayType.None + ) + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceCodeDialog.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCodeDialog.kt similarity index 91% rename from app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceCodeDialog.kt rename to app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCodeDialog.kt index e8ece72ff..00568b876 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceCodeDialog.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCodeDialog.kt @@ -1,4 +1,4 @@ -package org.sopt.official.feature.attendance.compose +package org.sopt.official.feature.attendance.compose.component import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -28,14 +28,13 @@ import org.sopt.official.R import org.sopt.official.designsystem.Black40 import org.sopt.official.designsystem.Gray60 import org.sopt.official.designsystem.SoptTheme -import org.sopt.official.feature.attendance.compose.component.AttendanceCodeCardList -import org.sopt.official.feature.attendance.model.AttendanceType +import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.MidtermAttendance.NotYet.AttendanceSession @Composable fun AttendanceCodeDialog( codes: ImmutableList, inputCodes: ImmutableList, - attendanceType: AttendanceType, + attendanceSession: AttendanceSession, onDismissRequest: () -> Unit, modifier: Modifier = Modifier, ) { @@ -58,7 +57,7 @@ fun AttendanceCodeDialog( .clickable(onClick = onDismissRequest) ) Text( - text = stringResource(R.string.attendance_do, attendanceType.type), + text = stringResource(R.string.attendance_do, attendanceSession.type), style = SoptTheme.typography.heading18B, color = SoptTheme.colors.onSurface10 ) @@ -114,7 +113,7 @@ private fun AttendanceCodeDialogPreview( AttendanceCodeDialog( codes = parameter.codes, inputCodes = parameter.inputCodes, - attendanceType = parameter.attendanceType, + attendanceSession = parameter.attendanceSession, modifier = Modifier.fillMaxWidth(), onDismissRequest = {} ) @@ -124,7 +123,7 @@ private fun AttendanceCodeDialogPreview( data class AttendanceCodeDialogPreviewParameter( val codes: ImmutableList, val inputCodes: ImmutableList, - val attendanceType: AttendanceType, + val attendanceSession: AttendanceSession, ) class AttendanceCodeDialogPreviewParameterProvider : @@ -134,22 +133,22 @@ class AttendanceCodeDialogPreviewParameterProvider : AttendanceCodeDialogPreviewParameter( codes = persistentListOf("1", "2", "3", "4", "5"), inputCodes = persistentListOf("1", "2", "3", null, null), - AttendanceType.FIRST, + AttendanceSession.FIRST, ), AttendanceCodeDialogPreviewParameter( codes = persistentListOf("1", "2", "3", "4", "5"), inputCodes = persistentListOf("1", "2", "3", "4", "5"), - AttendanceType.FIRST, + AttendanceSession.FIRST, ), AttendanceCodeDialogPreviewParameter( codes = persistentListOf("1", "2", "3", "4", "5"), inputCodes = persistentListOf("1", "2", "3", null, null), - AttendanceType.SECOND, + AttendanceSession.SECOND, ), AttendanceCodeDialogPreviewParameter( codes = persistentListOf("1", "2", "3", "4", "5"), inputCodes = persistentListOf("1", "2", "3", "4", "5"), - AttendanceType.SECOND, + AttendanceSession.SECOND, ), ) } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCountCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCountCard.kt index 624de66f9..2dc1b69fe 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCountCard.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCountCard.kt @@ -16,7 +16,11 @@ import org.sopt.official.R import org.sopt.official.designsystem.SoptTheme @Composable -fun AttendanceCountCard(resultType: String, count: Int, modifier: Modifier = Modifier) { +fun AttendanceCountCard( + resultType: String, + count: Int, + modifier: Modifier = Modifier +) { Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) { Text( text = resultType, diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceGradientBox.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceGradientBox.kt new file mode 100644 index 000000000..b6b2a1d6a --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceGradientBox.kt @@ -0,0 +1,36 @@ +package org.sopt.official.feature.attendance.compose.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Composable +fun AttendanceGradientBox(modifier: Modifier = Modifier) { + Box( + modifier = modifier + .fillMaxWidth() + .height(148.dp) + .background( + brush = Brush.verticalGradient( + colors = listOf( + Color(0x000F1010), Color(0xFF0F1010) + ) + ) + ) + .padding(bottom = 41.dp) + ) +} + +@Preview +@Composable +private fun AttendanceGradientBoxPreview() { + AttendanceGradientBox() +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryCard.kt index b0f71e546..be41474d7 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryCard.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryCard.kt @@ -15,10 +15,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.google.common.collect.ImmutableList +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import org.sopt.official.designsystem.SoptTheme -import org.sopt.official.feature.attendance.model.AttendanceHistory -import org.sopt.official.feature.attendance.model.AttendanceResultType +import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceHistory +import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceResultType @Composable fun AttendanceHistoryCard( @@ -26,11 +27,15 @@ fun AttendanceHistoryCard( attendanceScore: Int, totalAttendanceResult: Map, attendanceHistoryList: ImmutableList, - scrollState: ScrollState, modifier: Modifier = Modifier + scrollState: ScrollState, + modifier: Modifier = Modifier, ) { Column( modifier = modifier - .background(color = SoptTheme.colors.onSurface800) + .background( + color = SoptTheme.colors.onSurface800, + shape = RoundedCornerShape(16.dp) + ) .padding(all = 32.dp) ) { AttendanceHistoryUserInfoCard( @@ -74,7 +79,7 @@ private fun AttendanceHistoryCardPreview() { Pair(AttendanceResultType.LATE, 0), Pair(AttendanceResultType.ABSENT, 11) ), - attendanceHistoryList = ImmutableList.of( + attendanceHistoryList = persistentListOf( AttendanceHistory( status = "출석", eventName = "1차 세미나", date = "00월 00일" ), diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryListCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryListCard.kt index 737dfb273..126ce0bf5 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryListCard.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryListCard.kt @@ -16,15 +16,16 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.google.common.collect.ImmutableList +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import org.sopt.official.designsystem.SoptTheme -import org.sopt.official.feature.attendance.model.AttendanceHistory +import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceHistory @Composable fun AttendanceHistoryListCard( attendanceHistoryList: ImmutableList, scrollState: ScrollState, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Column(modifier = modifier) { Text( @@ -74,7 +75,7 @@ fun AttendanceHistoryListCard( private fun AttendanceHistoryListCardPreview() { SoptTheme { AttendanceHistoryListCard( - attendanceHistoryList = ImmutableList.of( + attendanceHistoryList = persistentListOf( AttendanceHistory( status = "출석", eventName = "1차 세미나", date = "00월 00일" ), diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceProgressBar.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceProgressBar.kt index 217e21267..4dd011136 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceProgressBar.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceProgressBar.kt @@ -14,19 +14,23 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import org.sopt.official.designsystem.SoptTheme -import org.sopt.official.feature.attendance.model.AttendanceType -import org.sopt.official.feature.attendance.model.FinalAttendance -import org.sopt.official.feature.attendance.model.MidtermAttendance -import org.sopt.official.feature.attendance.model.state.AttendanceProgressBarState +import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.FinalAttendance +import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.MidtermAttendance +import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.MidtermAttendance.NotYet.AttendanceSession @Composable -fun AttendanceProgressBar(state: AttendanceProgressBarState, modifier: Modifier = Modifier) { +fun AttendanceProgressBar( + firstAttendance: MidtermAttendance, + secondAttendance: MidtermAttendance, + finalAttendance: FinalAttendance, + modifier: Modifier = Modifier +) { Box(modifier = modifier) { LinearProgressIndicator( progress = { calculateAttendanceProgress( - firstAttendance = state.firstAttendance, - secondAttendance = state.secondAttendance + firstAttendance = firstAttendance, + secondAttendance = secondAttendance ) }, modifier = Modifier @@ -41,13 +45,13 @@ fun AttendanceProgressBar(state: AttendanceProgressBarState, modifier: Modifier horizontalArrangement = Arrangement.SpaceBetween ) { MidtermAttendanceCard( - midtermAttendance = state.firstAttendance, + midtermAttendance = firstAttendance, ) MidtermAttendanceCard( - midtermAttendance = state.secondAttendance, + midtermAttendance = secondAttendance, ) FinalAttendanceCard( - finalAttendance = state.finalAttendance, + finalAttendance = finalAttendance, ) } } @@ -55,7 +59,7 @@ fun AttendanceProgressBar(state: AttendanceProgressBarState, modifier: Modifier fun calculateAttendanceProgress( firstAttendance: MidtermAttendance, - secondAttendance: MidtermAttendance + secondAttendance: MidtermAttendance, ): Float { if (!firstAttendance.isFinished) return 0f if (!secondAttendance.isFinished) { @@ -65,37 +69,48 @@ fun calculateAttendanceProgress( } } + +class AttendanceProgressBarPreviewParameter( + val firstAttendance: MidtermAttendance, + val secondAttendance: MidtermAttendance, + val finalAttendance: FinalAttendance, +) + class AttendanceProgressBarPreviewParameterProvider( - override val values: Sequence = sequenceOf( - AttendanceProgressBarState( - firstAttendance = MidtermAttendance.NotYet(AttendanceType.FIRST), - secondAttendance = MidtermAttendance.NotYet(AttendanceType.SECOND), + override val values: Sequence = sequenceOf( + AttendanceProgressBarPreviewParameter( + firstAttendance = MidtermAttendance.NotYet(AttendanceSession.FIRST), + secondAttendance = MidtermAttendance.NotYet(AttendanceSession.SECOND), finalAttendance = FinalAttendance.NOT_YET, ), - AttendanceProgressBarState( + AttendanceProgressBarPreviewParameter( firstAttendance = MidtermAttendance.Present(attendanceAt = "14:00"), secondAttendance = MidtermAttendance.Absent, finalAttendance = FinalAttendance.LATE, ), - AttendanceProgressBarState( + AttendanceProgressBarPreviewParameter( firstAttendance = MidtermAttendance.Present(attendanceAt = "14:00"), secondAttendance = MidtermAttendance.Present(attendanceAt = "16:00"), finalAttendance = FinalAttendance.PRESENT, ), - AttendanceProgressBarState( + AttendanceProgressBarPreviewParameter( firstAttendance = MidtermAttendance.Absent, secondAttendance = MidtermAttendance.Absent, finalAttendance = FinalAttendance.ABSENT, ), - ) -) : PreviewParameterProvider + ), +) : PreviewParameterProvider @Preview(showBackground = true) @Composable private fun AttendanceProgressBarPreview( - @PreviewParameter(AttendanceProgressBarPreviewParameterProvider::class) attendanceProgressBarState: AttendanceProgressBarState + @PreviewParameter(AttendanceProgressBarPreviewParameterProvider::class) attendanceProgressBarPreviewParameter: AttendanceProgressBarPreviewParameter, ) { SoptTheme { - AttendanceProgressBar(state = attendanceProgressBarState) + AttendanceProgressBar( + firstAttendance = attendanceProgressBarPreviewParameter.firstAttendance, + secondAttendance = attendanceProgressBarPreviewParameter.secondAttendance, + finalAttendance = attendanceProgressBarPreviewParameter.finalAttendance + ) } } diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/FinalAttendanceCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/FinalAttendanceCard.kt index 8260be5dc..4a10415ff 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/FinalAttendanceCard.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/FinalAttendanceCard.kt @@ -14,10 +14,13 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import org.sopt.official.designsystem.SoptTheme -import org.sopt.official.feature.attendance.model.FinalAttendance +import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.FinalAttendance @Composable -fun FinalAttendanceCard(finalAttendance: FinalAttendance, modifier: Modifier = Modifier) { +fun FinalAttendanceCard( + finalAttendance: FinalAttendance, + modifier: Modifier = Modifier +) { Column( modifier = modifier.padding(horizontal = 12.dp), horizontalAlignment = Alignment.CenterHorizontally, diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/MidtermAttendanceCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/MidtermAttendanceCard.kt index 9d93552eb..3c75cfeb0 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/MidtermAttendanceCard.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/MidtermAttendanceCard.kt @@ -13,11 +13,14 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import org.sopt.official.designsystem.SoptTheme -import org.sopt.official.feature.attendance.model.AttendanceType -import org.sopt.official.feature.attendance.model.MidtermAttendance +import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.MidtermAttendance +import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.MidtermAttendance.NotYet.AttendanceSession @Composable -fun MidtermAttendanceCard(midtermAttendance: MidtermAttendance, modifier: Modifier = Modifier) { +fun MidtermAttendanceCard( + midtermAttendance: MidtermAttendance, + modifier: Modifier = Modifier, +) { Column( modifier = modifier.padding(horizontal = 12.dp), horizontalAlignment = Alignment.CenterHorizontally, @@ -36,10 +39,10 @@ fun MidtermAttendanceCard(midtermAttendance: MidtermAttendance, modifier: Modifi class MidtermAttendanceCardPreviewParameterProvider( override val values: Sequence = sequenceOf( - MidtermAttendance.NotYet(attendanceType = AttendanceType.FIRST), + MidtermAttendance.NotYet(attendanceSession = AttendanceSession.FIRST), MidtermAttendance.Present(attendanceAt = "14:00"), MidtermAttendance.Absent, - ) + ), ) : PreviewParameterProvider @Preview(showBackground = true) diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayAttendanceCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayAttendanceCard.kt index 1957b5bea..89f8c649b 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayAttendanceCard.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayAttendanceCard.kt @@ -20,21 +20,26 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import org.sopt.official.R import org.sopt.official.designsystem.SoptTheme -import org.sopt.official.feature.attendance.model.FinalAttendance -import org.sopt.official.feature.attendance.model.MidtermAttendance -import org.sopt.official.feature.attendance.model.state.AttendanceProgressBarState -import org.sopt.official.feature.attendance.model.state.TodayAttendanceCardState +import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.FinalAttendance +import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.MidtermAttendance @Composable -fun TodayAttendanceCard(state: TodayAttendanceCardState, modifier: Modifier = Modifier) { +fun TodayAttendanceCard( + eventDate: String, + eventLocation: String, + eventName: String, + firstAttendance: MidtermAttendance, + secondAttendance: MidtermAttendance, + finalAttendance: FinalAttendance, + modifier: Modifier = Modifier, +) { Column( - modifier = modifier .background( color = SoptTheme.colors.onSurface800, shape = RoundedCornerShape(16.dp) ) - .padding(horizontal = 24.dp, vertical = 32.dp), + .padding(horizontal = 24.dp, vertical = 32.dp) ) { Row(verticalAlignment = Alignment.CenterVertically) { Icon( @@ -44,7 +49,7 @@ fun TodayAttendanceCard(state: TodayAttendanceCardState, modifier: Modifier = Mo ) Spacer(modifier = Modifier.width(4.dp)) Text( - text = state.eventDate, + text = eventDate, color = SoptTheme.colors.onSurface300, style = SoptTheme.typography.body14M ) @@ -58,7 +63,7 @@ fun TodayAttendanceCard(state: TodayAttendanceCardState, modifier: Modifier = Mo ) Spacer(modifier = Modifier.width(4.dp)) Text( - text = state.eventLocation, + text = eventLocation, color = SoptTheme.colors.onSurface300, style = SoptTheme.typography.body14M ) @@ -71,7 +76,7 @@ fun TodayAttendanceCard(state: TodayAttendanceCardState, modifier: Modifier = Mo style = SoptTheme.typography.body18M ) Text( - text = state.eventName, + text = eventName, color = SoptTheme.colors.onSurface10, style = SoptTheme.typography.body18M.copy(fontWeight = FontWeight.ExtraBold) ) @@ -83,11 +88,9 @@ fun TodayAttendanceCard(state: TodayAttendanceCardState, modifier: Modifier = Mo } Spacer(modifier = Modifier.height(12.dp)) AttendanceProgressBar( - state = AttendanceProgressBarState( - firstAttendance = state.firstAttendance, - secondAttendance = state.secondAttendance, - finalAttendance = state.finalAttendance, - ) + firstAttendance = firstAttendance, + secondAttendance = secondAttendance, + finalAttendance = finalAttendance, ) } } @@ -100,15 +103,12 @@ private fun TodayAttendanceCardPreview() { modifier = Modifier.background(color = SoptTheme.colors.background) ) { TodayAttendanceCard( - state = - TodayAttendanceCardState( - eventDate = "3월 23일 토요일 14:00 - 18:00", - eventLocation = "건국대학교 꽥꽥오리관", - eventName = "2차 세미나", - firstAttendance = MidtermAttendance.Present(attendanceAt = "14:00"), - secondAttendance = MidtermAttendance.Absent, - finalAttendance = FinalAttendance.LATE, - ), + eventDate = "3월 23일 토요일 14:00 - 18:00", + eventLocation = "건국대학교 꽥꽥오리관", + eventName = "2차 세미나", + firstAttendance = MidtermAttendance.Present(attendanceAt = "14:00"), + secondAttendance = MidtermAttendance.Absent, + finalAttendance = FinalAttendance.LATE, ) } } diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayNoAttendanceCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayNoAttendanceCard.kt index 448b418fd..f70e09d44 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayNoAttendanceCard.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayNoAttendanceCard.kt @@ -20,13 +20,16 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import org.sopt.official.R import org.sopt.official.designsystem.SoptTheme -import org.sopt.official.feature.attendance.model.state.TodayNoAttendanceCardState @Composable -fun TodayNoAttendanceCard(state: TodayNoAttendanceCardState, modifier: Modifier = Modifier) { +fun TodayNoAttendanceCard( + eventDate: String, + eventLocation: String, + eventName: String, + modifier: Modifier = Modifier, +) { Column( - modifier = - modifier + modifier = modifier .background( color = SoptTheme.colors.onSurface800, shape = RoundedCornerShape(16.dp) @@ -41,7 +44,7 @@ fun TodayNoAttendanceCard(state: TodayNoAttendanceCardState, modifier: Modifier ) Spacer(modifier = Modifier.width(4.dp)) Text( - text = state.eventDate, + text = eventDate, color = SoptTheme.colors.onSurface300, style = SoptTheme.typography.body14M ) @@ -55,7 +58,7 @@ fun TodayNoAttendanceCard(state: TodayNoAttendanceCardState, modifier: Modifier ) Spacer(modifier = Modifier.width(4.dp)) Text( - text = state.eventLocation, + text = eventLocation, color = SoptTheme.colors.onSurface300, style = SoptTheme.typography.body14M ) @@ -68,7 +71,7 @@ fun TodayNoAttendanceCard(state: TodayNoAttendanceCardState, modifier: Modifier style = SoptTheme.typography.body18M ) Text( - text = state.eventName, + text = eventName, color = SoptTheme.colors.onSurface10, style = SoptTheme.typography.body18M.copy(fontWeight = FontWeight.ExtraBold) ) @@ -93,12 +96,9 @@ private fun TodayNoAttendanceCardPreview() { SoptTheme { Column(modifier = Modifier.background(color = SoptTheme.colors.background)) { TodayNoAttendanceCard( - state = - TodayNoAttendanceCardState( - eventDate = "5월 12일 일요일 14:00 - 18:00", - eventLocation = "배달의민족주문~", - eventName = "데모데이", - ), + eventDate = "5월 12일 일요일 14:00 - 18:00", + eventLocation = "배달의민족주문~", + eventName = "데모데이", ) } } diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceHistory.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceHistory.kt deleted file mode 100644 index cc4744112..000000000 --- a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceHistory.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.sopt.official.feature.attendance.model - -data class AttendanceHistory( - val status: String, - val eventName: String, - val date: String, -) \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceResultType.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceResultType.kt deleted file mode 100644 index 4da216854..000000000 --- a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceResultType.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.sopt.official.feature.attendance.model - -enum class AttendanceResultType(val type: String) { - ALL(type = "전체"), - PRESENT(type = "출석"), - LATE(type = "지각"), - ABSENT(type = "결석"); -} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceType.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceType.kt deleted file mode 100644 index f3fcb23b6..000000000 --- a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceType.kt +++ /dev/null @@ -1,6 +0,0 @@ -package org.sopt.official.feature.attendance.model - -enum class AttendanceType(val type: String) { - FIRST("1차 출석"), - SECOND("2차 출석") -} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceUiState.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceUiState.kt index 366dedcde..6527c2c53 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceUiState.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceUiState.kt @@ -1,10 +1,110 @@ package org.sopt.official.feature.attendance.model +import androidx.annotation.DrawableRes +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableMap +import org.sopt.official.R + sealed interface AttendanceUiState { data object Loading : AttendanceUiState data class Success( - val fakeTitle: String - ) : AttendanceUiState + val attendanceDayType: AttendanceDayType, + val userTitle: String, + val attendanceScore: Int, + val totalAttendanceResult: ImmutableMap, + val attendanceHistoryList: ImmutableList, + ) : AttendanceUiState { + sealed interface AttendanceDayType { + /** 출석이 진행되는 날 **/ + data class AttendanceDay( + val eventDate: String, + val eventLocation: String, + val eventName: String, + val firstAttendance: MidtermAttendance, + val secondAttendance: MidtermAttendance, + val finalAttendance: FinalAttendance, + ) : AttendanceDayType { + sealed class MidtermAttendance( + @DrawableRes val imageResId: Int, + val isFinished: Boolean, + val description: String, + ) { + data class NotYet(val attendanceSession: AttendanceSession) : MidtermAttendance( + imageResId = R.drawable.ic_attendance_state_nothing, + isFinished = false, + description = attendanceSession.type + ) { + enum class AttendanceSession(val type: String) { + FIRST("1차 출석"), + SECOND("2차 출석") + } + } + + data class Present(val attendanceAt: String) : MidtermAttendance( + imageResId = R.drawable.ic_attendance_state_yes, + isFinished = true, + description = attendanceAt + ) + + data object Absent : MidtermAttendance( + imageResId = R.drawable.ic_attendance_state_absence_white, + isFinished = true, + description = "-" + ) + } + + enum class FinalAttendance( + @DrawableRes val imageResId: Int, + val isFinished: Boolean, + val result: String, + ) { + NOT_YET( + imageResId = R.drawable.ic_attendance_state_nothing, + isFinished = false, + result = "출석 전" + ), + PRESENT( + imageResId = R.drawable.ic_attendance_state_done, + isFinished = true, + result = "출석완료!" + ), + LATE( + imageResId = R.drawable.ic_attendance_state_late, + isFinished = true, + result = "지각" + ), + ABSENT( + imageResId = R.drawable.ic_attendance_state_absence_black, + isFinished = true, + result = "결석" + ) + } + } + + /** 출석할 필요가 없는 날 **/ + data class Event( + val eventDate: String, + val eventLocation: String, + val eventName: String, + ) : AttendanceDayType + + /** 아무 일정이 없는 날 **/ + data object None : AttendanceDayType + } + + enum class AttendanceResultType(val type: String) { + ALL(type = "전체"), + PRESENT(type = "출석"), + LATE(type = "지각"), + ABSENT(type = "결석"); + } + + data class AttendanceHistory( + val status: String, + val eventName: String, + val date: String, + ) + } data class Failure(val error: Throwable?) : AttendanceUiState data object NetworkError : AttendanceUiState diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/FinalAttendance.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/FinalAttendance.kt deleted file mode 100644 index 26b21d5c9..000000000 --- a/app/src/main/java/org/sopt/official/feature/attendance/model/FinalAttendance.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.sopt.official.feature.attendance.model - -import androidx.annotation.DrawableRes -import org.sopt.official.R - -enum class FinalAttendance( - @DrawableRes val imageResId: Int, - val isFinished: Boolean, - val result: String, -) { - NOT_YET( - imageResId = R.drawable.ic_attendance_state_nothing, - isFinished = false, - result = "출석 전" - ), - PRESENT( - imageResId = R.drawable.ic_attendance_state_done, - isFinished = true, - result = "출석완료!" - ), - LATE( - imageResId = R.drawable.ic_attendance_state_late, - isFinished = true, - result = "지각" - ), - ABSENT( - imageResId = R.drawable.ic_attendance_state_absence_black, - isFinished = true, - result = "결석" - ) -} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/MidtermAttendance.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/MidtermAttendance.kt deleted file mode 100644 index 699a7b641..000000000 --- a/app/src/main/java/org/sopt/official/feature/attendance/model/MidtermAttendance.kt +++ /dev/null @@ -1,28 +0,0 @@ -package org.sopt.official.feature.attendance.model - -import androidx.annotation.DrawableRes -import org.sopt.official.R - -sealed class MidtermAttendance( - @DrawableRes val imageResId: Int, - val isFinished: Boolean, - val description: String -) { - data class NotYet(val attendanceType: AttendanceType) : MidtermAttendance( - imageResId = R.drawable.ic_attendance_state_nothing, - isFinished = false, - description = attendanceType.type - ) - - data class Present(val attendanceAt: String) : MidtermAttendance( - imageResId = R.drawable.ic_attendance_state_yes, - isFinished = true, - description = attendanceAt - ) - - data object Absent : MidtermAttendance( - imageResId = R.drawable.ic_attendance_state_absence_white, - isFinished = true, - description = "-" - ) -} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/state/AttendanceProgressBarState.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/state/AttendanceProgressBarState.kt deleted file mode 100644 index 4c638b06a..000000000 --- a/app/src/main/java/org/sopt/official/feature/attendance/model/state/AttendanceProgressBarState.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.sopt.official.feature.attendance.model.state - -import org.sopt.official.feature.attendance.model.FinalAttendance -import org.sopt.official.feature.attendance.model.MidtermAttendance - -class AttendanceProgressBarState( - val firstAttendance: MidtermAttendance, - val secondAttendance: MidtermAttendance, - val finalAttendance: FinalAttendance, -) diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/state/TodayAttendanceCardState.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/state/TodayAttendanceCardState.kt deleted file mode 100644 index 0b2898a08..000000000 --- a/app/src/main/java/org/sopt/official/feature/attendance/model/state/TodayAttendanceCardState.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.sopt.official.feature.attendance.model.state - -import org.sopt.official.feature.attendance.model.FinalAttendance -import org.sopt.official.feature.attendance.model.MidtermAttendance - -class TodayAttendanceCardState( - val eventDate: String, - val eventLocation: String, - val eventName: String, - val firstAttendance: MidtermAttendance, - val secondAttendance: MidtermAttendance, - val finalAttendance: FinalAttendance, -) diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/state/TodayNoAttendanceCardState.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/state/TodayNoAttendanceCardState.kt deleted file mode 100644 index 4b1646867..000000000 --- a/app/src/main/java/org/sopt/official/feature/attendance/model/state/TodayNoAttendanceCardState.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.sopt.official.feature.attendance.model.state - -class TodayNoAttendanceCardState( - val eventDate: String, - val eventLocation: String, - val eventName: String, -) From 816168fb93cfda3b771becfedde41016bc9dbe96 Mon Sep 17 00:00:00 2001 From: Giovanni Junseo Kim Date: Fri, 10 Jan 2025 19:41:56 +0900 Subject: [PATCH 7/7] =?UTF-8?q?=EC=B6=9C=EC=84=9D=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0=20=EA=B5=AC=ED=98=84=20(#1?= =?UTF-8?q?015)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * AttendanceCodeDialog 구현 (#852) * Attendance screen compose setting (#676) * feat: implement NewAttendanceActivity * feat: implement NewAttendanceViewModel * chore: add compose-lifecycle dependency * feat: define AttendanceAction * feat: implement screens * feat: use SoptTheme in designsystem * chore: data class -> class로 변경 * chore: 람다 프로퍼티 이름 명시 * chore: SoptTheme darkTheme 기본값 사용 * chore: 필요없는 함수 제거 * chore: 구현 안 된 함수에 TODO 삽입 * chore: 동작하지 않는 Preview 제거 * chore: AttendanceAction 내 뷰모델 참조 제거 * chore: code format 변경 * chore: make stamp design system internal * chore: extract string resource * feat: implement AttendanceCodeCard * feat: implement AttendanceCodeCardList * chore: change logic * feat: implement AttendanceCodeDialog * feat: implement attendance button * chore: string resource 추출 * chore: change parameter List to ImmutableList * chore: reformat codee * feat: implement domain models * feat: implement Repository * chore: remove unnecessary code * chore: change attendanceScore to Float * chore: reflect type name changed * chore: remove totalCount from parameters * feat: add action onClickRefresh * feat: implement bindDefaultAttendanceRepository * chore: add background at route * chore: change button radius * chore: reformat code * feat: implement Domain Model to UiModel * chore: rename AttendanceInfo to Attendance * chore: rename SessionInfo * feat: format LocalDateTime * chore: use DateTimeFormatter * chore: use getOrNull * chore: don't use it * feat: implement AttendanceMapper * chore: remove duplicate code * chore: change to Extension * chore: change parameter name * chore: change code clear * chore: make code clear * chore: separate package * chore: change function name * feat: implement FinalAttendanceTest * chore: remove duplicate tests --- .../sopt/official/data/AttendanceMapper.kt | 84 ++++++++++ .../attendance/DefaultAttendanceRepository.kt | 73 +++++++++ .../di/attendance/AttendanceBindsModule.kt | 8 +- .../domain/entity/attendance/Attendance.kt | 111 +++++++++++++ .../attendance/ConfirmAttendanceCodeResult.kt | 6 + .../FetchAttendanceCurrentRoundResult.kt | 6 + .../attendance/NewAttendanceRepository.kt | 11 ++ .../feature/attendance/AttendanceMapper.kt | 67 ++++++++ .../attendance/NewAttendanceViewModel.kt | 33 ++-- .../compose/AttendanceCodeDialog.kt | 155 ++++++++++++++++++ .../attendance/compose/AttendanceRoute.kt | 9 +- .../attendance/compose/AttendanceScreen.kt | 22 ++- .../compose/component/AttendanceCodeDialog.kt | 2 +- .../component/AttendanceHistoryCard.kt | 4 +- .../AttendanceHistoryUserInfoCard.kt | 26 ++- .../component/AttendanceProgressBar.kt | 6 +- .../compose/component/FinalAttendanceCard.kt | 2 +- .../component/MidtermAttendanceCard.kt | 4 +- .../compose/component/TodayAttendanceCard.kt | 16 +- .../attendance/model/AttendanceAction.kt | 2 +- .../attendance/model/AttendanceDayType.kt | 77 +++++++++ .../attendance/model/AttendanceUiState.kt | 108 ++++-------- .../attendance/model/FinalAttendance.kt | 45 +++++ .../attendance/model/MidtermAttendance.kt | 34 ++++ .../org/sopt/official/FinalAttendanceTest.kt | 61 +++++++ 25 files changed, 835 insertions(+), 137 deletions(-) create mode 100644 app/src/main/java/org/sopt/official/data/AttendanceMapper.kt create mode 100644 app/src/main/java/org/sopt/official/data/repository/attendance/DefaultAttendanceRepository.kt create mode 100644 app/src/main/java/org/sopt/official/domain/entity/attendance/Attendance.kt create mode 100644 app/src/main/java/org/sopt/official/domain/entity/attendance/ConfirmAttendanceCodeResult.kt create mode 100644 app/src/main/java/org/sopt/official/domain/entity/attendance/FetchAttendanceCurrentRoundResult.kt create mode 100644 app/src/main/java/org/sopt/official/domain/repository/attendance/NewAttendanceRepository.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/AttendanceMapper.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceCodeDialog.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceDayType.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/FinalAttendance.kt create mode 100644 app/src/main/java/org/sopt/official/feature/attendance/model/MidtermAttendance.kt create mode 100644 app/src/test/java/org/sopt/official/FinalAttendanceTest.kt diff --git a/app/src/main/java/org/sopt/official/data/AttendanceMapper.kt b/app/src/main/java/org/sopt/official/data/AttendanceMapper.kt new file mode 100644 index 000000000..c5889ce67 --- /dev/null +++ b/app/src/main/java/org/sopt/official/data/AttendanceMapper.kt @@ -0,0 +1,84 @@ +package org.sopt.official.data + +import org.sopt.official.data.model.attendance.AttendanceHistoryResponse +import org.sopt.official.data.model.attendance.AttendanceHistoryResponse.AttendanceResponse +import org.sopt.official.data.model.attendance.SoptEventResponse +import org.sopt.official.domain.entity.attendance.Attendance +import org.sopt.official.domain.entity.attendance.Attendance.AttendanceDayType +import org.sopt.official.domain.entity.attendance.Attendance.AttendanceDayType.HasAttendance.RoundAttendance +import org.sopt.official.domain.entity.attendance.Attendance.AttendanceDayType.HasAttendance.RoundAttendance.RoundAttendanceState +import org.sopt.official.domain.entity.attendance.Attendance.Session +import org.sopt.official.domain.entity.attendance.Attendance.User.AttendanceLog.AttendanceState +import java.time.LocalDateTime + +fun mapToAttendance( + attendanceHistoryResponse: AttendanceHistoryResponse?, + soptEventResponse: SoptEventResponse? +): Attendance { + return Attendance( + sessionId = soptEventResponse?.id ?: Attendance.UNKNOWN_SESSION_ID, + user = Attendance.User( + name = attendanceHistoryResponse?.name ?: Attendance.User.UNKNOWN_NAME, + generation = attendanceHistoryResponse?.generation ?: Attendance.User.UNKNOWN_GENERATION, + part = Attendance.User.Part.valueOf(attendanceHistoryResponse?.part ?: Attendance.User.UNKNOWN_PART), + attendanceScore = attendanceHistoryResponse?.score ?: 0.0, + attendanceCount = Attendance.User.AttendanceCount( + attendanceCount = attendanceHistoryResponse?.attendanceCount?.normal ?: 0, + lateCount = attendanceHistoryResponse?.attendanceCount?.late ?: 0, + absenceCount = attendanceHistoryResponse?.attendanceCount?.abnormal ?: 0, + ), + attendanceHistory = attendanceHistoryResponse?.attendances?.map { attendanceResponse: AttendanceResponse -> + Attendance.User.AttendanceLog( + sessionName = attendanceResponse.eventName, + date = attendanceResponse.date, + attendanceState = AttendanceState.valueOf(attendanceResponse.attendanceState) + ) + } ?: emptyList(), + ), + attendanceDayType = soptEventResponse.toAttendanceDayType() + ) +} + +private fun SoptEventResponse?.toAttendanceDayType(): AttendanceDayType { + return when (this?.type) { + "HAS_ATTENDANCE" -> { + val firstAttendanceResponse: SoptEventResponse.AttendanceResponse? = attendances.getOrNull(0) + val secondAttendanceResponse: SoptEventResponse.AttendanceResponse? = attendances.getOrNull(1) + AttendanceDayType.HasAttendance( + session = Session( + name = eventName, + location = location.ifBlank { null }, + startAt = LocalDateTime.parse(startAt), + endAt = LocalDateTime.parse(endAt), + ), + firstRoundAttendance = RoundAttendance( + state = RoundAttendanceState.valueOf(firstAttendanceResponse?.status ?: RoundAttendanceState.NOT_YET.name), + attendedAt = LocalDateTime.parse(firstAttendanceResponse?.attendedAt), + ), + secondRoundAttendance = RoundAttendance( + state = RoundAttendanceState.valueOf(secondAttendanceResponse?.status ?: RoundAttendanceState.NOT_YET.name), + attendedAt = LocalDateTime.parse(secondAttendanceResponse?.attendedAt), + ), + ) + } + + "NO_ATTENDANCE" -> { + AttendanceDayType.NoAttendance( + session = Session( + name = eventName, + location = location.ifBlank { null }, + startAt = LocalDateTime.parse(startAt), + endAt = LocalDateTime.parse(endAt), + ) + ) + } + + "NO_SESSION" -> { + AttendanceDayType.NoSession + } + + else -> { + AttendanceDayType.NoSession + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/data/repository/attendance/DefaultAttendanceRepository.kt b/app/src/main/java/org/sopt/official/data/repository/attendance/DefaultAttendanceRepository.kt new file mode 100644 index 000000000..be4d3e1e3 --- /dev/null +++ b/app/src/main/java/org/sopt/official/data/repository/attendance/DefaultAttendanceRepository.kt @@ -0,0 +1,73 @@ +package org.sopt.official.data.repository.attendance + +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import org.sopt.official.data.mapToAttendance +import org.sopt.official.data.model.attendance.AttendanceHistoryResponse +import org.sopt.official.data.model.attendance.AttendanceRoundResponse +import org.sopt.official.data.model.attendance.RequestAttendanceCode +import org.sopt.official.data.model.attendance.SoptEventResponse +import org.sopt.official.data.service.attendance.AttendanceService +import org.sopt.official.domain.entity.attendance.Attendance +import org.sopt.official.domain.entity.attendance.ConfirmAttendanceCodeResult +import org.sopt.official.domain.entity.attendance.FetchAttendanceCurrentRoundResult +import org.sopt.official.domain.repository.attendance.NewAttendanceRepository +import retrofit2.HttpException +import javax.inject.Inject + +class DefaultAttendanceRepository @Inject constructor( + private val attendanceService: AttendanceService, + private val json: Json +) : NewAttendanceRepository { + override suspend fun fetchAttendanceInfo(): Attendance { + val soptEventResponse: SoptEventResponse? = runCatching { attendanceService.getSoptEvent().data }.getOrNull() + val attendanceHistoryResponse: AttendanceHistoryResponse? = + runCatching { attendanceService.getAttendanceHistory().data }.getOrNull() + + val attendance: Attendance = + mapToAttendance(attendanceHistoryResponse = attendanceHistoryResponse, soptEventResponse = soptEventResponse) + return attendance + } + + override suspend fun fetchAttendanceCurrentRound(lectureId: Long): FetchAttendanceCurrentRoundResult { + return runCatching { attendanceService.getAttendanceRound(lectureId).data }.fold( + onSuccess = { attendanceRoundResponse: AttendanceRoundResponse? -> + FetchAttendanceCurrentRoundResult.Success(attendanceRoundResponse?.round) + }, + onFailure = { error: Throwable -> + if (error !is HttpException) return FetchAttendanceCurrentRoundResult.Failure(null) + + val message: String? = error.jsonErrorMessage + FetchAttendanceCurrentRoundResult.Failure(message) + }, + ) + } + + override suspend fun confirmAttendanceCode( + subLectureId: Long, + code: String + ): ConfirmAttendanceCodeResult { + return runCatching { + attendanceService.confirmAttendanceCode(RequestAttendanceCode(subLectureId = subLectureId, code = code)) + }.fold( + onSuccess = { ConfirmAttendanceCodeResult.Success }, + onFailure = { error: Throwable -> + if (error !is HttpException) return ConfirmAttendanceCodeResult.Failure(null) + + val message: String? = error.jsonErrorMessage + ConfirmAttendanceCodeResult.Failure(message) + }, + ) + } + + private val HttpException.jsonErrorMessage: String? + get() { + val errorBody: String = this.response()?.errorBody()?.string() ?: return null + val jsonObject: JsonObject = json.parseToJsonElement(errorBody).jsonObject + val errorMessage: String? = jsonObject["message"]?.jsonPrimitive?.contentOrNull + return errorMessage + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/di/attendance/AttendanceBindsModule.kt b/app/src/main/java/org/sopt/official/di/attendance/AttendanceBindsModule.kt index bd754a504..cbce18ed1 100644 --- a/app/src/main/java/org/sopt/official/di/attendance/AttendanceBindsModule.kt +++ b/app/src/main/java/org/sopt/official/di/attendance/AttendanceBindsModule.kt @@ -29,12 +29,14 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton import org.sopt.official.common.di.OperationRetrofit import org.sopt.official.data.repository.attendance.AttendanceRepositoryImpl +import org.sopt.official.data.repository.attendance.DefaultAttendanceRepository import org.sopt.official.data.service.attendance.AttendanceService import org.sopt.official.domain.repository.attendance.AttendanceRepository +import org.sopt.official.domain.repository.attendance.NewAttendanceRepository import retrofit2.Retrofit +import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) @@ -43,6 +45,10 @@ abstract class AttendanceBindsModule { @Singleton abstract fun bindAttendanceRepository(attendanceRepositoryImpl: AttendanceRepositoryImpl): AttendanceRepository + @Binds + @Singleton + abstract fun bindDefaultAttendanceRepository(defaultAttendanceRepository: DefaultAttendanceRepository): NewAttendanceRepository + companion object { @Provides @Singleton diff --git a/app/src/main/java/org/sopt/official/domain/entity/attendance/Attendance.kt b/app/src/main/java/org/sopt/official/domain/entity/attendance/Attendance.kt new file mode 100644 index 000000000..2557529e4 --- /dev/null +++ b/app/src/main/java/org/sopt/official/domain/entity/attendance/Attendance.kt @@ -0,0 +1,111 @@ +package org.sopt.official.domain.entity.attendance + +import java.time.LocalDateTime + +data class Attendance( + val sessionId: Int, + val user: User, + val attendanceDayType: AttendanceDayType, +) { + data class User( + val name: String, + val generation: Int, + val part: Part, + val attendanceScore: Number, + val attendanceCount: AttendanceCount, + val attendanceHistory: List + ) { + enum class Part(val partName: String) { + PLAN("기획"), + DESIGN("디자인"), + ANDROID("안드로이드"), + IOS("iOS"), + WEB("웹"), + SERVER("서버"), + UNKNOWN("") + } + + data class AttendanceCount( + /** 출석 전체 횟수 */ + val attendanceCount: Int, + /** 지각 전체 횟수 */ + val lateCount: Int, + /** 결석 전체 횟수 */ + val absenceCount: Int, + ) { + /** 전체 횟수 */ + val totalCount: Int + get() = attendanceCount + lateCount + absenceCount + } + + data class AttendanceLog( + val sessionName: String, + val date: String, + val attendanceState: AttendanceState + ) { + enum class AttendanceState { + /** 참여(출석 체크 X)*/ + PARTICIPATE, + + /** 출석 */ + ATTENDANCE, + + /** 지각 */ + TARDY, + + /** 결석 */ + ABSENT + } + } + + companion object { + const val UNKNOWN_NAME = "회원" + const val UNKNOWN_GENERATION = -1 + const val UNKNOWN_PART = "UNKNOWN" + } + } + + sealed interface AttendanceDayType { + + /** 일정이 없는 날 */ + data object NoSession : AttendanceDayType + + /** 일정이 있고, 출석 체크가 있는 날 */ + data class HasAttendance( + val session: Session, + val firstRoundAttendance: RoundAttendance, + val secondRoundAttendance: RoundAttendance + ) : AttendanceDayType { + /** n차 출석에 관한 정보 */ + data class RoundAttendance( + val state: RoundAttendanceState, + val attendedAt: LocalDateTime? + ) { + /** n차 출석 상태 */ + enum class RoundAttendanceState { + ABSENT, ATTENDANCE, NOT_YET, + } + } + } + + /** 일정이 있고, 출석 체크가 없는 날 */ + data class NoAttendance(val session: Session) : AttendanceDayType + } + + /** 솝트의 세션에 관한 정보 + * @property name 세션 이름 (OT, 1차 세미나, 솝커톤 등) + * @property location 세션 장소, 정해진 장소가 없을 경우(온라인) null + * @property startAt 세션 시작 시각 + * @property endAt 세션 종료 시각 + * */ + data class Session( + val name: String, + val location: String?, + val startAt: LocalDateTime, + val endAt: LocalDateTime, + ) + + companion object { + const val UNKNOWN_SESSION_ID = -1 + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/domain/entity/attendance/ConfirmAttendanceCodeResult.kt b/app/src/main/java/org/sopt/official/domain/entity/attendance/ConfirmAttendanceCodeResult.kt new file mode 100644 index 000000000..5d0e690c0 --- /dev/null +++ b/app/src/main/java/org/sopt/official/domain/entity/attendance/ConfirmAttendanceCodeResult.kt @@ -0,0 +1,6 @@ +package org.sopt.official.domain.entity.attendance + +sealed interface ConfirmAttendanceCodeResult { + data object Success : ConfirmAttendanceCodeResult + data class Failure(val errorMessage: String?) : ConfirmAttendanceCodeResult +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/domain/entity/attendance/FetchAttendanceCurrentRoundResult.kt b/app/src/main/java/org/sopt/official/domain/entity/attendance/FetchAttendanceCurrentRoundResult.kt new file mode 100644 index 000000000..994badeb2 --- /dev/null +++ b/app/src/main/java/org/sopt/official/domain/entity/attendance/FetchAttendanceCurrentRoundResult.kt @@ -0,0 +1,6 @@ +package org.sopt.official.domain.entity.attendance + +sealed interface FetchAttendanceCurrentRoundResult { + data class Success(val round: Int?) : FetchAttendanceCurrentRoundResult + data class Failure(val errorMessage: String?) : FetchAttendanceCurrentRoundResult +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/domain/repository/attendance/NewAttendanceRepository.kt b/app/src/main/java/org/sopt/official/domain/repository/attendance/NewAttendanceRepository.kt new file mode 100644 index 000000000..bbf81add1 --- /dev/null +++ b/app/src/main/java/org/sopt/official/domain/repository/attendance/NewAttendanceRepository.kt @@ -0,0 +1,11 @@ +package org.sopt.official.domain.repository.attendance + +import org.sopt.official.domain.entity.attendance.Attendance +import org.sopt.official.domain.entity.attendance.ConfirmAttendanceCodeResult +import org.sopt.official.domain.entity.attendance.FetchAttendanceCurrentRoundResult + +interface NewAttendanceRepository { + suspend fun fetchAttendanceInfo(): Attendance + suspend fun fetchAttendanceCurrentRound(lectureId: Long): FetchAttendanceCurrentRoundResult + suspend fun confirmAttendanceCode(subLectureId: Long, code: String): ConfirmAttendanceCodeResult +} diff --git a/app/src/main/java/org/sopt/official/feature/attendance/AttendanceMapper.kt b/app/src/main/java/org/sopt/official/feature/attendance/AttendanceMapper.kt new file mode 100644 index 000000000..45b13a4df --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/AttendanceMapper.kt @@ -0,0 +1,67 @@ +package org.sopt.official.feature.attendance + +import kotlinx.collections.immutable.ImmutableMap +import kotlinx.collections.immutable.persistentMapOf +import org.sopt.official.domain.entity.attendance.Attendance +import org.sopt.official.domain.entity.attendance.Attendance.AttendanceDayType.HasAttendance.RoundAttendance.RoundAttendanceState +import org.sopt.official.feature.attendance.model.AttendanceDayType +import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceResultType +import org.sopt.official.feature.attendance.model.MidtermAttendance +import org.sopt.official.feature.attendance.model.MidtermAttendance.NotYet.AttendanceSession + +fun Attendance.AttendanceDayType.toUiAttendanceDayType(): AttendanceDayType { + return when (this) { + is Attendance.AttendanceDayType.HasAttendance -> { + AttendanceDayType.AttendanceDay.of( + session, + firstRoundAttendance, + secondRoundAttendance + ) + } + + is Attendance.AttendanceDayType.NoAttendance -> { + AttendanceDayType.Event.of(session) + } + + is Attendance.AttendanceDayType.NoSession -> { + AttendanceDayType.None + } + } +} + +fun Attendance.User.AttendanceCount.toTotalAttendanceResult(): ImmutableMap { + return persistentMapOf( + AttendanceResultType.ALL to totalCount, + AttendanceResultType.PRESENT to attendanceCount, + AttendanceResultType.LATE to lateCount, + AttendanceResultType.ABSENT to absenceCount, + ) +} + +fun Attendance.AttendanceDayType.HasAttendance.RoundAttendance.toUiFirstRoundAttendance(): MidtermAttendance { + return when (state) { + RoundAttendanceState.ATTENDANCE -> MidtermAttendance.Present( + attendanceAt = attendedAt.toString() + ) + + RoundAttendanceState.NOT_YET -> MidtermAttendance.NotYet( + attendanceSession = AttendanceSession.FIRST + ) + + RoundAttendanceState.ABSENT -> MidtermAttendance.Absent + } +} + +fun Attendance.AttendanceDayType.HasAttendance.RoundAttendance.toUiSecondRoundAttendance(): MidtermAttendance { + return when (state) { + RoundAttendanceState.ATTENDANCE -> MidtermAttendance.Present( + attendanceAt = attendedAt.toString() + ) + + RoundAttendanceState.NOT_YET -> MidtermAttendance.NotYet( + attendanceSession = AttendanceSession.SECOND + ) + + RoundAttendanceState.ABSENT -> MidtermAttendance.Absent + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt b/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt index 1a27461e7..4002fade6 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/NewAttendanceViewModel.kt @@ -1,37 +1,36 @@ package org.sopt.official.feature.attendance import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import org.sopt.official.domain.repository.attendance.AttendanceRepository +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import org.sopt.official.domain.entity.attendance.Attendance +import org.sopt.official.domain.repository.attendance.NewAttendanceRepository import org.sopt.official.feature.attendance.model.AttendanceUiState import javax.inject.Inject + @HiltViewModel class NewAttendanceViewModel @Inject constructor( - private val attendanceRepository: AttendanceRepository, + private val attendanceRepository: NewAttendanceRepository, ) : ViewModel() { init { - fetchData() + fetchAttendanceInfo() } - private val _uiState: MutableStateFlow = - MutableStateFlow(AttendanceUiState.Loading) + private val _uiState: MutableStateFlow = MutableStateFlow(AttendanceUiState.Loading) val uiState: StateFlow = _uiState - - fun fetchData() { - fetchSoptEvent() - fetchAttendanceHistory() - } - - private fun fetchSoptEvent() { - // TODO - } - - private fun fetchAttendanceHistory() { - // TODO + fun fetchAttendanceInfo() { + viewModelScope.launch { + val attendance: Attendance = attendanceRepository.fetchAttendanceInfo() + _uiState.update { + AttendanceUiState.Success.of(attendance) + } + } } } diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceCodeDialog.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceCodeDialog.kt new file mode 100644 index 000000000..8f557379f --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceCodeDialog.kt @@ -0,0 +1,155 @@ +package org.sopt.official.feature.attendance.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import org.sopt.official.R +import org.sopt.official.designsystem.Black40 +import org.sopt.official.designsystem.Gray60 +import org.sopt.official.designsystem.SoptTheme +import org.sopt.official.feature.attendance.compose.component.AttendanceCodeCardList +import org.sopt.official.feature.attendance.model.MidtermAttendance.NotYet.AttendanceSession + +@Composable +fun AttendanceCodeDialog( + codes: ImmutableList, + inputCodes: ImmutableList, + attendanceType: AttendanceSession, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, +) { + Dialog(onDismissRequest = onDismissRequest) { + Column( + modifier + .background( + color = SoptTheme.colors.onSurface700, + shape = RoundedCornerShape(size = 10.dp) + ) + .padding(all = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + painter = painterResource(id = R.drawable.ic_close), + contentDescription = stringResource(id = R.string.close), + tint = SoptTheme.colors.onSurface10, + modifier = Modifier + .align(Alignment.End) + .clickable(onClick = onDismissRequest) + ) + Text( + text = stringResource(R.string.attendance_do, attendanceType.type), + style = SoptTheme.typography.heading18B, + color = SoptTheme.colors.onSurface10 + ) + Spacer(modifier = Modifier.height(10.dp)) + Text( + text = stringResource(R.string.attendance_code_description), + style = SoptTheme.typography.body13M, + color = SoptTheme.colors.onSurface300 + ) + Spacer(modifier = Modifier.height(24.dp)) + AttendanceCodeCardList( + codes = inputCodes, + onTextChange = {}, + onTextFieldFull = {}, + ) + if (codes != inputCodes) { + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = stringResource(R.string.attendance_code_does_not_match), + style = SoptTheme.typography.label12SB, + color = SoptTheme.colors.error + ) + } + Spacer(modifier = Modifier.height(32.dp)) + Button( + onClick = { /*TODO*/ }, + modifier = Modifier + .fillMaxWidth(), + shape = RoundedCornerShape(size = 6.dp), + colors = ButtonColors( + containerColor = SoptTheme.colors.onSurface10, + contentColor = SoptTheme.colors.onSurface950, + disabledContainerColor = Black40, + disabledContentColor = Gray60, + ), + enabled = codes == inputCodes + ) { + Text( + text = stringResource(R.string.attendance_dialog_button), + style = SoptTheme.typography.body13M, + ) + } + } + } +} + +@Preview +@Composable +private fun AttendanceCodeDialogPreview( + @PreviewParameter(AttendanceCodeDialogPreviewParameterProvider::class) parameter: AttendanceCodeDialogPreviewParameter, +) { + SoptTheme { + AttendanceCodeDialog( + codes = parameter.codes, + inputCodes = parameter.inputCodes, + attendanceType = parameter.attendanceType, + modifier = Modifier.fillMaxWidth(), + onDismissRequest = {} + ) + } +} + +data class AttendanceCodeDialogPreviewParameter( + val codes: ImmutableList, + val inputCodes: ImmutableList, + val attendanceType: AttendanceSession, +) + +class AttendanceCodeDialogPreviewParameterProvider : + PreviewParameterProvider { + override val values: Sequence = + sequenceOf( + AttendanceCodeDialogPreviewParameter( + codes = persistentListOf("1", "2", "3", "4", "5"), + inputCodes = persistentListOf("1", "2", "3", null, null), + AttendanceSession.FIRST, + ), + AttendanceCodeDialogPreviewParameter( + codes = persistentListOf("1", "2", "3", "4", "5"), + inputCodes = persistentListOf("1", "2", "3", "4", "5"), + AttendanceSession.FIRST, + ), + AttendanceCodeDialogPreviewParameter( + codes = persistentListOf("1", "2", "3", "4", "5"), + inputCodes = persistentListOf("1", "2", "3", null, null), + AttendanceSession.SECOND, + ), + AttendanceCodeDialogPreviewParameter( + codes = persistentListOf("1", "2", "3", "4", "5"), + inputCodes = persistentListOf("1", "2", "3", "4", "5"), + AttendanceSession.SECOND, + ), + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt index 30583e37e..057188948 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceRoute.kt @@ -1,9 +1,9 @@ package org.sopt.official.feature.attendance.compose +import androidx.compose.foundation.background 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.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -11,12 +11,12 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel +import org.sopt.official.designsystem.SoptTheme import org.sopt.official.feature.attendance.NewAttendanceViewModel import org.sopt.official.feature.attendance.compose.component.AttendanceTopAppBar import org.sopt.official.feature.attendance.model.AttendanceAction import org.sopt.official.feature.attendance.model.AttendanceUiState -@OptIn(ExperimentalMaterial3Api::class) @Composable fun AttendanceRoute(onClickBackIcon: () -> Unit) { val viewModel: NewAttendanceViewModel = viewModel() @@ -27,13 +27,14 @@ fun AttendanceRoute(onClickBackIcon: () -> Unit) { topBar = { AttendanceTopAppBar( onClickBackIcon = onClickBackIcon, - onClickRefreshIcon = viewModel::fetchData, + onClickRefreshIcon = action.onClickRefresh, ) } ) { innerPaddingValues -> Column( modifier = Modifier .fillMaxSize() + .background(color = SoptTheme.colors.background) .padding(innerPaddingValues) ) { when (state) { @@ -54,6 +55,6 @@ fun AttendanceRoute(onClickBackIcon: () -> Unit) { @Composable fun NewAttendanceViewModel.rememberAttendanceActions(): AttendanceAction = remember(this) { AttendanceAction( - onFakeClick = {} + onClickRefresh = ::fetchAttendanceInfo ) } diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceScreen.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceScreen.kt index e74e2b9bf..c1d20cb88 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceScreen.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/AttendanceScreen.kt @@ -38,12 +38,11 @@ import org.sopt.official.feature.attendance.compose.component.TodayAttendanceCar import org.sopt.official.feature.attendance.compose.component.TodayNoAttendanceCard import org.sopt.official.feature.attendance.compose.component.TodayNoScheduleCard import org.sopt.official.feature.attendance.model.AttendanceAction +import org.sopt.official.feature.attendance.model.AttendanceDayType import org.sopt.official.feature.attendance.model.AttendanceUiState -import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType -import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.FinalAttendance -import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.MidtermAttendance import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceHistory import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceResultType +import org.sopt.official.feature.attendance.model.MidtermAttendance @Composable fun AttendanceScreen(state: AttendanceUiState.Success, action: AttendanceAction) { @@ -66,8 +65,8 @@ fun AttendanceScreen(state: AttendanceUiState.Success, action: AttendanceAction) eventDate = state.attendanceDayType.eventDate, eventLocation = state.attendanceDayType.eventLocation, eventName = state.attendanceDayType.eventName, - firstAttendance = state.attendanceDayType.firstAttendance, - secondAttendance = state.attendanceDayType.secondAttendance, + firstRoundAttendance = state.attendanceDayType.firstRoundAttendance, + secondRoundAttendance = state.attendanceDayType.secondRoundAttendance, finalAttendance = state.attendanceDayType.finalAttendance, ) } @@ -103,7 +102,7 @@ fun AttendanceScreen(state: AttendanceUiState.Success, action: AttendanceAction) modifier = Modifier .fillMaxWidth() .padding(bottom = 9.dp), - shape = RoundedCornerShape(size = 6.dp), + shape = RoundedCornerShape(size = 12.dp), colors = ButtonColors( containerColor = SoptTheme.colors.onSurface10, contentColor = SoptTheme.colors.onSurface950, @@ -142,7 +141,7 @@ private fun AttendanceScreenPreview(@PreviewParameter(AttendanceScreenPreviewPar state = AttendanceUiState.Success( attendanceDayType = parameter.attendanceDayType, userTitle = "32기 디자인파트 김솝트", - attendanceScore = 1, + attendanceScore = 2f, totalAttendanceResult = persistentMapOf( Pair(AttendanceResultType.ALL, 16), Pair(AttendanceResultType.PRESENT, 10), @@ -170,7 +169,7 @@ private fun AttendanceScreenPreview(@PreviewParameter(AttendanceScreenPreviewPar ), ), ), - action = AttendanceAction(onFakeClick = {}) + action = AttendanceAction(onClickRefresh = {}) ) } } @@ -182,7 +181,7 @@ data class AttendanceScreenPreviewParameter( ) -class AttendanceScreenPreviewParameterProvider() : +class AttendanceScreenPreviewParameterProvider : PreviewParameterProvider { override val values: Sequence = sequenceOf( @@ -191,9 +190,8 @@ class AttendanceScreenPreviewParameterProvider() : eventDate = "3월 23일 토요일 14:00 - 18:00", eventLocation = "건국대학교 꽥꽥오리관", eventName = "2차 세미나", - firstAttendance = MidtermAttendance.Present(attendanceAt = "14:00"), - secondAttendance = MidtermAttendance.Absent, - finalAttendance = FinalAttendance.LATE, + firstRoundAttendance = MidtermAttendance.Present(attendanceAt = "14:00"), + secondRoundAttendance = MidtermAttendance.Absent, ) ), AttendanceScreenPreviewParameter( attendanceDayType = AttendanceDayType.Event( diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCodeDialog.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCodeDialog.kt index 00568b876..40c06029d 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCodeDialog.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceCodeDialog.kt @@ -28,7 +28,7 @@ import org.sopt.official.R import org.sopt.official.designsystem.Black40 import org.sopt.official.designsystem.Gray60 import org.sopt.official.designsystem.SoptTheme -import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.MidtermAttendance.NotYet.AttendanceSession +import org.sopt.official.feature.attendance.model.MidtermAttendance.NotYet.AttendanceSession @Composable fun AttendanceCodeDialog( diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryCard.kt index be41474d7..248623434 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryCard.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryCard.kt @@ -24,7 +24,7 @@ import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.Atte @Composable fun AttendanceHistoryCard( userTitle: String, - attendanceScore: Int, + attendanceScore: Float, totalAttendanceResult: Map, attendanceHistoryList: ImmutableList, scrollState: ScrollState, @@ -72,7 +72,7 @@ private fun AttendanceHistoryCardPreview() { SoptTheme { AttendanceHistoryCard( userTitle = "32기 디자인파트 김솝트", - attendanceScore = 1, + attendanceScore = 1f, totalAttendanceResult = mapOf( Pair(AttendanceResultType.ALL, 16), Pair(AttendanceResultType.PRESENT, 5), diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryUserInfoCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryUserInfoCard.kt index edb08a6d0..191aa044e 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryUserInfoCard.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceHistoryUserInfoCard.kt @@ -13,6 +13,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import org.sopt.official.R import org.sopt.official.designsystem.Orange400 @@ -21,7 +23,7 @@ import org.sopt.official.designsystem.SoptTheme @Composable fun AttendanceHistoryUserInfoCard( userTitle: String, - attendanceScore: Int, + attendanceScore: Float, modifier: Modifier = Modifier ) { Column(modifier = modifier) { @@ -38,7 +40,7 @@ fun AttendanceHistoryUserInfoCard( style = SoptTheme.typography.body18M ) Text( - text = "${attendanceScore}점", + text = "${attendanceScore.prettyString}점", color = Orange400, style = SoptTheme.typography.title20SB ) @@ -60,12 +62,26 @@ fun AttendanceHistoryUserInfoCard( @Preview @Composable -fun AttendanceHistoryUserInfoCardPreview() { +fun AttendanceHistoryUserInfoCardPreview( + @PreviewParameter(AttendanceHistoryUserInfoCardPreviewParameter::class) previewParameter: Float +) { SoptTheme { AttendanceHistoryUserInfoCard( userTitle = "32기 디자인파트 김솝트", - attendanceScore = 1, + attendanceScore = previewParameter, modifier = Modifier.background(color = SoptTheme.colors.onSurface800) ) } -} \ No newline at end of file +} + +class AttendanceHistoryUserInfoCardPreviewParameter(override val values: Sequence = sequenceOf(-0.5f, 0f, 0.5f, 1f, 1.5f, 2f)) : + PreviewParameterProvider + +private val Float.prettyString: String + get() { + return if (this == this.toInt().toFloat()) { + this.toInt().toString() + } else { + this.toString() + } + } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceProgressBar.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceProgressBar.kt index 4dd011136..738d49685 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceProgressBar.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/AttendanceProgressBar.kt @@ -14,9 +14,9 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import org.sopt.official.designsystem.SoptTheme -import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.FinalAttendance -import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.MidtermAttendance -import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.MidtermAttendance.NotYet.AttendanceSession +import org.sopt.official.feature.attendance.model.FinalAttendance +import org.sopt.official.feature.attendance.model.MidtermAttendance +import org.sopt.official.feature.attendance.model.MidtermAttendance.NotYet.AttendanceSession @Composable fun AttendanceProgressBar( diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/FinalAttendanceCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/FinalAttendanceCard.kt index 4a10415ff..30635b56c 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/FinalAttendanceCard.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/FinalAttendanceCard.kt @@ -14,7 +14,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import org.sopt.official.designsystem.SoptTheme -import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.FinalAttendance +import org.sopt.official.feature.attendance.model.FinalAttendance @Composable fun FinalAttendanceCard( diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/MidtermAttendanceCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/MidtermAttendanceCard.kt index 3c75cfeb0..e0aa98641 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/MidtermAttendanceCard.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/MidtermAttendanceCard.kt @@ -13,8 +13,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import org.sopt.official.designsystem.SoptTheme -import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.MidtermAttendance -import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.MidtermAttendance.NotYet.AttendanceSession +import org.sopt.official.feature.attendance.model.MidtermAttendance +import org.sopt.official.feature.attendance.model.MidtermAttendance.NotYet.AttendanceSession @Composable fun MidtermAttendanceCard( diff --git a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayAttendanceCard.kt b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayAttendanceCard.kt index 89f8c649b..33b09c439 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayAttendanceCard.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/compose/component/TodayAttendanceCard.kt @@ -20,16 +20,16 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import org.sopt.official.R import org.sopt.official.designsystem.SoptTheme -import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.FinalAttendance -import org.sopt.official.feature.attendance.model.AttendanceUiState.Success.AttendanceDayType.AttendanceDay.MidtermAttendance +import org.sopt.official.feature.attendance.model.FinalAttendance +import org.sopt.official.feature.attendance.model.MidtermAttendance @Composable fun TodayAttendanceCard( eventDate: String, eventLocation: String, eventName: String, - firstAttendance: MidtermAttendance, - secondAttendance: MidtermAttendance, + firstRoundAttendance: MidtermAttendance, + secondRoundAttendance: MidtermAttendance, finalAttendance: FinalAttendance, modifier: Modifier = Modifier, ) { @@ -88,8 +88,8 @@ fun TodayAttendanceCard( } Spacer(modifier = Modifier.height(12.dp)) AttendanceProgressBar( - firstAttendance = firstAttendance, - secondAttendance = secondAttendance, + firstAttendance = firstRoundAttendance, + secondAttendance = secondRoundAttendance, finalAttendance = finalAttendance, ) } @@ -106,8 +106,8 @@ private fun TodayAttendanceCardPreview() { eventDate = "3월 23일 토요일 14:00 - 18:00", eventLocation = "건국대학교 꽥꽥오리관", eventName = "2차 세미나", - firstAttendance = MidtermAttendance.Present(attendanceAt = "14:00"), - secondAttendance = MidtermAttendance.Absent, + firstRoundAttendance = MidtermAttendance.Present(attendanceAt = "14:00"), + secondRoundAttendance = MidtermAttendance.Absent, finalAttendance = FinalAttendance.LATE, ) } diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceAction.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceAction.kt index 6fdba104b..339881408 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceAction.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceAction.kt @@ -1,5 +1,5 @@ package org.sopt.official.feature.attendance.model class AttendanceAction( - val onFakeClick: () -> Unit + val onClickRefresh: () -> Unit ) diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceDayType.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceDayType.kt new file mode 100644 index 000000000..89fb1596e --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceDayType.kt @@ -0,0 +1,77 @@ +package org.sopt.official.feature.attendance.model + +import org.sopt.official.domain.entity.attendance.Attendance +import org.sopt.official.feature.attendance.toUiFirstRoundAttendance +import org.sopt.official.feature.attendance.toUiSecondRoundAttendance +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.time.format.TextStyle +import java.util.Locale + +sealed interface AttendanceDayType { + /** 출석이 진행되는 날 **/ + data class AttendanceDay( + val eventDate: String, + val eventLocation: String, + val eventName: String, + val firstRoundAttendance: MidtermAttendance, + val secondRoundAttendance: MidtermAttendance, + ) : AttendanceDayType { + val finalAttendance: FinalAttendance = + FinalAttendance.calculateFinalAttendance(firstRoundAttendance, secondRoundAttendance) + + companion object { + fun of( + session: Attendance.Session, + firstRoundAttendance: Attendance.AttendanceDayType.HasAttendance.RoundAttendance, + secondRoundAttendance: Attendance.AttendanceDayType.HasAttendance.RoundAttendance + ): AttendanceDay { + return AttendanceDay( + eventDate = formatSessionTime(session.startAt, session.endAt), + eventLocation = session.location ?: "장소 정보를 불러올 수 없습니다.", + eventName = session.name, + firstRoundAttendance = firstRoundAttendance.toUiFirstRoundAttendance(), + secondRoundAttendance = secondRoundAttendance.toUiSecondRoundAttendance(), + ) + } + } + } + + /** 출석할 필요가 없는 날 **/ + data class Event( + val eventDate: String, + val eventLocation: String, + val eventName: String, + ) : AttendanceDayType { + companion object { + fun of(session: Attendance.Session): Event { + return Event( + eventDate = formatSessionTime(session.startAt, session.endAt), + eventLocation = session.location ?: "장소 정보를 불러올 수 없습니다.", + eventName = session.name + ) + } + } + } + + /** 아무 일정이 없는 날 **/ + data object None : AttendanceDayType +} + +private fun formatSessionTime(startAt: LocalDateTime, endAt: LocalDateTime): String { + val dateFormatter = DateTimeFormatter.ofPattern("M월 d일") + val timeFormatter = DateTimeFormatter.ofPattern("HH:mm") + + return "${startAt.format(dateFormatter)} ${ + startAt.dayOfWeek.getDisplayName( + TextStyle.FULL, Locale.KOREAN + ) + } ${ + startAt.format( + timeFormatter + ) + } - " + if (startAt.toLocalDate() == endAt.toLocalDate()) endAt.format(timeFormatter) + else "${endAt.format(dateFormatter)} ${ + endAt.dayOfWeek.getDisplayName(TextStyle.FULL, Locale.KOREAN) + } ${endAt.format(timeFormatter)}" +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceUiState.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceUiState.kt index 6527c2c53..d2205367f 100644 --- a/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceUiState.kt +++ b/app/src/main/java/org/sopt/official/feature/attendance/model/AttendanceUiState.kt @@ -1,96 +1,21 @@ package org.sopt.official.feature.attendance.model -import androidx.annotation.DrawableRes import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableMap -import org.sopt.official.R +import kotlinx.collections.immutable.toPersistentList +import org.sopt.official.domain.entity.attendance.Attendance +import org.sopt.official.feature.attendance.toTotalAttendanceResult +import org.sopt.official.feature.attendance.toUiAttendanceDayType sealed interface AttendanceUiState { data object Loading : AttendanceUiState data class Success( val attendanceDayType: AttendanceDayType, val userTitle: String, - val attendanceScore: Int, + val attendanceScore: Float, val totalAttendanceResult: ImmutableMap, val attendanceHistoryList: ImmutableList, ) : AttendanceUiState { - sealed interface AttendanceDayType { - /** 출석이 진행되는 날 **/ - data class AttendanceDay( - val eventDate: String, - val eventLocation: String, - val eventName: String, - val firstAttendance: MidtermAttendance, - val secondAttendance: MidtermAttendance, - val finalAttendance: FinalAttendance, - ) : AttendanceDayType { - sealed class MidtermAttendance( - @DrawableRes val imageResId: Int, - val isFinished: Boolean, - val description: String, - ) { - data class NotYet(val attendanceSession: AttendanceSession) : MidtermAttendance( - imageResId = R.drawable.ic_attendance_state_nothing, - isFinished = false, - description = attendanceSession.type - ) { - enum class AttendanceSession(val type: String) { - FIRST("1차 출석"), - SECOND("2차 출석") - } - } - - data class Present(val attendanceAt: String) : MidtermAttendance( - imageResId = R.drawable.ic_attendance_state_yes, - isFinished = true, - description = attendanceAt - ) - - data object Absent : MidtermAttendance( - imageResId = R.drawable.ic_attendance_state_absence_white, - isFinished = true, - description = "-" - ) - } - - enum class FinalAttendance( - @DrawableRes val imageResId: Int, - val isFinished: Boolean, - val result: String, - ) { - NOT_YET( - imageResId = R.drawable.ic_attendance_state_nothing, - isFinished = false, - result = "출석 전" - ), - PRESENT( - imageResId = R.drawable.ic_attendance_state_done, - isFinished = true, - result = "출석완료!" - ), - LATE( - imageResId = R.drawable.ic_attendance_state_late, - isFinished = true, - result = "지각" - ), - ABSENT( - imageResId = R.drawable.ic_attendance_state_absence_black, - isFinished = true, - result = "결석" - ) - } - } - - /** 출석할 필요가 없는 날 **/ - data class Event( - val eventDate: String, - val eventLocation: String, - val eventName: String, - ) : AttendanceDayType - - /** 아무 일정이 없는 날 **/ - data object None : AttendanceDayType - } enum class AttendanceResultType(val type: String) { ALL(type = "전체"), @@ -104,6 +29,29 @@ sealed interface AttendanceUiState { val eventName: String, val date: String, ) + + companion object { + fun of(attendance: Attendance): Success { + return Success( + attendanceDayType = attendance.attendanceDayType.toUiAttendanceDayType(), + userTitle = "${attendance.user.generation}기 ${attendance.user.part.partName}파트 ${attendance.user.name}", + attendanceScore = attendance.user.attendanceScore.toFloat(), + totalAttendanceResult = attendance.user.attendanceCount.toTotalAttendanceResult(), + attendanceHistoryList = attendance.user.attendanceHistory.map { attendanceLog: Attendance.User.AttendanceLog -> + AttendanceHistory( + status = when (attendanceLog.attendanceState) { + Attendance.User.AttendanceLog.AttendanceState.PARTICIPATE -> "참여" + Attendance.User.AttendanceLog.AttendanceState.ATTENDANCE -> "출석" + Attendance.User.AttendanceLog.AttendanceState.TARDY -> "지각" + Attendance.User.AttendanceLog.AttendanceState.ABSENT -> "결석" + }, + eventName = attendanceLog.sessionName, + date = attendanceLog.date + ) + }.toPersistentList() + ) + } + } } data class Failure(val error: Throwable?) : AttendanceUiState diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/FinalAttendance.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/FinalAttendance.kt new file mode 100644 index 000000000..79dc8d29b --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/model/FinalAttendance.kt @@ -0,0 +1,45 @@ +package org.sopt.official.feature.attendance.model + +import androidx.annotation.DrawableRes +import org.sopt.official.R + +enum class FinalAttendance( + @DrawableRes val imageResId: Int, + val isFinished: Boolean, + val result: String, +) { + NOT_YET( + imageResId = R.drawable.ic_attendance_state_nothing, + isFinished = false, + result = "출석 전" + ), + PRESENT( + imageResId = R.drawable.ic_attendance_state_done, + isFinished = true, + result = "출석완료!" + ), + LATE( + imageResId = R.drawable.ic_attendance_state_late, + isFinished = true, + result = "지각" + ), + ABSENT( + imageResId = R.drawable.ic_attendance_state_absence_black, + isFinished = true, + result = "결석" + ); + + companion object { + fun calculateFinalAttendance( + firstAttendance: MidtermAttendance, + secondAttendance: MidtermAttendance, + ): FinalAttendance { + return when { + firstAttendance is MidtermAttendance.NotYet || secondAttendance is MidtermAttendance.NotYet -> NOT_YET + firstAttendance is MidtermAttendance.Present && secondAttendance is MidtermAttendance.Present -> PRESENT + firstAttendance is MidtermAttendance.Absent && secondAttendance is MidtermAttendance.Absent -> ABSENT + else -> LATE + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/official/feature/attendance/model/MidtermAttendance.kt b/app/src/main/java/org/sopt/official/feature/attendance/model/MidtermAttendance.kt new file mode 100644 index 000000000..97e42b948 --- /dev/null +++ b/app/src/main/java/org/sopt/official/feature/attendance/model/MidtermAttendance.kt @@ -0,0 +1,34 @@ +package org.sopt.official.feature.attendance.model + +import androidx.annotation.DrawableRes +import org.sopt.official.R + + +sealed class MidtermAttendance private constructor( + @DrawableRes val imageResId: Int, + val isFinished: Boolean, + val description: String, +) { + data class NotYet(val attendanceSession: AttendanceSession) : MidtermAttendance( + imageResId = R.drawable.ic_attendance_state_nothing, + isFinished = false, + description = attendanceSession.type + ) { + enum class AttendanceSession(val type: String) { + FIRST("1차 출석"), + SECOND("2차 출석") + } + } + + data class Present(val attendanceAt: String) : MidtermAttendance( + imageResId = R.drawable.ic_attendance_state_yes, + isFinished = true, + description = attendanceAt + ) + + data object Absent : MidtermAttendance( + imageResId = R.drawable.ic_attendance_state_absence_white, + isFinished = true, + description = "-" + ) +} \ No newline at end of file diff --git a/app/src/test/java/org/sopt/official/FinalAttendanceTest.kt b/app/src/test/java/org/sopt/official/FinalAttendanceTest.kt new file mode 100644 index 000000000..0b53e57bc --- /dev/null +++ b/app/src/test/java/org/sopt/official/FinalAttendanceTest.kt @@ -0,0 +1,61 @@ +package org.sopt.official + +import org.junit.jupiter.api.Test +import org.sopt.official.feature.attendance.model.FinalAttendance +import org.sopt.official.feature.attendance.model.MidtermAttendance +import org.sopt.official.feature.attendance.model.MidtermAttendance.NotYet.AttendanceSession.FIRST +import org.sopt.official.feature.attendance.model.MidtermAttendance.NotYet.AttendanceSession.SECOND + +class FinalAttendanceTest { + private lateinit var firstRoundAttendance: MidtermAttendance + private lateinit var secondRoundAttendance: MidtermAttendance + + @Test + fun `1차 또는 2차 출석 여부가 아직 결정되지 않은 경우에는 최종 출석이 아직 결정되지 않은 상태로 한다`() { + firstRoundAttendance = MidtermAttendance.NotYet(FIRST) + secondRoundAttendance = MidtermAttendance.NotYet(SECOND) + assert(FinalAttendance.calculateFinalAttendance(firstRoundAttendance, secondRoundAttendance) == FinalAttendance.NOT_YET) + + firstRoundAttendance = MidtermAttendance.NotYet(FIRST) + secondRoundAttendance = MidtermAttendance.Present(attendanceAt = "16:00") + assert(FinalAttendance.calculateFinalAttendance(firstRoundAttendance, secondRoundAttendance) == FinalAttendance.NOT_YET) + + firstRoundAttendance = MidtermAttendance.NotYet(FIRST) + secondRoundAttendance = MidtermAttendance.Absent + assert(FinalAttendance.calculateFinalAttendance(firstRoundAttendance, secondRoundAttendance) == FinalAttendance.NOT_YET) + + firstRoundAttendance = MidtermAttendance.Present(attendanceAt = "14:00") + secondRoundAttendance = MidtermAttendance.NotYet(SECOND) + assert(FinalAttendance.calculateFinalAttendance(firstRoundAttendance, secondRoundAttendance) == FinalAttendance.NOT_YET) + + firstRoundAttendance = MidtermAttendance.Absent + secondRoundAttendance = MidtermAttendance.NotYet(SECOND) + assert(FinalAttendance.calculateFinalAttendance(firstRoundAttendance, secondRoundAttendance) == FinalAttendance.NOT_YET) + } + + @Test + fun `1차, 2차 출석 여부가 모두 출석일 경우 출석으로 한다`() { + firstRoundAttendance = MidtermAttendance.Present(attendanceAt = "14:00") + secondRoundAttendance = MidtermAttendance.Present(attendanceAt = "16:00") + assert(FinalAttendance.calculateFinalAttendance(firstRoundAttendance, secondRoundAttendance) == FinalAttendance.PRESENT) + } + + @Test + fun `1차, 2차 출석 여부가 모두 결석일 경우 결석으로 한다`() { + firstRoundAttendance = MidtermAttendance.Absent + secondRoundAttendance = MidtermAttendance.Absent + assert(FinalAttendance.calculateFinalAttendance(firstRoundAttendance, secondRoundAttendance) == FinalAttendance.ABSENT) + } + + @Test + fun `1차, 2차 출석 중 한 번은 출석하고 한 번은 결석한 경우 지각으로 한다`() { + firstRoundAttendance = MidtermAttendance.Present(attendanceAt = "14:00") + secondRoundAttendance = MidtermAttendance.Absent + assert(FinalAttendance.calculateFinalAttendance(firstRoundAttendance, secondRoundAttendance) == FinalAttendance.LATE) + + firstRoundAttendance = MidtermAttendance.Absent + secondRoundAttendance = MidtermAttendance.Present(attendanceAt = "16:00") + assert(FinalAttendance.calculateFinalAttendance(firstRoundAttendance, secondRoundAttendance) == FinalAttendance.LATE) + } + +}