From d1389f427fdf84907544bd7f261a90ecb4034683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Thu, 25 Dec 2025 23:20:30 +0900 Subject: [PATCH 01/33] =?UTF-8?q?feat=20::=20dmsTab,=20TabRow=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/core/designsystem/tab/DmsTab.kt | 44 +++++++++++ .../core/designsystem/tab/DmsTabRow.kt | 55 ++++++++++++++ .../main/application/ui/ApplicationScreen.kt | 74 ++++++++++++++++++- 3 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/tab/DmsTab.kt create mode 100644 core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/tab/DmsTabRow.kt diff --git a/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/tab/DmsTab.kt b/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/tab/DmsTab.kt new file mode 100644 index 000000000..ace886a19 --- /dev/null +++ b/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/tab/DmsTab.kt @@ -0,0 +1,44 @@ +package team.aliens.dms.android.core.designsystem.tab + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.material3.Tab +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import team.aliens.dms.android.core.designsystem.DmsTheme +import team.aliens.dms.android.core.designsystem.lBodyB +import team.aliens.dms.android.core.designsystem.lBodyM + +@Composable +fun DmsTab( + selected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + text: String, + icon: @Composable (() -> Unit)? = null, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + selectedContentColor: Color = DmsTheme.colorScheme.tertiaryContainer, + unselectedContentColor: Color = DmsTheme.colorScheme.inverseSurface, +) { + val (textStyle, textColor) = if (selected) DmsTheme.typography.lBodyB to selectedContentColor else DmsTheme.typography.lBodyM to unselectedContentColor + Tab( + selected = selected, + onClick = onClick, + modifier = modifier, + enabled = enabled, + text = { + Text( + text = text, + style = textStyle, + color = textColor, + ) + }, + icon = icon, + interactionSource = interactionSource, + selectedContentColor = selectedContentColor, + unselectedContentColor = unselectedContentColor, + ) +} diff --git a/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/tab/DmsTabRow.kt b/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/tab/DmsTabRow.kt new file mode 100644 index 000000000..82a32d876 --- /dev/null +++ b/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/tab/DmsTabRow.kt @@ -0,0 +1,55 @@ +package team.aliens.dms.android.core.designsystem.tab + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.TabPosition +import androidx.compose.material3.TabRow +import androidx.compose.material3.TabRowDefaults +import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import team.aliens.dms.android.core.designsystem.DmsTheme + +@Composable +fun DmsTabRow( + selectedTabIndex: Int, + modifier: Modifier = Modifier, + containerColor: Color = DmsTheme.colorScheme.background, + contentColor: Color = DmsTheme.colorScheme.onBackground, + indicator: @Composable (tabPositions: List) -> Unit = + @Composable { tabPositions -> + if (selectedTabIndex < tabPositions.size) { + TabRowDefaults.SecondaryIndicator( + modifier = Modifier + .tabIndicatorOffset(tabPositions[selectedTabIndex]) + .clip( + shape = RoundedCornerShape( + topStart = 12.dp, + topEnd = 12.dp, + ), + ), + color = DmsTheme.colorScheme.onPrimaryContainer, + height = 2.dp, + ) + } + }, + divider: @Composable () -> Unit = @Composable { + HorizontalDivider( + color = DmsTheme.colorScheme.onSurfaceVariant, + ) + }, + tabs: @Composable () -> Unit, +) { + TabRow( + selectedTabIndex = selectedTabIndex, + modifier = modifier, + containerColor = containerColor, + contentColor = contentColor, + indicator = indicator, + divider = divider, + tabs = tabs, + ) +} diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt index 9c1efbfdc..0243f17f4 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt @@ -1,6 +1,22 @@ package team.aliens.dms.android.feature.main.application.ui +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import kotlinx.coroutines.launch +import team.aliens.dms.android.core.designsystem.DmsTheme +import team.aliens.dms.android.core.designsystem.tab.DmsTab +import team.aliens.dms.android.core.designsystem.tab.DmsTabRow +import team.aliens.dms.kmp.feature.application.ui.component.ApplicationContent +import team.aliens.dms.kmp.feature.application.ui.component.VoteContent @Composable internal fun Application() { @@ -9,5 +25,61 @@ internal fun Application() { @Composable private fun ApplicationScreen() { - + Column( + modifier = Modifier + .fillMaxSize() + .background(DmsTheme.colorScheme.background) + .statusBarsPadding(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + val tabData = listOf( + "신청", + "투표", + ) + val pagerState = rememberPagerState( + pageCount = { tabData.size }, + initialPage = 0, + ) + val tabIndex = pagerState.currentPage + val coroutineScope = rememberCoroutineScope() + DmsTabRow( + selectedTabIndex = tabIndex, + ) { + tabData.forEachIndexed { index, text -> + DmsTab( + selected = tabIndex == index, + onClick = { + coroutineScope.launch { + pagerState.animateScrollToPage(index) + } + }, + text = text, + ) + } + } + HorizontalPager( + modifier = Modifier.fillMaxSize(), + state = pagerState, + beyondViewportPageCount = 1, + ) { page -> + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + if (page == 0) { + ApplicationContent( + onNavigateOutingApplication = onNavigateOutingApplication, + onNavigateRemainApplication = onNavigateRemainApplication, + onNavigateVolunteerApplication = onNavigateVolunteerApplication, + ) + } else { + VoteContent( + votes = state.votes, + onNavigateVote = onNavigateVote, + ) + } + } + } + } } From 07b7176886cf5a9bd640f3a07ac36f2d3524380e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Thu, 25 Dec 2025 23:21:05 +0900 Subject: [PATCH 02/33] =?UTF-8?q?feat=20::=20applicationContent=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../designsystem/card/DmsApplicationCard.kt | 126 ++++++++++++++++++ .../src/dev/res/drawable/img_home.png | Bin 0 -> 3776 bytes .../src/dev/res/drawable/img_outing.png | Bin 0 -> 3833 bytes .../src/dev/res/drawable/img_volunteer.png | Bin 0 -> 2948 bytes .../ui/component/ApplicationContent.kt | 42 ++++++ 5 files changed, 168 insertions(+) create mode 100644 core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/card/DmsApplicationCard.kt create mode 100644 core/design-system/src/dev/res/drawable/img_home.png create mode 100644 core/design-system/src/dev/res/drawable/img_outing.png create mode 100644 core/design-system/src/dev/res/drawable/img_volunteer.png create mode 100644 feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/ApplicationContent.kt diff --git a/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/card/DmsApplicationCard.kt b/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/card/DmsApplicationCard.kt new file mode 100644 index 000000000..776580cce --- /dev/null +++ b/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/card/DmsApplicationCard.kt @@ -0,0 +1,126 @@ +package team.aliens.dms.android.core.designsystem.card + +import androidx.annotation.DrawableRes +import androidx.compose.animation.animateColorAsState +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +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.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import team.aliens.dms.android.core.designsystem.DmsTheme +import team.aliens.dms.android.core.designsystem.bodyB +import team.aliens.dms.android.core.designsystem.foundation.DmsIcon +import team.aliens.dms.android.core.designsystem.labelB +import team.aliens.dms.android.core.designsystem.labelM +import team.aliens.dms.android.core.designsystem.util.clickable + +@Composable +fun DmsApplicationCard( + modifier: Modifier = Modifier, + title: String, + description: String? = null, + period: String? = null, + appliedTitle: String? = null, + @DrawableRes iconRes: Int, + isSelected: Boolean = false, + onClick: () -> Unit, +) { + val borderColor by animateColorAsState( + targetValue = if (isSelected) { + DmsTheme.colorScheme.onPrimaryContainer + } else { + DmsTheme.colorScheme.surfaceTint + }, + ) + Column( + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(32.dp)) + .background(DmsTheme.colorScheme.surfaceTint) + .clickable(onClick = onClick) + .border( + width = 2.dp, + color = borderColor, + shape = RoundedCornerShape(32.dp), + ) + .padding(horizontal = 16.dp, vertical = 24.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + modifier = Modifier.size(32.dp), + painter = painterResource(iconRes), + contentDescription = null, + ) + Text( + modifier = Modifier.padding(start = 8.dp), + text = title, + style = DmsTheme.typography.bodyB, + color = DmsTheme.colorScheme.inverseOnSurface, + ) + Spacer(modifier = Modifier.weight(1f)) + Icon( + painter = painterResource(DmsIcon.Forward), + tint = DmsTheme.colorScheme.scrim, + contentDescription = null, + ) + } + period?.let { + Text( + text = period, + style = DmsTheme.typography.labelM, + color = DmsTheme.colorScheme.onPrimaryContainer, + ) + } + description?.let { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = description, + style = DmsTheme.typography.labelM, + color = DmsTheme.colorScheme.inverseSurface, + ) + appliedTitle?.let { + AppliedTitleText(appliedTitle = appliedTitle) + } + } + } + } +} + +@Composable +private fun AppliedTitleText( + modifier: Modifier = Modifier, + appliedTitle: String, +) { + Text( + modifier = modifier + .background( + color = DmsTheme.colorScheme.primary, + shape = RoundedCornerShape(6.dp), + ) + .padding(horizontal = 22.dp, vertical = 8.dp), + text = appliedTitle, + style = DmsTheme.typography.labelB, + color = DmsTheme.colorScheme.onPrimaryContainer, + ) +} diff --git a/core/design-system/src/dev/res/drawable/img_home.png b/core/design-system/src/dev/res/drawable/img_home.png new file mode 100644 index 0000000000000000000000000000000000000000..17a46997b8157bbd259c5bd1b2b8440cc4ad6b6f GIT binary patch literal 3776 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 uM&v9hQlv|Jks?Kk3Gg2Vtw5eTR*Lrk0000 Unit, + onNavigateOutingApplication: () -> Unit, + onNavigateVolunteerApplication: () -> Unit, +) { + Column( + modifier = modifier + .fillMaxSize() + .padding( + horizontal = 10.dp, + vertical = 16.dp, + ), + verticalArrangement = Arrangement.spacedBy(20.dp), + ) { + listOf( + Triple("잔류", R.drawable.img_home, onNavigateRemainApplication), + Triple("외출 신청하기", R.drawable.img_outing, onNavigateOutingApplication), + Triple("봉사 활동 신청하기", R.drawable.img_volunteer, onNavigateVolunteerApplication), + ).forEach { (title, icon, onClick) -> + DmsApplicationCard( + title = title, + iconRes = icon, + onClick = onClick, + ) + } + } +} From f7f24f2647c7424abeaffbe515344dfa383929c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Fri, 26 Dec 2025 09:54:45 +0900 Subject: [PATCH 03/33] =?UTF-8?q?feat=20::=20voting=20repository=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../voting/di/RepositoryModule.kt | 18 ++++++++ .../voting/mapper/VotingMapper.kt | 45 +++++++++++++++++++ .../voting/model/AllVoteSearch.kt | 14 ++++++ .../voting/model/ModelStudentCandidates.kt | 10 +++++ .../voting/model/StudentGcnInfo.kt | 6 +++ .../voting/model/Vote.kt | 6 +++ .../voting/model/VotingItem.kt | 8 ++++ .../voting/repository/VotingRepository.kt | 23 ++++++++++ .../voting/repository/VotingRepositoryImpl.kt | 34 ++++++++++++++ 9 files changed, 164 insertions(+) create mode 100644 data/src/dev/kotlin/team.aliens.dms.android.data/voting/di/RepositoryModule.kt create mode 100644 data/src/dev/kotlin/team.aliens.dms.android.data/voting/mapper/VotingMapper.kt create mode 100644 data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/AllVoteSearch.kt create mode 100644 data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/ModelStudentCandidates.kt create mode 100644 data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/StudentGcnInfo.kt create mode 100644 data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/Vote.kt create mode 100644 data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/VotingItem.kt create mode 100644 data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepository.kt create mode 100644 data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepositoryImpl.kt diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/voting/di/RepositoryModule.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/di/RepositoryModule.kt new file mode 100644 index 000000000..30c50ce9d --- /dev/null +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/di/RepositoryModule.kt @@ -0,0 +1,18 @@ +package team.aliens.dms.android.data.voting.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import team.aliens.dms.android.data.voting.repository.VotingRepository +import team.aliens.dms.android.data.voting.repository.VotingRepositoryImpl +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal abstract class RepositoryModule { + + @Binds + @Singleton + abstract fun bindVotingRepository(impl: VotingRepositoryImpl): VotingRepository +} diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/voting/mapper/VotingMapper.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/mapper/VotingMapper.kt new file mode 100644 index 000000000..34e6024f1 --- /dev/null +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/mapper/VotingMapper.kt @@ -0,0 +1,45 @@ +package team.aliens.dms.android.data.voting.mapper + +import team.aliens.dms.android.data.voting.model.AllVoteSearch +import team.aliens.dms.android.data.voting.model.ModelStudentCandidates +import team.aliens.dms.android.data.voting.model.Vote +import team.aliens.dms.android.data.voting.model.VotingItem +import team.aliens.dms.android.network.voting.model.FetchAllVoteSearchResponse +import team.aliens.dms.android.network.voting.model.FetchCheckVotingItemResponse +import team.aliens.dms.android.network.voting.model.FetchModelStudentCandidatesResponse +import team.aliens.dms.android.shared.date.toLocalDateTime +import java.util.UUID + +internal fun FetchAllVoteSearchResponse.toModel(): List = + this.votingTopics.map(FetchAllVoteSearchResponse.VoteSearchResponse::toModel) + +private fun FetchAllVoteSearchResponse.VoteSearchResponse.toModel(): AllVoteSearch = + AllVoteSearch( + id = UUID.fromString(this.id), + topicName = this.topicName, + description = this.description, + startTime = this.startTime.toLocalDateTime(), + endTime = this.endTime.toLocalDateTime(), + voteType = Vote.valueOf(this.voteType), + isVoted = this.isVoted, + ) + +internal fun FetchCheckVotingItemResponse.toModel(): List = + this.votingOptions.map(FetchCheckVotingItemResponse.VotingItemResponse::toModel) + +private fun FetchCheckVotingItemResponse.VotingItemResponse.toModel(): VotingItem = + VotingItem( + id = id, + votingOptionName = votingOptionName, + ) + +internal fun FetchModelStudentCandidatesResponse.toModel(): List = + this.students.map(FetchModelStudentCandidatesResponse.ModelStudentCandidatesResponse::toModel) + +private fun FetchModelStudentCandidatesResponse.ModelStudentCandidatesResponse.toModel(): ModelStudentCandidates = + ModelStudentCandidates( + id = this.id, + studentGcn = this.studentGcn, + name = this.name, + profileImageUrl = this.profileImageUrl, + ) diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/AllVoteSearch.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/AllVoteSearch.kt new file mode 100644 index 000000000..4eef97aca --- /dev/null +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/AllVoteSearch.kt @@ -0,0 +1,14 @@ +package team.aliens.dms.android.data.voting.model + +import java.time.LocalDateTime +import java.util.UUID + +data class AllVoteSearch( + val id: UUID, + val topicName: String, + val description: String, + val startTime: LocalDateTime, + val endTime: LocalDateTime, + val voteType: Vote, + val isVoted: Boolean, +) diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/ModelStudentCandidates.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/ModelStudentCandidates.kt new file mode 100644 index 000000000..8c47e5d2f --- /dev/null +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/ModelStudentCandidates.kt @@ -0,0 +1,10 @@ +package team.aliens.dms.android.data.voting.model + +import java.util.UUID + +data class ModelStudentCandidates( + val id: UUID, + val studentGcn: Long, + val name: String, + val profileImageUrl: String, +) diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/StudentGcnInfo.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/StudentGcnInfo.kt new file mode 100644 index 000000000..ab8fdda6e --- /dev/null +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/StudentGcnInfo.kt @@ -0,0 +1,6 @@ +package team.aliens.dms.android.data.voting.model + +data class StudentGcnInfo( + val studentGcn: String, + val studentFilterId: Int, +) diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/Vote.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/Vote.kt new file mode 100644 index 000000000..2fe4e5790 --- /dev/null +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/Vote.kt @@ -0,0 +1,6 @@ +package team.aliens.dms.android.data.voting.model + +enum class Vote { + MODEL_STUDENT_VOTE, APPROVAL_VOTE, STUDENT_VOTE, OPTION_VOTE + ; +} diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/VotingItem.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/VotingItem.kt new file mode 100644 index 000000000..5409c5f13 --- /dev/null +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/VotingItem.kt @@ -0,0 +1,8 @@ +package team.aliens.dms.android.data.voting.model + +import java.util.UUID + +data class VotingItem( + val id: UUID, + val votingOptionName: String, +) diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepository.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepository.kt new file mode 100644 index 000000000..b8507c024 --- /dev/null +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepository.kt @@ -0,0 +1,23 @@ +package team.aliens.dms.android.data.voting.repository + +import java.time.LocalDate +import team.aliens.dms.android.data.voting.model.AllVoteSearch +import team.aliens.dms.android.data.voting.model.ModelStudentCandidates +import team.aliens.dms.android.data.voting.model.VotingItem +import java.util.UUID + +abstract class VotingRepository { + + abstract suspend fun fetchAllVoteSearch(): Result> + + abstract suspend fun fetchCheckVotingItem(votingTopicId: UUID): Result> + + abstract suspend fun fetchCreateVotingItem( + votingTopicId: UUID, + selectedId: UUID, + ): Result + + abstract suspend fun fetchDeleteVotingItem(voteId: UUID): Result + + abstract suspend fun fetchModelStudentCandidates(requestDate: LocalDate): Result> +} diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepositoryImpl.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepositoryImpl.kt new file mode 100644 index 000000000..0d9675bc0 --- /dev/null +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepositoryImpl.kt @@ -0,0 +1,34 @@ +package team.aliens.dms.android.data.voting.repository + +import java.time.LocalDate +import team.aliens.dms.android.data.voting.mapper.toModel +import team.aliens.dms.android.data.voting.model.AllVoteSearch +import team.aliens.dms.android.data.voting.model.ModelStudentCandidates +import team.aliens.dms.android.data.voting.model.VotingItem +import team.aliens.dms.android.network.voting.datasource.NetworkVotingDataSource +import java.util.UUID +import javax.inject.Inject + +internal class VotingRepositoryImpl @Inject constructor( + private val networkVotingDataSource: NetworkVotingDataSource, +) : VotingRepository() { + override suspend fun fetchAllVoteSearch(): Result> = runCatching { + networkVotingDataSource.fetchAllVoteSearch().toModel() + } + + override suspend fun fetchCheckVotingItem(votingTopicId: UUID): Result> = runCatching { + networkVotingDataSource.fetchCheckVotingItem(votingTopicId).toModel() + } + + override suspend fun fetchCreateVotingItem(votingTopicId: UUID, selectedId: UUID): Result = runCatching { + networkVotingDataSource.fetchCreateVotingItem(votingTopicId, selectedId) + } + + override suspend fun fetchDeleteVotingItem(voteId: UUID): Result = runCatching { + networkVotingDataSource.fetchDeleteVotingItem(voteId) + } + + override suspend fun fetchModelStudentCandidates(requestDate: LocalDate): Result> = runCatching { + networkVotingDataSource.fetchModelStudentCandidates(requestDate).toModel() + } +} From b83a1cb66bcdd94a7ce340f527dd3d74c7fed805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Fri, 26 Dec 2025 10:06:04 +0900 Subject: [PATCH 04/33] =?UTF-8?q?feat=20::=20voteContent=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ui/component/VoteContent.kt | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt new file mode 100644 index 000000000..a44184726 --- /dev/null +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt @@ -0,0 +1,49 @@ +package team.aliens.dms.kmp.feature.application.ui.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import team.aliens.dms.android.core.designsystem.R +import team.aliens.dms.android.core.designsystem.card.DmsApplicationCard +import team.aliens.dms.android.core.ui.util.toDateString +import team.aliens.dms.android.data.voting.model.AllVoteSearch +import team.aliens.dms.android.data.voting.model.Vote + +@Composable +internal fun VoteContent( + modifier: Modifier = Modifier, + votes: List, + onNavigateVote: (AllVoteSearch) -> Unit, +) { + LazyColumn( + modifier = modifier + .fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(20.dp), + contentPadding = PaddingValues( + horizontal = 10.dp, + vertical = 16.dp, + ), + ) { + items(votes) { vote -> + val icon = when (vote.voteType) { + Vote.STUDENT_VOTE -> R.drawable.img_student_tag + Vote.OPTION_VOTE -> R.drawable.img_percent + Vote.APPROVAL_VOTE -> R.drawable.img_choice + Vote.MODEL_STUDENT_VOTE -> R.drawable.img_model_student + } + DmsApplicationCard( + title = vote.topicName, + appliedTitle = null, + period = "${vote.startTime.toLocalTime()} ~ ${vote.endTime.toLocalTime()}", + description = vote.description, + iconRes = icon, + onClick = { onNavigateVote(vote) }, + ) + } + } +} From 489f18954f9e336590b317195f3f96d30bbf4f8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Fri, 26 Dec 2025 10:06:45 +0900 Subject: [PATCH 05/33] =?UTF-8?q?feat=20::=20application=20screen=20navhos?= =?UTF-8?q?t=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../navigation/ApplicationRoute.kt | 18 ++++++++- .../main/application/ui/ApplicationScreen.kt | 39 ++++++++++++++++--- .../viewmodel/ApplicationViewModel.kt | 35 +++++++++++++++++ 3 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/navigation/ApplicationRoute.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/navigation/ApplicationRoute.kt index 2eaef7773..501c8b75f 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/navigation/ApplicationRoute.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/navigation/ApplicationRoute.kt @@ -2,9 +2,23 @@ package team.aliens.dms.android.feature.main.application.navigation import androidx.compose.runtime.Composable import kotlinx.serialization.Serializable +import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType +import team.aliens.dms.android.data.voting.model.AllVoteSearch import team.aliens.dms.android.feature.main.application.ui.Application @Composable -fun ApplicationRoute() { - Application() +fun ApplicationRoute( + onNavigateRemainApplication: () -> Unit, + onNavigateOutingApplication: () -> Unit, + onNavigateVolunteerApplication: () -> Unit, + onNavigateVote: (AllVoteSearch) -> Unit, + onShowSnackBar: (DmsSnackBarType, String) -> Unit, +) { + Application( + onNavigateRemainApplication = onNavigateRemainApplication, + onNavigateOutingApplication = onNavigateOutingApplication, + onNavigateVolunteerApplication = onNavigateVolunteerApplication, + onNavigateVote = onNavigateVote, + onShowSnackBar = onShowSnackBar, + ) } diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt index 0243f17f4..0f4c12e8f 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt @@ -8,28 +8,57 @@ import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import kotlinx.coroutines.launch import team.aliens.dms.android.core.designsystem.DmsTheme +import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType import team.aliens.dms.android.core.designsystem.tab.DmsTab import team.aliens.dms.android.core.designsystem.tab.DmsTabRow +import team.aliens.dms.android.data.voting.model.AllVoteSearch +import team.aliens.dms.android.feature.main.application.viewmodel.ApplicationState +import team.aliens.dms.android.feature.main.application.viewmodel.ApplicationViewModel import team.aliens.dms.kmp.feature.application.ui.component.ApplicationContent import team.aliens.dms.kmp.feature.application.ui.component.VoteContent @Composable -internal fun Application() { - ApplicationScreen() +internal fun Application( + onNavigateRemainApplication: () -> Unit, + onNavigateOutingApplication: () -> Unit, + onNavigateVolunteerApplication: () -> Unit, + onNavigateVote: (AllVoteSearch) -> Unit, + onShowSnackBar: (DmsSnackBarType, String) -> Unit, +) { + val viewModel: ApplicationViewModel = hiltViewModel() + val state by viewModel.uiState.collectAsStateWithLifecycle() + + + + ApplicationScreen( + state = state, + onNavigateRemainApplication = onNavigateRemainApplication, + onNavigateOutingApplication = { onShowSnackBar(DmsSnackBarType.SUCCESS, "준비중인 기능이에요") }, + onNavigateVolunteerApplication = { onShowSnackBar(DmsSnackBarType.SUCCESS, "준비중인 기능이에요") }, + onNavigateVote = onNavigateVote, + ) } @Composable -private fun ApplicationScreen() { +private fun ApplicationScreen( + state: ApplicationState, + onNavigateRemainApplication: () -> Unit, + onNavigateOutingApplication: () -> Unit, + onNavigateVolunteerApplication: () -> Unit, + onNavigateVote: (AllVoteSearch) -> Unit, +) { Column( modifier = Modifier .fillMaxSize() - .background(DmsTheme.colorScheme.background) - .statusBarsPadding(), + .background(DmsTheme.colorScheme.background), horizontalAlignment = Alignment.CenterHorizontally, ) { val tabData = listOf( diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt new file mode 100644 index 000000000..64dfbb51b --- /dev/null +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt @@ -0,0 +1,35 @@ +package team.aliens.dms.android.feature.main.application.viewmodel + +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import team.aliens.dms.android.core.ui.viewmodel.BaseStateViewModel +import team.aliens.dms.android.data.voting.model.AllVoteSearch +import team.aliens.dms.android.data.voting.repository.VotingRepository +import javax.inject.Inject + +@HiltViewModel +internal class ApplicationViewModel @Inject constructor( + private val votingRepository: VotingRepository, +) : BaseStateViewModel(ApplicationState()) { + + init { + getAllVotes() + } + + private fun getAllVotes() { + viewModelScope.launch(Dispatchers.IO) { + votingRepository.fetchAllVoteSearch() + .onSuccess { + setState { it.copy(votes = it.votes) } + }.onFailure { +// Logger.a(it) { it.message.toString() } + } + } + } +} + +data class ApplicationState( + val votes: List = emptyList(), +) From 0ad124693320689e96a99adeb183b05ab691cb03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Fri, 26 Dec 2025 10:07:37 +0900 Subject: [PATCH 06/33] =?UTF-8?q?feat=20::=20=EC=9E=94=EB=A5=98=20?= =?UTF-8?q?=EC=8B=A0=EC=B2=AD=20screen=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../team/aliens/dms/android/app/DmsApp.kt | 19 +++++++++++++++++- .../src/dev/res/drawable/img_choice.png | Bin 0 -> 2690 bytes .../dev/res/drawable/img_model_student.png | Bin 0 -> 3647 bytes .../src/dev/res/drawable/img_percent.png | Bin 0 -> 3516 bytes .../src/dev/res/drawable/img_student_tag.png | Bin 0 -> 3653 bytes .../aliens/dms/android/core/ui/util/Date.kt | 7 +++++++ .../navigation/RemainApplicationRoute.kt | 9 +++++++++ .../remain/ui/RemainApplicationScreen.kt | 13 ++++++++++++ 8 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 core/design-system/src/dev/res/drawable/img_choice.png create mode 100644 core/design-system/src/dev/res/drawable/img_model_student.png create mode 100644 core/design-system/src/dev/res/drawable/img_percent.png create mode 100644 core/design-system/src/dev/res/drawable/img_student_tag.png create mode 100644 core/ui/src/dev/java/team/aliens/dms/android/core/ui/util/Date.kt create mode 100644 feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/navigation/RemainApplicationRoute.kt create mode 100644 feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt diff --git a/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt b/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt index 9ec9c41d0..c43ac0697 100644 --- a/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt +++ b/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt @@ -27,6 +27,7 @@ import team.aliens.dms.android.feature.main.home.navigation.HomeRoute import team.aliens.dms.android.feature.main.mypage.navigation.MyPageRoute import team.aliens.dms.android.feature.meal.navigation.MealRoute import team.aliens.dms.android.feature.onboarding.navigation.OnboardingRoute +import team.aliens.dms.android.feature.remain.navigation.RemainApplicationRoute import team.aliens.dms.android.feature.signin.navigation.SignInRoute @Serializable @@ -44,6 +45,9 @@ data object MealScreenNav : NavKey @Serializable data object ApplicationScreenNav : NavKey +@Serializable +data object RemainScreenNav : NavKey + @Serializable data object MyPageScreenNav : NavKey @@ -136,7 +140,20 @@ fun DmsApp( ) } entry { - ApplicationRoute() + ApplicationRoute( + onNavigateRemainApplication = { + backStack.add(RemainScreenNav) + }, + onNavigateOutingApplication = {}, + onNavigateVolunteerApplication = {}, + onNavigateVote = {}, + onShowSnackBar = { snackBarType, message -> + appState.showSnackBar(snackBarType, message) + }, + ) + } + entry { + RemainApplicationRoute() } entry { MyPageRoute() diff --git a/core/design-system/src/dev/res/drawable/img_choice.png b/core/design-system/src/dev/res/drawable/img_choice.png new file mode 100644 index 0000000000000000000000000000000000000000..c5a956036d6c68a6c5fc68a3123b7370ca9c0300 GIT binary patch literal 2690 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 literal 0 HcmV?d00001 diff --git a/core/design-system/src/dev/res/drawable/img_percent.png b/core/design-system/src/dev/res/drawable/img_percent.png new file mode 100644 index 0000000000000000000000000000000000000000..d15a9eb0935a69259680a446675b11fe587ffb38 GIT binary patch literal 3516 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 XiW&iuLEHh600000NkvXXu0mjf+vC(p literal 0 HcmV?d00001 diff --git a/core/ui/src/dev/java/team/aliens/dms/android/core/ui/util/Date.kt b/core/ui/src/dev/java/team/aliens/dms/android/core/ui/util/Date.kt new file mode 100644 index 000000000..22bb7ff67 --- /dev/null +++ b/core/ui/src/dev/java/team/aliens/dms/android/core/ui/util/Date.kt @@ -0,0 +1,7 @@ +package team.aliens.dms.android.core.ui.util + +import org.threeten.bp.LocalDateTime + +fun LocalDateTime.toDateString(): String { + return "${this.year}년 ${this.monthValue}월 ${this.dayOfMonth}일" +} diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/navigation/RemainApplicationRoute.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/navigation/RemainApplicationRoute.kt new file mode 100644 index 000000000..df3ff351a --- /dev/null +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/navigation/RemainApplicationRoute.kt @@ -0,0 +1,9 @@ +package team.aliens.dms.android.feature.remain.navigation + +import androidx.compose.runtime.Composable +import team.aliens.dms.android.feature.remain.ui.RemainApplication + +@Composable +fun RemainApplicationRoute() { + RemainApplication() +} diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt new file mode 100644 index 000000000..26991e5bf --- /dev/null +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt @@ -0,0 +1,13 @@ +package team.aliens.dms.android.feature.remain.ui + +import androidx.compose.runtime.Composable + +@Composable +internal fun RemainApplication() { + RemainApplicationScreen() +} + +@Composable +private fun RemainApplicationScreen() { + +} From b1f9bc081a5dddf50e68dcede2d43b8c6902e23e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Fri, 26 Dec 2025 10:42:09 +0900 Subject: [PATCH 07/33] =?UTF-8?q?refactor=20::=20data=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?remain=20repository=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../remain/di/RepositoryModule.kt | 18 ++++++++++ .../remain/mapper/RemainsMapper.kt | 33 +++++++++++++++++++ .../remain/model/AppliedRemainsOption.kt | 8 +++++ .../remain/model/RemainsApplicationTime.kt | 10 ++++++ .../remain/model/RemainsOption.kt | 10 ++++++ .../remain/repository/RemainsRepository.kt | 17 ++++++++++ .../repository/RemainsRepositoryImpl.kt | 30 +++++++++++++++++ 7 files changed, 126 insertions(+) create mode 100644 data/src/dev/kotlin/team.aliens.dms.android.data/remain/di/RepositoryModule.kt create mode 100644 data/src/dev/kotlin/team.aliens.dms.android.data/remain/mapper/RemainsMapper.kt create mode 100644 data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/AppliedRemainsOption.kt create mode 100644 data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/RemainsApplicationTime.kt create mode 100644 data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/RemainsOption.kt create mode 100644 data/src/dev/kotlin/team.aliens.dms.android.data/remain/repository/RemainsRepository.kt create mode 100644 data/src/dev/kotlin/team.aliens.dms.android.data/remain/repository/RemainsRepositoryImpl.kt diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/remain/di/RepositoryModule.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/remain/di/RepositoryModule.kt new file mode 100644 index 000000000..b8d133e0c --- /dev/null +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/remain/di/RepositoryModule.kt @@ -0,0 +1,18 @@ +package team.aliens.dms.android.data.remain.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import team.aliens.dms.android.data.remain.repository.RemainsRepository +import team.aliens.dms.android.data.remain.repository.RemainsRepositoryImpl +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal abstract class RepositoryModule { + + @Binds + @Singleton + abstract fun bindRemainsRepository(impl: RemainsRepositoryImpl): RemainsRepository +} diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/remain/mapper/RemainsMapper.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/remain/mapper/RemainsMapper.kt new file mode 100644 index 000000000..e4b91c2ac --- /dev/null +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/remain/mapper/RemainsMapper.kt @@ -0,0 +1,33 @@ +package team.aliens.dms.android.data.remain.mapper + +import team.aliens.dms.android.data.remain.model.AppliedRemainsOption +import team.aliens.dms.android.data.remain.model.RemainsApplicationTime +import team.aliens.dms.android.data.remain.model.RemainsOption +import team.aliens.dms.android.network.remains.model.FetchAppliedRemainsOptionResponse +import team.aliens.dms.android.network.remains.model.FetchRemainsApplicationTimeResponse +import team.aliens.dms.android.network.remains.model.FetchRemainsOptionsResponse + +internal fun FetchAppliedRemainsOptionResponse.toModel(): AppliedRemainsOption = + AppliedRemainsOption( + id = this.remainsOptionId, + title = this.title, + ) + +internal fun FetchRemainsApplicationTimeResponse.toModel(): RemainsApplicationTime = + RemainsApplicationTime( + startDayOfWeek = this.startDayOfWeek, + startTime = this.startTime, + endDayOfWeek = this.endDayOfWeek, + endTime = this.endTime, + ) + +internal fun FetchRemainsOptionsResponse.toModel(): List = + this.remainsOptionResponse.map(FetchRemainsOptionsResponse.RemainsOptionResponse::toModel) + +private fun FetchRemainsOptionsResponse.RemainsOptionResponse.toModel(): RemainsOption = + RemainsOption( + id = this.id, + title = this.title, + description = this.description, + applied = this.applied, + ) diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/AppliedRemainsOption.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/AppliedRemainsOption.kt new file mode 100644 index 000000000..959a2fa13 --- /dev/null +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/AppliedRemainsOption.kt @@ -0,0 +1,8 @@ +package team.aliens.dms.android.data.remain.model + +import java.util.UUID + +data class AppliedRemainsOption( + val id: UUID, + val title: String, +) diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/RemainsApplicationTime.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/RemainsApplicationTime.kt new file mode 100644 index 000000000..c128e7f76 --- /dev/null +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/RemainsApplicationTime.kt @@ -0,0 +1,10 @@ +package team.aliens.dms.android.data.remain.model + +import java.time.DayOfWeek + +data class RemainsApplicationTime( + val startDayOfWeek: DayOfWeek = DayOfWeek.SUNDAY, + val startTime: String = "", + val endDayOfWeek: DayOfWeek = DayOfWeek.SUNDAY, + val endTime: String = "", +) diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/RemainsOption.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/RemainsOption.kt new file mode 100644 index 000000000..b18f59385 --- /dev/null +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/RemainsOption.kt @@ -0,0 +1,10 @@ +package team.aliens.dms.android.data.remain.model + +import java.util.UUID + +data class RemainsOption( + val id: UUID, + val title: String, + val description: String, + val applied: Boolean, +) diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/remain/repository/RemainsRepository.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/remain/repository/RemainsRepository.kt new file mode 100644 index 000000000..11a345d1d --- /dev/null +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/remain/repository/RemainsRepository.kt @@ -0,0 +1,17 @@ +package team.aliens.dms.android.data.remain.repository + +import team.aliens.dms.android.data.remain.model.AppliedRemainsOption +import team.aliens.dms.android.data.remain.model.RemainsApplicationTime +import team.aliens.dms.android.data.remain.model.RemainsOption +import java.util.UUID + +abstract class RemainsRepository { + + abstract suspend fun updateRemainsOption(optionId: UUID): Result + + abstract suspend fun fetchAppliedRemainsOption(): Result + + abstract suspend fun fetchRemainsApplicationTime(): Result + + abstract suspend fun fetchRemainsOptions(): Result> +} diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/remain/repository/RemainsRepositoryImpl.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/remain/repository/RemainsRepositoryImpl.kt new file mode 100644 index 000000000..090dff6de --- /dev/null +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/remain/repository/RemainsRepositoryImpl.kt @@ -0,0 +1,30 @@ +package team.aliens.dms.android.data.remain.repository + +import team.aliens.dms.android.data.remain.mapper.toModel +import team.aliens.dms.android.data.remain.model.AppliedRemainsOption +import team.aliens.dms.android.data.remain.model.RemainsApplicationTime +import team.aliens.dms.android.data.remain.model.RemainsOption +import team.aliens.dms.android.network.remains.datasource.NetworkRemainsDataSource +import java.util.UUID +import javax.inject.Inject + +internal class RemainsRepositoryImpl @Inject constructor( + private val networkRemainsDataSource: NetworkRemainsDataSource, +) : RemainsRepository() { + + override suspend fun updateRemainsOption(optionId: UUID): Result = runCatching { + networkRemainsDataSource.updateRemainsOption(optionId) + } + + override suspend fun fetchAppliedRemainsOption(): Result = runCatching { + networkRemainsDataSource.fetchAppliedRemainsOption().toModel() + } + + override suspend fun fetchRemainsApplicationTime(): Result = runCatching { + networkRemainsDataSource.fetchRemainsApplicationTime().toModel() + } + + override suspend fun fetchRemainsOptions(): Result> = runCatching { + networkRemainsDataSource.fetchRemainsOptions().toModel() + } +} From b67332a3ec835b95f308d98ea11fb7fb7ab75c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Fri, 26 Dec 2025 10:42:27 +0900 Subject: [PATCH 08/33] =?UTF-8?q?refactor=20::=20=EC=9E=94=EB=A5=98=20?= =?UTF-8?q?=EC=8B=A0=EC=B2=AD=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../viewmodel/RemainApplicationViewModel.kt | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt new file mode 100644 index 000000000..18a5910d8 --- /dev/null +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt @@ -0,0 +1,71 @@ +package team.aliens.dms.android.feature.remain.viewmodel + +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import team.aliens.dms.android.core.ui.viewmodel.BaseStateViewModel +import team.aliens.dms.android.data.remain.model.AppliedRemainsOption +import team.aliens.dms.android.data.remain.model.RemainsApplicationTime +import team.aliens.dms.android.data.remain.model.RemainsOption +import team.aliens.dms.android.data.remain.repository.RemainsRepository +import java.util.UUID +import javax.inject.Inject +import kotlin.collections.map + +@HiltViewModel +class RemainApplicationViewModel @Inject constructor( + val remainsRepository: RemainsRepository +): BaseStateViewModel(RemainApplicationState()) { + init { + getRemainsOptions() + getRemainsApplicationTime() + } + + private fun getRemainsOptions() { + viewModelScope.launch { + remainsRepository.fetchRemainsOptions().onSuccess { remainsOptions -> + val selectRemainsOptionId = remainsOptions.find { it.applied }?.id ?: UUID.randomUUID() + setState { + it.copy( + remainsOptions = remainsOptions, + selectRemainsOptionId = selectRemainsOptionId, + ) + } + } + } + } + + private fun getRemainsApplicationTime() { + viewModelScope.launch { + remainsRepository.fetchRemainsApplicationTime().onSuccess { + setState { it.copy(remainsApplicationTime = it.remainsApplicationTime) } + } + } + } + + internal fun setSelectRemainsOption(remainsOptionId: UUID) { + setState { it.copy(selectRemainsOptionId = remainsOptionId) } + } + + internal fun changeRemainsOption() { + viewModelScope.launch { + val remainOptionId = uiState.value.selectRemainsOptionId + remainsRepository.updateRemainsOption(optionId = remainOptionId).onSuccess { + val remainsOptions = uiState.value.remainsOptions.map { remainsOption -> + if (remainsOption.id == uiState.value.selectRemainsOptionId) { + remainsOption.copy(applied = true) + } else { + remainsOption.copy(applied = false) + } + } + setState { it.copy(remainsOptions = remainsOptions) } + } + } + } +} + +data class RemainApplicationState( + val remainsOptions: List = emptyList(), + val selectRemainsOptionId: UUID = UUID.randomUUID(), + val remainsApplicationTime: RemainsApplicationTime = RemainsApplicationTime(), +) From c1eb5a0c9343c7a06205970fef7651ac5495018a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Fri, 26 Dec 2025 11:54:14 +0900 Subject: [PATCH 09/33] =?UTF-8?q?feat=20::=20=EC=9E=94=EB=A5=98=20?= =?UTF-8?q?=EC=8B=A0=EC=B2=AD=20screen=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../team/aliens/dms/android/app/DmsApp.kt | 8 +- .../src/dev/res/drawable/img_bus.png | Bin 0 -> 3340 bytes .../src/dev/res/drawable/img_night_bus.png | Bin 0 -> 2746 bytes .../src/dev/res/drawable/img_small_home.png | Bin 0 -> 2819 bytes .../navigation/RemainApplicationRoute.kt | 8 +- .../remain/ui/RemainApplicationScreen.kt | 109 +++++++++++++++++- .../remain/ui/component/DmsFloatingNotice.kt | 50 ++++++++ .../viewmodel/RemainApplicationViewModel.kt | 6 +- 8 files changed, 170 insertions(+), 11 deletions(-) create mode 100644 core/design-system/src/dev/res/drawable/img_bus.png create mode 100644 core/design-system/src/dev/res/drawable/img_night_bus.png create mode 100644 core/design-system/src/dev/res/drawable/img_small_home.png create mode 100644 feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/component/DmsFloatingNotice.kt diff --git a/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt b/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt index c43ac0697..95e5afaeb 100644 --- a/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt +++ b/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt @@ -153,14 +153,18 @@ fun DmsApp( ) } entry { - RemainApplicationRoute() + RemainApplicationRoute( + onBackPressed = { + backStack.remove(RemainScreenNav) + } + ) } entry { MyPageRoute() } entry { MealRoute( - onNavigateBack = { backStack.removeLastOrNull() } + onNavigateBack = { backStack.remove(MealScreenNav) } ) } }, diff --git a/core/design-system/src/dev/res/drawable/img_bus.png b/core/design-system/src/dev/res/drawable/img_bus.png new file mode 100644 index 0000000000000000000000000000000000000000..d1e5dd860149fb6d365455265452635a4b64979f GIT binary patch literal 3340 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 WMv}Z(c8_BK0000 literal 0 HcmV?d00001 diff --git a/core/design-system/src/dev/res/drawable/img_night_bus.png b/core/design-system/src/dev/res/drawable/img_night_bus.png new file mode 100644 index 0000000000000000000000000000000000000000..c888813206c9b3dbaab6f5ba8523a636d357aea7 GIT binary patch literal 2746 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 Unit, +) { + RemainApplication( + onNavigateBack = onBackPressed, + ) } diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt index 26991e5bf..4643326b7 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt @@ -1,13 +1,114 @@ package team.aliens.dms.android.feature.remain.ui +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import team.aliens.dms.android.core.designsystem.DmsTheme +import team.aliens.dms.android.core.designsystem.R +import team.aliens.dms.android.core.designsystem.appbar.DmsTopAppBar +import team.aliens.dms.android.core.designsystem.button.ButtonColor +import team.aliens.dms.android.core.designsystem.button.ButtonType +import team.aliens.dms.android.core.designsystem.button.DmsButton +import team.aliens.dms.android.core.designsystem.card.DmsApplicationCard +import team.aliens.dms.android.core.designsystem.horizontalPadding +import team.aliens.dms.android.core.designsystem.topPadding +import team.aliens.dms.android.core.designsystem.verticalPadding +import team.aliens.dms.android.core.ui.util.toLocale +import team.aliens.dms.android.feature.remain.ui.component.DmsFloatingNotice +import team.aliens.dms.android.feature.remain.viewmodel.RemainApplicationState +import team.aliens.dms.android.feature.remain.viewmodel.RemainApplicationViewModel +import java.util.UUID @Composable -internal fun RemainApplication() { - RemainApplicationScreen() +internal fun RemainApplication( + onNavigateBack: () -> Unit, +) { + val remainApplicationViewModel: RemainApplicationViewModel = hiltViewModel() + val state by remainApplicationViewModel.uiState.collectAsStateWithLifecycle() + + RemainApplicationScreen( + onNavigateBack = onNavigateBack, + state = state, + setSelectRemainsOption = remainApplicationViewModel::setSelectRemainsOption, + changeRemainsOption = remainApplicationViewModel::changeRemainsOption, + ) } @Composable -private fun RemainApplicationScreen() { - +private fun RemainApplicationScreen( + onNavigateBack: () -> Unit, + state: RemainApplicationState, + setSelectRemainsOption: (UUID) -> Unit, + changeRemainsOption: () -> Unit, +) { + Column( + modifier = Modifier + .fillMaxSize() + .background(DmsTheme.colorScheme.background), + ) { + DmsTopAppBar( + title = "잔류 신청", + onBackPressed = onNavigateBack, + ) + DmsFloatingNotice( + modifier = Modifier + .fillMaxWidth() + .padding( + top = 12.dp, + start = 24.dp, + end = 24.dp, + ), + text = "잔류 신청 시간은 ${state.remainsApplicationTime.startDayOfWeek.toLocale()} ${state.remainsApplicationTime.startTime} ~ ${state.remainsApplicationTime.endDayOfWeek.toLocale()} ${state.remainsApplicationTime.endTime} 까지 입니다.", + ) + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .topPadding(30.dp) + .horizontalPadding(24.dp), + verticalArrangement = Arrangement.spacedBy(20.dp), + ) { + items(state.remainsOptions) { remainsOption -> + val icon = when (remainsOption.title) { + "금요일 귀가" -> R.drawable.img_night_bus + "토요일 귀가" -> R.drawable.img_bus + "토요일 귀사" -> R.drawable.img_home + "주말 잔류" -> R.drawable.img_small_home + else -> R.drawable.img_bus + } + val appliedTitle = if (remainsOption.applied) "신청됨" else null + DmsApplicationCard( + title = remainsOption.title, + description = remainsOption.description, + isSelected = state.selectRemainsOptionId == remainsOption.id, + iconRes = icon, + appliedTitle = appliedTitle, + onClick = { setSelectRemainsOption(remainsOption.id) }, + ) + } + } + Spacer(modifier = Modifier.weight(1f)) + DmsButton( + modifier = Modifier + .fillMaxWidth() + .horizontalPadding(24.dp) + .verticalPadding(16.dp), + text = "변경하기", + buttonType = ButtonType.Contained, + buttonColor = ButtonColor.Primary, + enabled = state.selectRemainsOptionId.toString().isNotEmpty(), + onClick = changeRemainsOption, + ) + } } diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/component/DmsFloatingNotice.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/component/DmsFloatingNotice.kt new file mode 100644 index 000000000..3d4eef979 --- /dev/null +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/component/DmsFloatingNotice.kt @@ -0,0 +1,50 @@ +package team.aliens.dms.android.feature.remain.ui.component + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +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.unit.dp +import team.aliens.dms.android.core.designsystem.DmsTheme +import team.aliens.dms.android.core.designsystem.foundation.DmsIcon +import team.aliens.dms.android.core.designsystem.labelM + +@Composable +fun DmsFloatingNotice( + modifier: Modifier = Modifier, + text: String, + @DrawableRes iconResource: Int = DmsIcon.Notification, +) { + Row( + modifier = modifier + .fillMaxWidth() + .background( + color = DmsTheme.colorScheme.primary, + shape = RoundedCornerShape(30.dp), + ).padding( + horizontal = 22.dp, + vertical = 12.dp, + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(14.dp), + ) { + Icon( + painter = painterResource(iconResource), + contentDescription = null, + ) + Text( + text = text, + style = DmsTheme.typography.labelM, + color = DmsTheme.colorScheme.onBackground, + ) + } +} diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt index 18a5910d8..94c8c5fac 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt @@ -37,8 +37,8 @@ class RemainApplicationViewModel @Inject constructor( private fun getRemainsApplicationTime() { viewModelScope.launch { - remainsRepository.fetchRemainsApplicationTime().onSuccess { - setState { it.copy(remainsApplicationTime = it.remainsApplicationTime) } + remainsRepository.fetchRemainsApplicationTime().onSuccess { time -> + setState { it.copy(remainsApplicationTime = time) } } } } @@ -50,7 +50,7 @@ class RemainApplicationViewModel @Inject constructor( internal fun changeRemainsOption() { viewModelScope.launch { val remainOptionId = uiState.value.selectRemainsOptionId - remainsRepository.updateRemainsOption(optionId = remainOptionId).onSuccess { + remainsRepository.updateRemainsOption(optionId = remainOptionId!!).onSuccess { val remainsOptions = uiState.value.remainsOptions.map { remainsOption -> if (remainsOption.id == uiState.value.selectRemainsOptionId) { remainsOption.copy(applied = true) From 60285c52e118c6bef3ceba15b9c41a3eb61b12ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Sat, 27 Dec 2025 19:23:29 +0900 Subject: [PATCH 10/33] =?UTF-8?q?refactor=20::=20maxLine=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EA=B2=8C=20=ED=81=AC=EA=B8=B0=20=EC=A7=80=EC=A0=95?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../remain/ui/component/DmsFloatingNotice.kt | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/component/DmsFloatingNotice.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/component/DmsFloatingNotice.kt index 3d4eef979..5d95bfa8c 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/component/DmsFloatingNotice.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/component/DmsFloatingNotice.kt @@ -1,5 +1,6 @@ package team.aliens.dms.android.feature.remain.ui.component +import android.R import androidx.annotation.DrawableRes import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -7,13 +8,20 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicText +import androidx.compose.foundation.text.TextAutoSize +import androidx.compose.foundation.text.TextAutoSizeDefaults import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.widget.TextViewCompat import team.aliens.dms.android.core.designsystem.DmsTheme import team.aliens.dms.android.core.designsystem.foundation.DmsIcon import team.aliens.dms.android.core.designsystem.labelM @@ -24,6 +32,7 @@ fun DmsFloatingNotice( text: String, @DrawableRes iconResource: Int = DmsIcon.Notification, ) { + Row( modifier = modifier .fillMaxWidth() @@ -41,10 +50,16 @@ fun DmsFloatingNotice( painter = painterResource(iconResource), contentDescription = null, ) - Text( + BasicText( text = text, - style = DmsTheme.typography.labelM, - color = DmsTheme.colorScheme.onBackground, + style = DmsTheme.typography.labelM.copy( + color = DmsTheme.colorScheme.onBackground, + ), + autoSize = TextAutoSize.StepBased( + minFontSize = 1.sp, + maxFontSize = 50.sp, + ), + maxLines = 1, ) } } From 5bf40d71ba198946aa12b7f7247fd3c692e65adf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Sat, 27 Dec 2025 19:24:04 +0900 Subject: [PATCH 11/33] =?UTF-8?q?feat=20::=20Meal=20Screen=20scroll=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../team/aliens/dms/android/feature/meal/ui/MealScreen.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/meal/ui/MealScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/meal/ui/MealScreen.kt index 574669365..0483dba13 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/meal/ui/MealScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/meal/ui/MealScreen.kt @@ -14,6 +14,8 @@ import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.rememberModalBottomSheetState @@ -146,13 +148,15 @@ private fun MealScreen( Column( modifier = Modifier .fillMaxSize() - .background(DmsTheme.colorScheme.background), + .background(DmsTheme.colorScheme.background) + .verticalScroll(rememberScrollState()), ) { DmsTopAppBar( onBackPressed = onBackClick, ) Box( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize(), ) { Canvas( modifier = Modifier From be65977d5242147a6a5c31eb97fcfbb7d7c6cc9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Sat, 27 Dec 2025 19:24:52 +0900 Subject: [PATCH 12/33] =?UTF-8?q?refactor=20::=20applicationCard=20applied?= =?UTF-8?q?Text=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dms/android/core/designsystem/card/DmsApplicationCard.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/card/DmsApplicationCard.kt b/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/card/DmsApplicationCard.kt index 776580cce..75ac2dbea 100644 --- a/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/card/DmsApplicationCard.kt +++ b/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/card/DmsApplicationCard.kt @@ -99,6 +99,7 @@ fun DmsApplicationCard( style = DmsTheme.typography.labelM, color = DmsTheme.colorScheme.inverseSurface, ) + Spacer(modifier = Modifier.weight(1f)) appliedTitle?.let { AppliedTitleText(appliedTitle = appliedTitle) } From 1f3f44ca85b3d8a8657dcb832fe106d109f6bb8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Sat, 27 Dec 2025 19:25:14 +0900 Subject: [PATCH 13/33] =?UTF-8?q?feat=20::=20=EB=B0=B1=EC=8A=A4=ED=83=9D?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A0=84?= =?UTF-8?q?=EB=8B=AC=ED=95=A0=20=EC=88=98=20=EC=9E=88=EA=B2=8C=20resultSto?= =?UTF-8?q?re=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../team/aliens/dms/android/app/DmsApp.kt | 26 ++++-- .../dms/android/core/jwt/JwtProviderImpl.kt | 4 + .../android/core/ui/navigation/ResultStore.kt | 84 +++++++++++++++++++ .../main/application/ui/ApplicationScreen.kt | 17 +++- .../navigation/RemainApplicationRoute.kt | 4 +- .../remain/ui/RemainApplicationScreen.kt | 9 +- .../viewmodel/RemainApplicationViewModel.kt | 9 +- 7 files changed, 133 insertions(+), 20 deletions(-) create mode 100644 core/ui/src/dev/java/team/aliens/dms/android/core/ui/navigation/ResultStore.kt diff --git a/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt b/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt index 95e5afaeb..8717d5e5b 100644 --- a/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt +++ b/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt @@ -1,5 +1,6 @@ package team.aliens.dms.android.app +import android.util.Log import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding @@ -8,6 +9,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.windowsizeclass.WindowSizeClass import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -22,6 +24,8 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.serialization.Serializable import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBar import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarVisuals +import team.aliens.dms.android.core.ui.navigation.LocalResultStore +import team.aliens.dms.android.core.ui.navigation.rememberResultStore import team.aliens.dms.android.feature.main.application.navigation.ApplicationRoute import team.aliens.dms.android.feature.main.home.navigation.HomeRoute import team.aliens.dms.android.feature.main.mypage.navigation.MyPageRoute @@ -62,6 +66,7 @@ fun DmsApp( val isJwtAvailableState by isJwtAvailable.collectAsState() val backStack = rememberNavBackStack(OnboardingScreenNav) + val resultStore = rememberResultStore() val currentScreen = backStack.lastOrNull() val shouldShowBottomBar = currentScreen in listOf( HomeScreenNav, @@ -102,13 +107,14 @@ fun DmsApp( } } ) { paddingValues -> - NavDisplay( - modifier = Modifier - .padding(paddingValues) - .navigationBarsPadding(), - backStack = backStack, - onBack = { backStack.removeLastOrNull() }, - entryProvider = entryProvider { + CompositionLocalProvider(LocalResultStore provides resultStore) { + NavDisplay( + modifier = Modifier + .padding(paddingValues) + .navigationBarsPadding(), + backStack = backStack, + onBack = { backStack.removeLastOrNull() }, + entryProvider = entryProvider { entry { OnboardingRoute( navigateToSignIn = { @@ -154,7 +160,8 @@ fun DmsApp( } entry { RemainApplicationRoute( - onBackPressed = { + onNavigateBack = { title -> + resultStore.setResult("remain_application_result", title) backStack.remove(RemainScreenNav) } ) @@ -168,7 +175,8 @@ fun DmsApp( ) } }, - ) + ) + } SnackbarHost( modifier = Modifier .statusBarsPadding() diff --git a/core/jwt/src/main/java/team/aliens/dms/android/core/jwt/JwtProviderImpl.kt b/core/jwt/src/main/java/team/aliens/dms/android/core/jwt/JwtProviderImpl.kt index 80f3d4b38..29f19045e 100644 --- a/core/jwt/src/main/java/team/aliens/dms/android/core/jwt/JwtProviderImpl.kt +++ b/core/jwt/src/main/java/team/aliens/dms/android/core/jwt/JwtProviderImpl.kt @@ -112,6 +112,10 @@ internal class JwtProviderImpl @Inject constructor( jwtReissueManager(refreshToken = cachedRefreshToken.value) }.onSuccess { tokens -> this@JwtProviderImpl.updateTokens(tokens = tokens) + }.onFailure { exception -> + if (exception is retrofit2.HttpException && exception.code() == 401) { + this@JwtProviderImpl.clearCaches() + } } this@JwtProviderImpl.refreshTokenAbility() } diff --git a/core/ui/src/dev/java/team/aliens/dms/android/core/ui/navigation/ResultStore.kt b/core/ui/src/dev/java/team/aliens/dms/android/core/ui/navigation/ResultStore.kt new file mode 100644 index 000000000..9bea507d2 --- /dev/null +++ b/core/ui/src/dev/java/team/aliens/dms/android/core/ui/navigation/ResultStore.kt @@ -0,0 +1,84 @@ +package team.aliens.dms.android.core.ui.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.ProvidableCompositionLocal +import androidx.compose.runtime.ProvidedValue +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.rememberSaveable + +/** + * Local for storing results in a [ResultStore] + */ +object LocalResultStore { + private val LocalResultStore: ProvidableCompositionLocal = + compositionLocalOf { null } + + /** + * The current [ResultStore] + */ + val current: ResultStore + @Composable + get() = LocalResultStore.current ?: error("No ResultStore has been provided") + + /** + * Provides a [ResultStore] to the composition + */ + infix fun provides( + store: ResultStore + ): ProvidedValue { + return LocalResultStore.provides(store) + } +} + +/** + * Provides a [ResultStore] that will be remembered across configuration changes. + */ +@Composable +fun rememberResultStore(): ResultStore { + return rememberSaveable(saver = ResultStoreSaver()) { + ResultStore() + } +} + +/** + * A store for passing results between multiple sets of screens. + * + * It provides a solution for state based results. + */ +class ResultStore { + + /** + * Map from the result key to a mutable state of the result. + */ + val resultStateMap: MutableMap> = mutableMapOf() + + /** + * Retrieves the current result of the given resultKey. + */ + inline fun getResultState(resultKey: String = T::class.toString()) = + resultStateMap[resultKey]?.value as T + + /** + * Sets the result for the given resultKey. + */ + inline fun setResult(resultKey: String = T::class.toString(), result: T) { + resultStateMap[resultKey] = mutableStateOf(result) + } + + /** + * Removes all results associated with the given key from the store. + */ + inline fun removeResult(resultKey: String = T::class.toString()) { + resultStateMap.remove(resultKey) + } +} + +/** Saver to save and restore the NavController across config change and process death. */ +private fun ResultStoreSaver(): Saver = + Saver( + save = { it.resultStateMap }, + restore = { ResultStore().apply { resultStateMap.putAll(it) } }, + ) \ No newline at end of file diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt index 0f4c12e8f..d3dc3542d 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt @@ -1,5 +1,6 @@ package team.aliens.dms.android.feature.main.application.ui +import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -8,8 +9,10 @@ import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel @@ -19,6 +22,8 @@ import team.aliens.dms.android.core.designsystem.DmsTheme import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType import team.aliens.dms.android.core.designsystem.tab.DmsTab import team.aliens.dms.android.core.designsystem.tab.DmsTabRow +import team.aliens.dms.android.core.ui.navigation.LocalResultStore +import team.aliens.dms.android.core.ui.navigation.rememberResultStore import team.aliens.dms.android.data.voting.model.AllVoteSearch import team.aliens.dms.android.feature.main.application.viewmodel.ApplicationState import team.aliens.dms.android.feature.main.application.viewmodel.ApplicationViewModel @@ -35,8 +40,18 @@ internal fun Application( ) { val viewModel: ApplicationViewModel = hiltViewModel() val state by viewModel.uiState.collectAsStateWithLifecycle() + val resultStore = LocalResultStore.current - + LaunchedEffect(Unit) { + snapshotFlow { + resultStore.resultStateMap["remain_application_result"]?.value as? String? + }.collect { result -> + if (result != null) { + // ViewModel 업데이트 + resultStore.removeResult(resultKey = "remain_application_result") + } + } + } ApplicationScreen( state = state, diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/navigation/RemainApplicationRoute.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/navigation/RemainApplicationRoute.kt index f1e112d20..6a1ade343 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/navigation/RemainApplicationRoute.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/navigation/RemainApplicationRoute.kt @@ -5,9 +5,9 @@ import team.aliens.dms.android.feature.remain.ui.RemainApplication @Composable fun RemainApplicationRoute( - onBackPressed: () -> Unit, + onNavigateBack: (String) -> Unit, ) { RemainApplication( - onNavigateBack = onBackPressed, + onNavigateBack = onNavigateBack, ) } diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt index 4643326b7..24187f86d 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt @@ -1,5 +1,6 @@ package team.aliens.dms.android.feature.remain.ui +import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -33,7 +34,7 @@ import java.util.UUID @Composable internal fun RemainApplication( - onNavigateBack: () -> Unit, + onNavigateBack: (String) -> Unit, ) { val remainApplicationViewModel: RemainApplicationViewModel = hiltViewModel() val state by remainApplicationViewModel.uiState.collectAsStateWithLifecycle() @@ -48,7 +49,7 @@ internal fun RemainApplication( @Composable private fun RemainApplicationScreen( - onNavigateBack: () -> Unit, + onNavigateBack: (String) -> Unit, state: RemainApplicationState, setSelectRemainsOption: (UUID) -> Unit, changeRemainsOption: () -> Unit, @@ -60,7 +61,7 @@ private fun RemainApplicationScreen( ) { DmsTopAppBar( title = "잔류 신청", - onBackPressed = onNavigateBack, + onBackPressed = { onNavigateBack(state.selectedRemainTitle) }, ) DmsFloatingNotice( modifier = Modifier @@ -107,7 +108,7 @@ private fun RemainApplicationScreen( text = "변경하기", buttonType = ButtonType.Contained, buttonColor = ButtonColor.Primary, - enabled = state.selectRemainsOptionId.toString().isNotEmpty(), + enabled = state.selectRemainsOptionId != null, onClick = changeRemainsOption, ) } diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt index 94c8c5fac..fc1771b43 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt @@ -4,7 +4,6 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import team.aliens.dms.android.core.ui.viewmodel.BaseStateViewModel -import team.aliens.dms.android.data.remain.model.AppliedRemainsOption import team.aliens.dms.android.data.remain.model.RemainsApplicationTime import team.aliens.dms.android.data.remain.model.RemainsOption import team.aliens.dms.android.data.remain.repository.RemainsRepository @@ -35,7 +34,7 @@ class RemainApplicationViewModel @Inject constructor( } } - private fun getRemainsApplicationTime() { + private fun getRemainsApplicationTime() { // TODO :: 2일 이상인 경우 시간이 아닌 날짜로 선택할 수 있도록 구현 viewModelScope.launch { remainsRepository.fetchRemainsApplicationTime().onSuccess { time -> setState { it.copy(remainsApplicationTime = time) } @@ -50,9 +49,10 @@ class RemainApplicationViewModel @Inject constructor( internal fun changeRemainsOption() { viewModelScope.launch { val remainOptionId = uiState.value.selectRemainsOptionId - remainsRepository.updateRemainsOption(optionId = remainOptionId!!).onSuccess { + remainsRepository.updateRemainsOption(optionId = remainOptionId ?: UUID.randomUUID()).onSuccess { val remainsOptions = uiState.value.remainsOptions.map { remainsOption -> if (remainsOption.id == uiState.value.selectRemainsOptionId) { + setState { it.copy(selectedRemainTitle = remainsOption.title) } remainsOption.copy(applied = true) } else { remainsOption.copy(applied = false) @@ -66,6 +66,7 @@ class RemainApplicationViewModel @Inject constructor( data class RemainApplicationState( val remainsOptions: List = emptyList(), - val selectRemainsOptionId: UUID = UUID.randomUUID(), + val selectRemainsOptionId: UUID? = null, + val selectedRemainTitle: String = "금요 귀가", val remainsApplicationTime: RemainsApplicationTime = RemainsApplicationTime(), ) From eacf8205743bb6a6ff2a8e75c88d9f5fbe3ca8af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Sat, 27 Dec 2025 20:04:43 +0900 Subject: [PATCH 14/33] =?UTF-8?q?refactor=20::=20=EC=9E=94=EB=A5=98=20?= =?UTF-8?q?=EC=8B=A0=EC=B2=AD=20=EC=8B=9C=EA=B0=84=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../viewmodel/RemainApplicationViewModel.kt | 65 ++++++++++++++++--- 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt index fc1771b43..857062573 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt @@ -46,20 +46,65 @@ class RemainApplicationViewModel @Inject constructor( setState { it.copy(selectRemainsOptionId = remainsOptionId) } } - internal fun changeRemainsOption() { + internal fun changeRemainsOption( + onShowSnackBar: (DmsSnackBarType, String) -> Unit, + ) { viewModelScope.launch { + if (!isWithinApplicationTime()) { + onShowSnackBar(DmsSnackBarType.ERROR, "잔류 신청 시간이 아닙니다") + return@launch + } val remainOptionId = uiState.value.selectRemainsOptionId - remainsRepository.updateRemainsOption(optionId = remainOptionId ?: UUID.randomUUID()).onSuccess { - val remainsOptions = uiState.value.remainsOptions.map { remainsOption -> - if (remainsOption.id == uiState.value.selectRemainsOptionId) { - setState { it.copy(selectedRemainTitle = remainsOption.title) } - remainsOption.copy(applied = true) - } else { - remainsOption.copy(applied = false) + remainsRepository.updateRemainsOption(optionId = remainOptionId ?: UUID.randomUUID()) + .onSuccess { + val remainsOptions = uiState.value.remainsOptions.map { remainsOption -> + if (remainsOption.id == uiState.value.selectRemainsOptionId) { + setState { it.copy(selectedRemainTitle = remainsOption.title) } + remainsOption.copy(applied = true) + } else { + remainsOption.copy(applied = false) + } + } + val appliedOption = remainsOptions.find { it.applied } + setState { + it.copy( + remainsOptions = remainsOptions, + selectedRemainTitle = appliedOption?.title + ) } + onShowSnackBar(DmsSnackBarType.SUCCESS, "잔류 신청이 완료되었습니다") + }.onFailure { + onShowSnackBar(DmsSnackBarType.ERROR, "잔류 신청에 실패했습니다") } - setState { it.copy(remainsOptions = remainsOptions) } - } + } + } + + private fun isWithinApplicationTime(): Boolean { + val now = LocalDateTime.now() + val currentDayOfWeek = now.dayOfWeek.value + val currentTime = now.toLocalTime() + + val applicationTime = uiState.value.remainsApplicationTime + + if (applicationTime.startTime.isEmpty() || applicationTime.endTime.isEmpty()) { + return true + } + + val startTime = LocalTime.parse(applicationTime.startTime) + val endTime = LocalTime.parse(applicationTime.endTime) + val startDayValue = applicationTime.startDayOfWeek.value + val endDayValue = applicationTime.endDayOfWeek.value + + if (startDayValue == endDayValue) { + return currentDayOfWeek == startDayValue && + currentTime >= startTime && currentTime <= endTime + } + + return when { + currentDayOfWeek == startDayValue -> currentTime >= startTime + currentDayOfWeek == endDayValue -> currentTime <= endTime + currentDayOfWeek in (startDayValue + 1).. true + else -> false } } } From 2167158cd56fd23ea33a0c383c3cd9992eb58ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Sat, 27 Dec 2025 20:05:04 +0900 Subject: [PATCH 15/33] =?UTF-8?q?feat=20::=20=EC=9E=94=EB=A5=98=20?= =?UTF-8?q?=EC=8B=A0=EC=B2=AD=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev/kotlin/team/aliens/dms/android/app/DmsApp.kt | 3 +++ .../remain/navigation/RemainApplicationRoute.kt | 5 ++++- .../feature/remain/ui/RemainApplicationScreen.kt | 10 +++++++--- .../remain/viewmodel/RemainApplicationViewModel.kt | 10 +++++++--- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt b/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt index 8717d5e5b..24d47d213 100644 --- a/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt +++ b/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt @@ -163,6 +163,9 @@ fun DmsApp( onNavigateBack = { title -> resultStore.setResult("remain_application_result", title) backStack.remove(RemainScreenNav) + }, + onShowSnackBar = { snackBarType, message -> + appState.showSnackBar(snackBarType, message) } ) } diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/navigation/RemainApplicationRoute.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/navigation/RemainApplicationRoute.kt index 6a1ade343..463d11170 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/navigation/RemainApplicationRoute.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/navigation/RemainApplicationRoute.kt @@ -1,13 +1,16 @@ package team.aliens.dms.android.feature.remain.navigation import androidx.compose.runtime.Composable +import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType import team.aliens.dms.android.feature.remain.ui.RemainApplication @Composable fun RemainApplicationRoute( - onNavigateBack: (String) -> Unit, + onNavigateBack: (String?) -> Unit, + onShowSnackBar: (DmsSnackBarType, String) -> Unit ) { RemainApplication( onNavigateBack = onNavigateBack, + onShowSnackBar = onShowSnackBar, ) } diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt index 24187f86d..af672137d 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt @@ -24,6 +24,7 @@ import team.aliens.dms.android.core.designsystem.button.ButtonType import team.aliens.dms.android.core.designsystem.button.DmsButton import team.aliens.dms.android.core.designsystem.card.DmsApplicationCard import team.aliens.dms.android.core.designsystem.horizontalPadding +import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType import team.aliens.dms.android.core.designsystem.topPadding import team.aliens.dms.android.core.designsystem.verticalPadding import team.aliens.dms.android.core.ui.util.toLocale @@ -34,7 +35,8 @@ import java.util.UUID @Composable internal fun RemainApplication( - onNavigateBack: (String) -> Unit, + onNavigateBack: (String?) -> Unit, + onShowSnackBar: (DmsSnackBarType, String) -> Unit, ) { val remainApplicationViewModel: RemainApplicationViewModel = hiltViewModel() val state by remainApplicationViewModel.uiState.collectAsStateWithLifecycle() @@ -43,13 +45,15 @@ internal fun RemainApplication( onNavigateBack = onNavigateBack, state = state, setSelectRemainsOption = remainApplicationViewModel::setSelectRemainsOption, - changeRemainsOption = remainApplicationViewModel::changeRemainsOption, + changeRemainsOption = { + remainApplicationViewModel.changeRemainsOption(onShowSnackBar) + }, ) } @Composable private fun RemainApplicationScreen( - onNavigateBack: (String) -> Unit, + onNavigateBack: (String?) -> Unit, state: RemainApplicationState, setSelectRemainsOption: (UUID) -> Unit, changeRemainsOption: () -> Unit, diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt index 857062573..7b451c4ba 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt @@ -3,6 +3,9 @@ package team.aliens.dms.android.feature.remain.viewmodel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch +import org.threeten.bp.LocalDateTime +import org.threeten.bp.LocalTime +import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType import team.aliens.dms.android.core.ui.viewmodel.BaseStateViewModel import team.aliens.dms.android.data.remain.model.RemainsApplicationTime import team.aliens.dms.android.data.remain.model.RemainsOption @@ -23,7 +26,8 @@ class RemainApplicationViewModel @Inject constructor( private fun getRemainsOptions() { viewModelScope.launch { remainsRepository.fetchRemainsOptions().onSuccess { remainsOptions -> - val selectRemainsOptionId = remainsOptions.find { it.applied }?.id ?: UUID.randomUUID() + val selectRemainsOptionId = + remainsOptions.find { it.applied }?.id ?: UUID.randomUUID() setState { it.copy( remainsOptions = remainsOptions, @@ -34,7 +38,7 @@ class RemainApplicationViewModel @Inject constructor( } } - private fun getRemainsApplicationTime() { // TODO :: 2일 이상인 경우 시간이 아닌 날짜로 선택할 수 있도록 구현 + private fun getRemainsApplicationTime() { viewModelScope.launch { remainsRepository.fetchRemainsApplicationTime().onSuccess { time -> setState { it.copy(remainsApplicationTime = time) } @@ -112,6 +116,6 @@ class RemainApplicationViewModel @Inject constructor( data class RemainApplicationState( val remainsOptions: List = emptyList(), val selectRemainsOptionId: UUID? = null, - val selectedRemainTitle: String = "금요 귀가", + val selectedRemainTitle: String? = null, val remainsApplicationTime: RemainsApplicationTime = RemainsApplicationTime(), ) From 24183d070c207ed58b4157df89c5832d8a6527d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Sat, 27 Dec 2025 20:52:34 +0900 Subject: [PATCH 16/33] =?UTF-8?q?refactor=20::=20application=20screen=20ap?= =?UTF-8?q?pliedText=20ui=20=EC=A0=81=EC=9A=A9=20=EB=B0=8F=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/core/designsystem/card/DmsApplicationCard.kt | 7 +++++++ .../feature/main/application/ui/ApplicationScreen.kt | 5 ++--- .../main/application/ui/component/ApplicationContent.kt | 2 ++ .../main/application/viewmodel/ApplicationViewModel.kt | 5 +++++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/card/DmsApplicationCard.kt b/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/card/DmsApplicationCard.kt index 75ac2dbea..4172b55e0 100644 --- a/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/card/DmsApplicationCard.kt +++ b/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/card/DmsApplicationCard.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import team.aliens.dms.android.core.designsystem.DmsTheme import team.aliens.dms.android.core.designsystem.bodyB +import team.aliens.dms.android.core.designsystem.endPadding import team.aliens.dms.android.core.designsystem.foundation.DmsIcon import team.aliens.dms.android.core.designsystem.labelB import team.aliens.dms.android.core.designsystem.labelM @@ -76,6 +77,12 @@ fun DmsApplicationCard( color = DmsTheme.colorScheme.inverseOnSurface, ) Spacer(modifier = Modifier.weight(1f)) + if (description == null && appliedTitle != null) { + AppliedTitleText( + modifier = Modifier.endPadding(16.dp), + appliedTitle = appliedTitle, + ) + } Icon( painter = painterResource(DmsIcon.Forward), tint = DmsTheme.colorScheme.scrim, diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt index d3dc3542d..5dc105f92 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.runtime.Composable @@ -23,7 +22,6 @@ import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType import team.aliens.dms.android.core.designsystem.tab.DmsTab import team.aliens.dms.android.core.designsystem.tab.DmsTabRow import team.aliens.dms.android.core.ui.navigation.LocalResultStore -import team.aliens.dms.android.core.ui.navigation.rememberResultStore import team.aliens.dms.android.data.voting.model.AllVoteSearch import team.aliens.dms.android.feature.main.application.viewmodel.ApplicationState import team.aliens.dms.android.feature.main.application.viewmodel.ApplicationViewModel @@ -47,7 +45,7 @@ internal fun Application( resultStore.resultStateMap["remain_application_result"]?.value as? String? }.collect { result -> if (result != null) { - // ViewModel 업데이트 + viewModel.setRemainApplication(result) resultStore.removeResult(resultKey = "remain_application_result") } } @@ -113,6 +111,7 @@ private fun ApplicationScreen( ) { if (page == 0) { ApplicationContent( + appliedTitle = state.remainApplicationTitle, onNavigateOutingApplication = onNavigateOutingApplication, onNavigateRemainApplication = onNavigateRemainApplication, onNavigateVolunteerApplication = onNavigateVolunteerApplication, diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/ApplicationContent.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/ApplicationContent.kt index a6e66e20d..b40fa90a3 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/ApplicationContent.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/ApplicationContent.kt @@ -14,6 +14,7 @@ import kotlin.to @Composable internal fun ApplicationContent( modifier: Modifier = Modifier, + appliedTitle: String?, onNavigateRemainApplication: () -> Unit, onNavigateOutingApplication: () -> Unit, onNavigateVolunteerApplication: () -> Unit, @@ -36,6 +37,7 @@ internal fun ApplicationContent( title = title, iconRes = icon, onClick = onClick, + appliedTitle = if (title == "잔류") appliedTitle else null, ) } } diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt index 64dfbb51b..eab7f859b 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt @@ -28,8 +28,13 @@ internal class ApplicationViewModel @Inject constructor( } } } + + internal fun setRemainApplication(title: String) { + setState { it.copy(remainApplicationTitle = title) } + } } data class ApplicationState( val votes: List = emptyList(), + val remainApplicationTitle: String? = null, ) From 4c54f55c3d722f25c55e8ea4954d7c9368cd1fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Sat, 27 Dec 2025 20:55:05 +0900 Subject: [PATCH 17/33] =?UTF-8?q?fix=20::=20=ED=88=AC=ED=91=9C=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EB=9E=8C=EB=8B=A4=20=EC=A7=80=EC=A0=95=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/application/viewmodel/ApplicationViewModel.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt index eab7f859b..6b27bfdf2 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt @@ -21,8 +21,8 @@ internal class ApplicationViewModel @Inject constructor( private fun getAllVotes() { viewModelScope.launch(Dispatchers.IO) { votingRepository.fetchAllVoteSearch() - .onSuccess { - setState { it.copy(votes = it.votes) } + .onSuccess { votes -> + setState { it.copy(votes = votes) } }.onFailure { // Logger.a(it) { it.message.toString() } } From 6157fdc11153a3b5879dc0acafa0a8ad12698304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Sat, 27 Dec 2025 21:28:57 +0900 Subject: [PATCH 18/33] =?UTF-8?q?refactor=20::=20=EC=9E=94=EB=A5=98=20?= =?UTF-8?q?=EC=8B=A0=EC=B2=AD=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=A1=9C?= =?UTF-8?q?=EC=BB=AC=EC=97=90=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../viewmodel/ApplicationViewModel.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt index 6b27bfdf2..bbed36381 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/viewmodel/ApplicationViewModel.kt @@ -1,7 +1,10 @@ package team.aliens.dms.android.feature.main.application.viewmodel +import android.content.Context +import android.content.SharedPreferences import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import team.aliens.dms.android.core.ui.viewmodel.BaseStateViewModel @@ -12,10 +15,15 @@ import javax.inject.Inject @HiltViewModel internal class ApplicationViewModel @Inject constructor( private val votingRepository: VotingRepository, + @ApplicationContext private val context: Context, ) : BaseStateViewModel(ApplicationState()) { + private val sharedPreferences: SharedPreferences = + context.getSharedPreferences("application_prefs", Context.MODE_PRIVATE) + init { getAllVotes() + loadAppliedRemainsTitle() } private fun getAllVotes() { @@ -29,9 +37,21 @@ internal class ApplicationViewModel @Inject constructor( } } + private fun loadAppliedRemainsTitle() { + val savedTitle = sharedPreferences.getString(KEY_APPLIED_REMAINS_TITLE, null) + if (savedTitle != null) { + setState { it.copy(remainApplicationTitle = savedTitle) } + } + } + internal fun setRemainApplication(title: String) { + sharedPreferences.edit().putString(KEY_APPLIED_REMAINS_TITLE, title).apply() setState { it.copy(remainApplicationTitle = title) } } + + companion object { + private const val KEY_APPLIED_REMAINS_TITLE = "applied_remains_title" + } } data class ApplicationState( From bdbc00a7107f0f69788947570acae225ac3774f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Sun, 28 Dec 2025 19:34:17 +0900 Subject: [PATCH 19/33] =?UTF-8?q?refactor=20::=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EB=A3=A8=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/feature/main/application/ui/ApplicationScreen.kt | 5 ++--- .../main/application/ui/component/ApplicationContent.kt | 3 +-- .../feature/main/application/ui/component/VoteContent.kt | 3 +-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt index 5dc105f92..ef1d01cc2 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/ApplicationScreen.kt @@ -1,6 +1,5 @@ package team.aliens.dms.android.feature.main.application.ui -import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -25,8 +24,8 @@ import team.aliens.dms.android.core.ui.navigation.LocalResultStore import team.aliens.dms.android.data.voting.model.AllVoteSearch import team.aliens.dms.android.feature.main.application.viewmodel.ApplicationState import team.aliens.dms.android.feature.main.application.viewmodel.ApplicationViewModel -import team.aliens.dms.kmp.feature.application.ui.component.ApplicationContent -import team.aliens.dms.kmp.feature.application.ui.component.VoteContent +import team.aliens.dms.android.feature.main.application.ui.component.ApplicationContent +import team.aliens.dms.android.feature.main.application.ui.component.VoteContent @Composable internal fun Application( diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/ApplicationContent.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/ApplicationContent.kt index b40fa90a3..c57114098 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/ApplicationContent.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/ApplicationContent.kt @@ -1,4 +1,4 @@ -package team.aliens.dms.kmp.feature.application.ui.component +package team.aliens.dms.android.feature.main.application.ui.component import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -9,7 +9,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import team.aliens.dms.android.core.designsystem.R import team.aliens.dms.android.core.designsystem.card.DmsApplicationCard -import kotlin.to @Composable internal fun ApplicationContent( diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt index a44184726..381763e23 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt @@ -1,4 +1,4 @@ -package team.aliens.dms.kmp.feature.application.ui.component +package team.aliens.dms.android.feature.main.application.ui.component import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues @@ -10,7 +10,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import team.aliens.dms.android.core.designsystem.R import team.aliens.dms.android.core.designsystem.card.DmsApplicationCard -import team.aliens.dms.android.core.ui.util.toDateString import team.aliens.dms.android.data.voting.model.AllVoteSearch import team.aliens.dms.android.data.voting.model.Vote From 6630bb55845976b63891db520f1baabfbe33d686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Sun, 28 Dec 2025 19:34:32 +0900 Subject: [PATCH 20/33] =?UTF-8?q?refactor=20::=20vote=20navHost=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/team/aliens/dms/android/app/DmsApp.kt | 11 ++++++++++- .../android/feature/vote/navigation/VoteRoute.kt | 9 +++++++++ .../dms/android/feature/vote/ui/VoteScreen.kt | 13 +++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/navigation/VoteRoute.kt create mode 100644 feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt diff --git a/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt b/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt index 24d47d213..4a1316f61 100644 --- a/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt +++ b/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt @@ -33,6 +33,7 @@ import team.aliens.dms.android.feature.meal.navigation.MealRoute import team.aliens.dms.android.feature.onboarding.navigation.OnboardingRoute import team.aliens.dms.android.feature.remain.navigation.RemainApplicationRoute import team.aliens.dms.android.feature.signin.navigation.SignInRoute +import team.aliens.dms.android.feature.vote.navigation.VoteRoute @Serializable data object OnboardingScreenNav : NavKey @@ -49,6 +50,9 @@ data object MealScreenNav : NavKey @Serializable data object ApplicationScreenNav : NavKey +@Serializable +data object VoteScreenNav : NavKey + @Serializable data object RemainScreenNav : NavKey @@ -152,12 +156,17 @@ fun DmsApp( }, onNavigateOutingApplication = {}, onNavigateVolunteerApplication = {}, - onNavigateVote = {}, + onNavigateVote = { + backStack.add(VoteScreenNav) + }, onShowSnackBar = { snackBarType, message -> appState.showSnackBar(snackBarType, message) }, ) } + entry { + VoteRoute() + } entry { RemainApplicationRoute( onNavigateBack = { title -> diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/navigation/VoteRoute.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/navigation/VoteRoute.kt new file mode 100644 index 000000000..8d54a47d4 --- /dev/null +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/navigation/VoteRoute.kt @@ -0,0 +1,9 @@ +package team.aliens.dms.android.feature.vote.navigation + +import androidx.compose.runtime.Composable +import team.aliens.dms.android.feature.vote.ui.Vote + +@Composable +fun VoteRoute() { + Vote() +} diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt new file mode 100644 index 000000000..48f0400e4 --- /dev/null +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt @@ -0,0 +1,13 @@ +package team.aliens.dms.android.feature.vote.ui + +import androidx.compose.runtime.Composable + +@Composable +internal fun Vote() { + VoteScreen() +} + +@Composable +private fun VoteScreen() { + +} From 42fcb524ccad5764b85b5e88b89491604cdf169f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Sun, 28 Dec 2025 23:29:42 +0900 Subject: [PATCH 21/33] =?UTF-8?q?feat=20::=20=ED=88=AC=ED=91=9C=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=B0=8F=20Shadow=20modifier=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .../modifier/DmsShadowModifier.kt | 60 +++++++++++++++++++ .../src/dev/res/drawable/ic_approve.xml | 11 ++++ .../src/dev/res/drawable/ic_oppose.xml | 9 +++ 3 files changed, 80 insertions(+) create mode 100644 core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/modifier/DmsShadowModifier.kt create mode 100644 core/design-system/src/dev/res/drawable/ic_approve.xml create mode 100644 core/design-system/src/dev/res/drawable/ic_oppose.xml diff --git a/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/modifier/DmsShadowModifier.kt b/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/modifier/DmsShadowModifier.kt new file mode 100644 index 000000000..eab988009 --- /dev/null +++ b/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/modifier/DmsShadowModifier.kt @@ -0,0 +1,60 @@ +package team.aliens.dms.android.core.designsystem.modifier + +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.dp + +@Composable +fun Modifier.dmsShadowModifier( + dmsShadowType: DmsShadowType, + shape: Shape = CircleShape, +): Modifier { + return when (dmsShadowType) { + DmsShadowType.Light10 -> this.dmsDropShadow( + shape = shape, + color = Color.Black.copy(alpha = 0.08f), + blur = 10.dp, + offsetX = 0.dp, + offsetY = 4.dp, + ) + + DmsShadowType.Light20 -> this.dmsDropShadow( + shape = shape, + color = Color.Black.copy(alpha = 0.1f), + blur = 15.dp, + offsetX = 0.dp, + offsetY = 1.dp, + ) + + DmsShadowType.Standard -> this.dmsDropShadow( + shape = shape, + color = Color.Black.copy(alpha = 0.19f), + blur = 20.dp, + offsetX = 0.dp, + offsetY = 3.dp, + ) + + DmsShadowType.Dark10 -> this.dmsDropShadow( + shape = shape, + color = Color.Black.copy(alpha = 0.25f), + blur = 14.dp, + offsetX = 0.dp, + offsetY = 14.dp, + ) + + DmsShadowType.Dark20 -> this.dmsDropShadow( + shape = shape, + color = Color.Black.copy(alpha = 0.30f), + blur = 19.dp, + offsetX = 0.dp, + offsetY = 19.dp, + ) + } +} + +enum class DmsShadowType { + Light10, Light20, Standard, Dark10, Dark20 +} diff --git a/core/design-system/src/dev/res/drawable/ic_approve.xml b/core/design-system/src/dev/res/drawable/ic_approve.xml new file mode 100644 index 000000000..436407aaf --- /dev/null +++ b/core/design-system/src/dev/res/drawable/ic_approve.xml @@ -0,0 +1,11 @@ + + + diff --git a/core/design-system/src/dev/res/drawable/ic_oppose.xml b/core/design-system/src/dev/res/drawable/ic_oppose.xml new file mode 100644 index 000000000..939d463a9 --- /dev/null +++ b/core/design-system/src/dev/res/drawable/ic_oppose.xml @@ -0,0 +1,9 @@ + + + From 0c5bdbd0441b3f259476f5c62cbe99c8f01942ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Sun, 28 Dec 2025 23:30:07 +0900 Subject: [PATCH 22/33] =?UTF-8?q?refactor=20::=20voting=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EB=A0=88=EC=9D=B4=EC=96=B4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- data/build.gradle.kts | 1 + .../voting/mapper/VotingMapper.kt | 10 +++++----- .../voting/model/AllVoteSearch.kt | 15 ++++++++------- .../voting/repository/VotingRepository.kt | 4 ++-- .../voting/repository/VotingRepositoryImpl.kt | 4 ++-- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 0cd43d399..f2c1c9689 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -7,6 +7,7 @@ plugins { alias(libs.plugins.hilt) alias(libs.plugins.ksp) alias(libs.plugins.ktlint.gradle) + alias(libs.plugins.serialization) } android { diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/voting/mapper/VotingMapper.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/mapper/VotingMapper.kt index 34e6024f1..4bd632c8b 100644 --- a/data/src/dev/kotlin/team.aliens.dms.android.data/voting/mapper/VotingMapper.kt +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/mapper/VotingMapper.kt @@ -1,7 +1,7 @@ package team.aliens.dms.android.data.voting.mapper +import team.aliens.dms.android.data.student.model.Student import team.aliens.dms.android.data.voting.model.AllVoteSearch -import team.aliens.dms.android.data.voting.model.ModelStudentCandidates import team.aliens.dms.android.data.voting.model.Vote import team.aliens.dms.android.data.voting.model.VotingItem import team.aliens.dms.android.network.voting.model.FetchAllVoteSearchResponse @@ -33,13 +33,13 @@ private fun FetchCheckVotingItemResponse.VotingItemResponse.toModel(): VotingIte votingOptionName = votingOptionName, ) -internal fun FetchModelStudentCandidatesResponse.toModel(): List = +internal fun FetchModelStudentCandidatesResponse.toModel(): List = this.students.map(FetchModelStudentCandidatesResponse.ModelStudentCandidatesResponse::toModel) -private fun FetchModelStudentCandidatesResponse.ModelStudentCandidatesResponse.toModel(): ModelStudentCandidates = - ModelStudentCandidates( +private fun FetchModelStudentCandidatesResponse.ModelStudentCandidatesResponse.toModel(): Student = + Student( id = this.id, - studentGcn = this.studentGcn, + gradeClassNumber = this.studentGcn.toString(), name = this.name, profileImageUrl = this.profileImageUrl, ) diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/AllVoteSearch.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/AllVoteSearch.kt index 4eef97aca..4ff013b7e 100644 --- a/data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/AllVoteSearch.kt +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/model/AllVoteSearch.kt @@ -1,14 +1,15 @@ package team.aliens.dms.android.data.voting.model +import team.aliens.dms.android.shared.date.util.now import java.time.LocalDateTime import java.util.UUID data class AllVoteSearch( - val id: UUID, - val topicName: String, - val description: String, - val startTime: LocalDateTime, - val endTime: LocalDateTime, - val voteType: Vote, - val isVoted: Boolean, + val id: UUID = UUID.randomUUID(), + val topicName: String = "", + val description: String = "", + val startTime: LocalDateTime = now, + val endTime: LocalDateTime = now, + val voteType: Vote = Vote.STUDENT_VOTE, + val isVoted: Boolean = false, ) diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepository.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepository.kt index b8507c024..c697017e5 100644 --- a/data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepository.kt +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepository.kt @@ -1,8 +1,8 @@ package team.aliens.dms.android.data.voting.repository +import team.aliens.dms.android.data.student.model.Student import java.time.LocalDate import team.aliens.dms.android.data.voting.model.AllVoteSearch -import team.aliens.dms.android.data.voting.model.ModelStudentCandidates import team.aliens.dms.android.data.voting.model.VotingItem import java.util.UUID @@ -19,5 +19,5 @@ abstract class VotingRepository { abstract suspend fun fetchDeleteVotingItem(voteId: UUID): Result - abstract suspend fun fetchModelStudentCandidates(requestDate: LocalDate): Result> + abstract suspend fun fetchModelStudentCandidates(requestDate: LocalDate): Result> } diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepositoryImpl.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepositoryImpl.kt index 0d9675bc0..7106995d0 100644 --- a/data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepositoryImpl.kt +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepositoryImpl.kt @@ -1,9 +1,9 @@ package team.aliens.dms.android.data.voting.repository +import team.aliens.dms.android.data.student.model.Student import java.time.LocalDate import team.aliens.dms.android.data.voting.mapper.toModel import team.aliens.dms.android.data.voting.model.AllVoteSearch -import team.aliens.dms.android.data.voting.model.ModelStudentCandidates import team.aliens.dms.android.data.voting.model.VotingItem import team.aliens.dms.android.network.voting.datasource.NetworkVotingDataSource import java.util.UUID @@ -28,7 +28,7 @@ internal class VotingRepositoryImpl @Inject constructor( networkVotingDataSource.fetchDeleteVotingItem(voteId) } - override suspend fun fetchModelStudentCandidates(requestDate: LocalDate): Result> = runCatching { + override suspend fun fetchModelStudentCandidates(requestDate: LocalDate): Result> = runCatching { networkVotingDataSource.fetchModelStudentCandidates(requestDate).toModel() } } From d578bf145ee2fef313167d9958a9f68a869e8557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Sun, 28 Dec 2025 23:34:37 +0900 Subject: [PATCH 23/33] =?UTF-8?q?feat=20::=20=ED=88=AC=ED=91=9C=20UI=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .../vote/ui/component/ApprovalContent.kt | 151 +++++++++++++++++ .../vote/ui/component/OptionContent.kt | 101 +++++++++++ .../vote/ui/component/StudentContent.kt | 160 ++++++++++++++++++ .../feature/vote/ui/component/TitleContent.kt | 41 +++++ .../vote/ui/component/VoteItemContent.kt | 71 ++++++++ 5 files changed, 524 insertions(+) create mode 100644 feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/ApprovalContent.kt create mode 100644 feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/OptionContent.kt create mode 100644 feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/StudentContent.kt create mode 100644 feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/TitleContent.kt create mode 100644 feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/VoteItemContent.kt diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/ApprovalContent.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/ApprovalContent.kt new file mode 100644 index 000000000..69c18590c --- /dev/null +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/ApprovalContent.kt @@ -0,0 +1,151 @@ + +package team.aliens.dms.android.feature.vote.ui.component + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +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.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import team.aliens.dms.android.core.designsystem.DmsTheme +import team.aliens.dms.android.core.designsystem.R +import team.aliens.dms.android.core.designsystem.bodyM +import team.aliens.dms.android.core.designsystem.modifier.DmsShadowType +import team.aliens.dms.android.core.designsystem.modifier.dmsShadowModifier +import team.aliens.dms.android.core.designsystem.titleB +import team.aliens.dms.android.core.designsystem.util.clickable +import team.aliens.dms.android.data.voting.model.VotingItem + +@Composable +internal fun ApprovalContent( + modifier: Modifier = Modifier, + title: String, + options: List, + selectItem: String, + onSelect: (String) -> Unit, +) { + Column( + modifier = modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy( + space = 60.dp, + alignment = Alignment.CenterVertically, + ), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp), + text = title, + style = DmsTheme.typography.titleB, + color = DmsTheme.colorScheme.surfaceContainer, + textAlign = TextAlign.Center, + ) + Row( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(0.4f) + .padding(horizontal = 24.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + options.forEachIndexed { index, option -> + if (index == 0) { + ApprovalItem( + modifier = Modifier.weight(1f), + imageResource = R.drawable.ic_approve, + isSelected = option.id.toString() == selectItem, + clickColor = DmsTheme.colorScheme.onPrimary, + clickBorderColor = DmsTheme.colorScheme.onPrimaryContainer, + title = option.votingOptionName, + contentColor = DmsTheme.colorScheme.onPrimaryContainer, + clickContentColor = DmsTheme.colorScheme.inversePrimary, + onClick = { onSelect(option.id.toString()) }, + ) + } else { + ApprovalItem( + modifier = Modifier.weight(1f), + imageResource = R.drawable.ic_oppose, + isSelected = option.id.toString() == selectItem, + clickColor = DmsTheme.colorScheme.onError, + clickBorderColor = DmsTheme.colorScheme.onErrorContainer, + title = option.votingOptionName, + contentColor = DmsTheme.colorScheme.onErrorContainer, + clickContentColor = DmsTheme.colorScheme.outline, + onClick = { onSelect(option.id.toString()) }, + ) + } + } + } + } +} + +@Composable +private fun ApprovalItem( + modifier: Modifier = Modifier, + @DrawableRes imageResource: Int, + isSelected: Boolean, + clickColor: Color, + clickBorderColor: Color, + contentColor: Color, + clickContentColor: Color, + title: String, + onClick: () -> Unit, +) { + val (backgroundColor, borderColor, content) = if (isSelected) { + Triple(clickColor, clickBorderColor, clickContentColor) + } else { + Triple(DmsTheme.colorScheme.surfaceTint, DmsTheme.colorScheme.surfaceVariant, contentColor) + } + Card( + modifier = modifier + .dmsShadowModifier( + dmsShadowType = DmsShadowType.Light20, + shape = RoundedCornerShape(12.dp), + ) + .clickable(onClick = onClick), + colors = CardDefaults.cardColors( + containerColor = backgroundColor, + ), + shape = RoundedCornerShape(32.dp), + border = BorderStroke( + width = 2.dp, + color = borderColor, + ), + ) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy( + space = 20.dp, + alignment = Alignment.CenterVertically, + ), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + painter = painterResource(imageResource), + tint = content, + contentDescription = null, + ) + Text( + text = title, + style = DmsTheme.typography.bodyM, + color = content, + ) + } + } +} diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/OptionContent.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/OptionContent.kt new file mode 100644 index 000000000..3109cfb6f --- /dev/null +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/OptionContent.kt @@ -0,0 +1,101 @@ +package team.aliens.dms.android.feature.vote.ui.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.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.HorizontalDivider +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.unit.dp +import java.time.LocalDateTime +import team.aliens.dms.android.core.designsystem.DmsTheme +import team.aliens.dms.android.core.designsystem.bodyB +import team.aliens.dms.android.core.designsystem.foundation.DmsIcon +import team.aliens.dms.android.core.designsystem.startPadding +import team.aliens.dms.android.core.designsystem.util.clickable +import team.aliens.dms.android.data.voting.model.VotingItem + +@Composable +internal fun OptionContent( + modifier: Modifier = Modifier, + title: String, + startTime: LocalDateTime, + endTime: LocalDateTime, + options: List, + selectItem: String, + onSelect: (String) -> Unit, +) { + Column( + modifier = modifier, + ) { + TitleContent( + title = title, + startTime = startTime, + endTime = endTime, + ) + HorizontalDivider( + modifier = Modifier.fillMaxWidth(), + thickness = 0.4.dp, + color = DmsTheme.colorScheme.onSurfaceVariant, + ) + LazyColumn { + items( + items = options, + key = { it.id }, + ) { option -> + OptionItem( + title = option.votingOptionName, + selected = option.id.toString() == selectItem, + onClick = { onSelect(option.id.toString()) }, + ) + } + } + } +} + +@Composable +private fun OptionItem( + modifier: Modifier = Modifier, + title: String, + selected: Boolean, + onClick: () -> Unit, +) { + val backgroundColor = if (selected) { + DmsTheme.colorScheme.surfaceVariant + } else { + DmsTheme.colorScheme.background + } + Row( + modifier = modifier + .fillMaxWidth() + .background(backgroundColor) + .clickable(onClick = onClick) + .padding( + horizontal = 24.dp, + vertical = 18.dp, + ), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + modifier = Modifier.startPadding(12.dp), + text = title, + style = DmsTheme.typography.bodyB, + color = DmsTheme.colorScheme.inverseOnSurface, + ) + Spacer(modifier = Modifier.weight(1f)) + Icon( + painter = painterResource(DmsIcon.Forward), + tint = DmsTheme.colorScheme.scrim, + contentDescription = null, + ) + } +} diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/StudentContent.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/StudentContent.kt new file mode 100644 index 000000000..5b58317cc --- /dev/null +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/StudentContent.kt @@ -0,0 +1,160 @@ +package team.aliens.dms.android.feature.vote.ui.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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import coil.request.ImageRequest +import kotlinx.coroutines.launch +import team.aliens.dms.android.core.designsystem.DmsTheme +import team.aliens.dms.android.core.designsystem.bodyB +import team.aliens.dms.android.core.designsystem.foundation.DmsIcon +import team.aliens.dms.android.core.designsystem.startPadding +import team.aliens.dms.android.core.designsystem.tab.DmsTab +import team.aliens.dms.android.core.designsystem.tab.DmsTabRow +import team.aliens.dms.android.core.designsystem.util.clickable +import team.aliens.dms.android.data.student.model.Student +import java.time.LocalDateTime + +@Composable +internal fun StudentContent( + modifier: Modifier = Modifier, + title: String, + startTime: LocalDateTime, + endTime: LocalDateTime, + students: List, + selectItem: String, + onSelect: (String) -> Unit, +) { + val grades = listOf("1학년", "2학년", "3학년") + val pagerState = rememberPagerState( + pageCount = { grades.size }, + initialPage = 0, + ) + val tabIndex = pagerState.currentPage + val coroutineScope = rememberCoroutineScope() + + Column( + modifier = modifier.fillMaxSize(), + ) { + TitleContent( + title = title, + startTime = startTime, + endTime = endTime, + ) + HorizontalDivider( + modifier = Modifier.fillMaxWidth(), + thickness = 0.4.dp, + color = DmsTheme.colorScheme.onSurfaceVariant, + ) + DmsTabRow( + selectedTabIndex = tabIndex, + ) { + grades.forEachIndexed { index, text -> + DmsTab( + selected = tabIndex == index, + onClick = { + coroutineScope.launch { + pagerState.animateScrollToPage(index) + } + }, + text = text, + ) + } + } + HorizontalPager( + modifier = Modifier.fillMaxWidth(), + state = pagerState, + beyondViewportPageCount = 1, + ) { page -> + val selectGrade = page + 1 + LazyColumn(modifier = Modifier.fillMaxSize()) { + val filteredStudents = students.filter { it.gradeClassNumber.startsWith("$selectGrade") } + items( + items = filteredStudents, + key = { student -> student.id }, + ) { student -> + StudentItem( + profileImageUrl = student.profileImageUrl, + name = student.name, + gcn = student.gradeClassNumber, + isSelected = selectItem == student.id.toString(), + onClick = { onSelect(student.id.toString()) }, + ) + } + } + } + } +} + +@Composable +private fun StudentItem( + modifier: Modifier = Modifier, + profileImageUrl: String, + name: String, + gcn: String, + isSelected: Boolean, + onClick: () -> Unit, +) { + val backgroundColor = if (isSelected) { + DmsTheme.colorScheme.surfaceVariant + } else { + DmsTheme.colorScheme.background + } + Row( + modifier = modifier + .fillMaxWidth() + .background(backgroundColor) + .clickable(onClick = onClick) + .padding( + horizontal = 24.dp, + vertical = 18.dp, + ), + verticalAlignment = Alignment.CenterVertically, + ) { + AsyncImage( + modifier = Modifier + .size(32.dp) + .clip(CircleShape), + model = ImageRequest.Builder(context = LocalContext.current) + .data(profileImageUrl) + .build(), + contentDescription = null, + contentScale = ContentScale.Crop, + ) + Text( + modifier = Modifier.startPadding(12.dp), + text = "$gcn $name", + style = DmsTheme.typography.bodyB, + color = DmsTheme.colorScheme.inverseOnSurface, + ) + Spacer(modifier = Modifier.weight(1f)) + Icon( + painter = painterResource(DmsIcon.Forward), + tint = DmsTheme.colorScheme.scrim, + contentDescription = null, + ) + } +} diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/TitleContent.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/TitleContent.kt new file mode 100644 index 000000000..5534f94db --- /dev/null +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/TitleContent.kt @@ -0,0 +1,41 @@ +package team.aliens.dms.android.feature.vote.ui.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import java.time.LocalDateTime +import team.aliens.dms.android.core.designsystem.DmsTheme +import team.aliens.dms.android.core.designsystem.lBodyB +import team.aliens.dms.android.core.designsystem.labelM +import team.aliens.dms.android.core.ui.util.toDateString + +@Composable +internal fun TitleContent( + modifier: Modifier = Modifier, + title: String, + startTime: LocalDateTime, + endTime: LocalDateTime, +) { + Column( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 24.dp, vertical = 8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + Text( + text = title, + style = DmsTheme.typography.lBodyB, + color = DmsTheme.colorScheme.tertiaryContainer, + ) + Text( + text = "${startTime.toDateString()} ~ ${endTime.toDateString()}", + style = DmsTheme.typography.labelM, + color = DmsTheme.colorScheme.inverseOnSurface, + ) + } +} diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/VoteItemContent.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/VoteItemContent.kt new file mode 100644 index 000000000..7b3b7aa02 --- /dev/null +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/component/VoteItemContent.kt @@ -0,0 +1,71 @@ +package team.aliens.dms.android.feature.vote.ui.component + +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import team.aliens.dms.android.data.student.model.Student +import team.aliens.dms.android.data.voting.model.Vote +import team.aliens.dms.android.data.voting.model.VotingItem +import java.time.LocalDateTime + +@Composable +internal fun VoteItemContent( + modifier: Modifier = Modifier, + vote: Vote, + title: String, + startTime: LocalDateTime, + endTime: LocalDateTime, + options: List, + students: List, + modelStudents: List, + selectItem: String, + onSelect: (String) -> Unit, +) { + Column( + modifier = modifier, + ) { + when (vote) { + Vote.OPTION_VOTE -> { + OptionContent( + title = title, + startTime = startTime, + endTime = endTime, + options = options, + selectItem = selectItem, + onSelect = onSelect, + ) + } + + Vote.STUDENT_VOTE -> { + StudentContent( + title = title, + startTime = startTime, + endTime = endTime, + students = students, + selectItem = selectItem, + onSelect = onSelect, + ) + } + + Vote.APPROVAL_VOTE -> { + ApprovalContent( + title = title, + options = options, + selectItem = selectItem, + onSelect = onSelect, + ) + } + + Vote.MODEL_STUDENT_VOTE -> { + StudentContent( + title = title, + startTime = startTime, + endTime = endTime, + students = modelStudents, + selectItem = selectItem, + onSelect = onSelect, + ) + } + } + } +} From 7ac60c6b71838a08f9e38e66343f6d804aebc6ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Sun, 28 Dec 2025 23:35:01 +0900 Subject: [PATCH 24/33] =?UTF-8?q?feat=20::=20=ED=88=AC=ED=91=9C=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EB=B0=8F=20ViewModel=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- feature/build.gradle.kts | 2 + .../feature/vote/navigation/VoteRoute.kt | 13 +- .../dms/android/feature/vote/ui/VoteScreen.kt | 117 +++++++++++++++- .../feature/vote/viewmodel/VoteViewModel.kt | 127 ++++++++++++++++++ 4 files changed, 253 insertions(+), 6 deletions(-) create mode 100644 feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt diff --git a/feature/build.gradle.kts b/feature/build.gradle.kts index 2f447ff0c..e486199b8 100644 --- a/feature/build.gradle.kts +++ b/feature/build.gradle.kts @@ -70,6 +70,8 @@ dependencies { implementation(project(ProjectPaths.Core.NOTIFICATION)) implementation(project(ProjectPaths.Core.JWT)) implementation(project(ProjectPaths.Core.ONBOARDING)) + implementation(project(ProjectPaths.Core.NETWORK)) + implementation(project(ProjectPaths.NETWORK)) implementation(project(ProjectPaths.DATA)) diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/navigation/VoteRoute.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/navigation/VoteRoute.kt index 8d54a47d4..39a03a5eb 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/navigation/VoteRoute.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/navigation/VoteRoute.kt @@ -1,9 +1,18 @@ package team.aliens.dms.android.feature.vote.navigation import androidx.compose.runtime.Composable +import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType import team.aliens.dms.android.feature.vote.ui.Vote @Composable -fun VoteRoute() { - Vote() +fun VoteRoute( + title: String, + onShowSnackBar: (DmsSnackBarType, String) -> Unit, + onNavigateBack: () -> Unit, +) { + Vote( + title = title, + onShowSnackBar = onShowSnackBar, + onNavigateBack = onNavigateBack, + ) } diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt index 48f0400e4..f6f9c420f 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt @@ -1,13 +1,122 @@ package team.aliens.dms.android.feature.vote.ui +import android.util.Log +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import team.aliens.dms.android.core.designsystem.DmsTheme +import team.aliens.dms.android.core.designsystem.appbar.DmsTopAppBar +import team.aliens.dms.android.core.designsystem.button.ButtonColor +import team.aliens.dms.android.core.designsystem.button.ButtonType +import team.aliens.dms.android.core.designsystem.button.DmsButton +import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType +import team.aliens.dms.android.feature.main.application.ui.component.VoteContent +import team.aliens.dms.android.feature.vote.ui.component.VoteItemContent +import team.aliens.dms.android.feature.vote.viewmodel.VoteSideEffect +import team.aliens.dms.android.feature.vote.viewmodel.VoteState +import team.aliens.dms.android.feature.vote.viewmodel.VoteViewModel +import java.util.UUID @Composable -internal fun Vote() { - VoteScreen() +internal fun Vote( + title: String, + onShowSnackBar: (DmsSnackBarType, String) -> Unit, + onNavigateBack: () -> Unit, +) { + val viewModel: VoteViewModel = hiltViewModel() + val state by viewModel.uiState.collectAsStateWithLifecycle() + + LaunchedEffect(Unit) { + viewModel.sideEffect.collect { + when (it) { + is VoteSideEffect.VoteSuccess -> onShowSnackBar( + DmsSnackBarType.SUCCESS, + "투표를 완료했어요!", + ) + + is VoteSideEffect.VoteConflict -> onShowSnackBar( + DmsSnackBarType.ERROR, + "이미 해당 투표에 참여했어요", + ) + + is VoteSideEffect.VoteFail -> onShowSnackBar( + DmsSnackBarType.ERROR, + "투표 중 오류가 발생했어요", + ) + + is VoteSideEffect.VoteLoadFail -> onShowSnackBar( + DmsSnackBarType.ERROR, + "정보를 불러오지 못했어요", + ) + } + } + } + + VoteScreen( + state = state, + title = title, + onNavigateBack = onNavigateBack, + onSelectItem = { selectedId -> viewModel.setSelectId(UUID.fromString(selectedId)) }, + submitVote = viewModel::postVote, + ) } @Composable -private fun VoteScreen() { - +private fun VoteScreen( + modifier: Modifier = Modifier, + state: VoteState, + title: String, + onNavigateBack: () -> Unit, + onSelectItem: (String) -> Unit, + submitVote: () -> Unit, +) { + Column( + modifier = modifier + .fillMaxSize() + .background(DmsTheme.colorScheme.background), + ) { + DmsTopAppBar( + title = "투표", + onBackPressed = onNavigateBack, + ) + VoteItemContent( + modifier = Modifier + .fillMaxSize() + .weight(1f), + vote = state.vote.voteType, + title = title, + startTime = state.vote.startTime, + endTime = state.vote.endTime, + options = state.options, + students = state.students, + modelStudents = state.modelStudent, + selectItem = state.selectId.toString(), + onSelect = onSelectItem, + ) + DmsButton( + modifier = Modifier + .fillMaxWidth() + .padding( + start = 24.dp, + end = 24.dp, + top = 80.dp, + bottom = 18.dp, + ), + text = "투표하기", + buttonType = ButtonType.Contained, + buttonColor = ButtonColor.Primary, + onClick = submitVote, + enabled = state.buttonEnabled, + isLoading = state.isLoading, + ) + } } diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt new file mode 100644 index 000000000..d7f569ba6 --- /dev/null +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt @@ -0,0 +1,127 @@ +package team.aliens.dms.android.feature.vote.viewmodel + +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import team.aliens.dms.android.core.network.exception.ConflictException +import team.aliens.dms.android.core.ui.viewmodel.BaseStateViewModel +import team.aliens.dms.android.data.student.model.Student +import team.aliens.dms.android.data.student.repository.StudentRepository +import team.aliens.dms.android.data.voting.model.AllVoteSearch +import team.aliens.dms.android.data.voting.model.Vote +import team.aliens.dms.android.data.voting.model.VotingItem +import team.aliens.dms.android.data.voting.repository.VotingRepository +import team.aliens.dms.android.shared.date.util.today +import java.util.UUID +import javax.inject.Inject + +@HiltViewModel +internal class VoteViewModel @Inject constructor( + private val voteRepository: VotingRepository, + private val studentRepository: StudentRepository, +) : BaseStateViewModel(VoteState()) { + + init { + fetchVotesByType() + } + + internal fun initState(vote: AllVoteSearch) { + setState { + it.copy( + vote = vote, + ) + } + } + + private fun fetchVotesByType() { + when (uiState.value.vote.voteType) { + Vote.OPTION_VOTE -> getVoteItems() + Vote.STUDENT_VOTE -> getStudents() + Vote.APPROVAL_VOTE -> getVoteItems() + Vote.MODEL_STUDENT_VOTE -> getCandidateModelStudents() + } + } + + private fun getStudents() { + viewModelScope.launch(Dispatchers.IO) { + studentRepository.fetchStudents() + .onSuccess { student -> setState { it.copy(students = student) } } + .onFailure { +// Logger.a(it) { it.message.toString() } + sendEffect(VoteSideEffect.VoteLoadFail) + } + } + } + + private fun getVoteItems() { + viewModelScope.launch(Dispatchers.IO) { + voteRepository.fetchCheckVotingItem(uiState.value.vote.id) + .onSuccess { voteItems -> setState { it.copy(options = voteItems) } } + .onFailure { +// Logger.a(it) { it.message.toString() } + sendEffect(VoteSideEffect.VoteLoadFail) + } + } + } + + private fun getCandidateModelStudents() { + viewModelScope.launch(Dispatchers.IO) { + voteRepository.fetchModelStudentCandidates(requestDate = today) + .onSuccess { modelStudents -> setState { it.copy(modelStudent = modelStudents) } } + .onFailure { +// Logger.a(it) { it.message.toString() } + sendEffect(VoteSideEffect.VoteLoadFail) + } + } + } + + internal fun setSelectId(selectId: UUID) { + setState { it.copy(selectId = selectId) } + setButtonEnabled() + } + + private fun setButtonEnabled() { + val isSelectIdNotNull = uiState.value.selectId != null + setState { uiState.value.copy(buttonEnabled = isSelectIdNotNull) } + } + + internal fun postVote() { + with(uiState.value) { + viewModelScope.launch(Dispatchers.IO) { + setState { uiState.value.copy(isLoading = true, buttonEnabled = false) } + voteRepository.fetchCreateVotingItem( + votingTopicId = uiState.value.vote.id, + selectedId = uiState.value.selectId ?: UUID.randomUUID(), + ).onSuccess { + setState { uiState.value.copy(buttonEnabled = false, isLoading = false) } + sendEffect(VoteSideEffect.VoteSuccess) + }.onFailure { + setState { uiState.value.copy(isLoading = false, buttonEnabled = true) } +// Logger.a(it) { it.message.toString() } + when (it) { + is ConflictException -> sendEffect(VoteSideEffect.VoteConflict) + else -> sendEffect(VoteSideEffect.VoteFail) + } + } + } + } + } +} + +internal data class VoteState( + val vote: AllVoteSearch = AllVoteSearch(), + val options: List = emptyList(), + val students: List = emptyList(), + val modelStudent: List = emptyList(), + val selectId: UUID? = null, + val buttonEnabled: Boolean = false, + val isLoading: Boolean = false, +) + +internal sealed interface VoteSideEffect { + data object VoteSuccess : VoteSideEffect + data object VoteConflict : VoteSideEffect + data object VoteFail : VoteSideEffect + data object VoteLoadFail : VoteSideEffect +} From 009e581094d02c53ac76fe5c7b9fbe1c61d0bf29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Sun, 28 Dec 2025 23:35:24 +0900 Subject: [PATCH 25/33] =?UTF-8?q?refactor=20::=20=EC=95=B1=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EC=9C=A0=ED=8B=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .../kotlin/team/aliens/dms/android/app/DmsApp.kt | 15 +++++++++++---- .../team/aliens/dms/android/core/ui/util/Date.kt | 2 +- .../viewmodel/RemainApplicationViewModel.kt | 4 ++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt b/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt index 4a1316f61..1f9bf0653 100644 --- a/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt +++ b/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt @@ -26,6 +26,7 @@ import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBar import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarVisuals import team.aliens.dms.android.core.ui.navigation.LocalResultStore import team.aliens.dms.android.core.ui.navigation.rememberResultStore +import team.aliens.dms.android.data.voting.model.AllVoteSearch import team.aliens.dms.android.feature.main.application.navigation.ApplicationRoute import team.aliens.dms.android.feature.main.home.navigation.HomeRoute import team.aliens.dms.android.feature.main.mypage.navigation.MyPageRoute @@ -51,7 +52,7 @@ data object MealScreenNav : NavKey data object ApplicationScreenNav : NavKey @Serializable -data object VoteScreenNav : NavKey +data class VoteScreenNav(val title: String) : NavKey @Serializable data object RemainScreenNav : NavKey @@ -157,15 +158,21 @@ fun DmsApp( onNavigateOutingApplication = {}, onNavigateVolunteerApplication = {}, onNavigateVote = { - backStack.add(VoteScreenNav) + backStack.add(VoteScreenNav(it.topicName)) }, onShowSnackBar = { snackBarType, message -> appState.showSnackBar(snackBarType, message) }, ) } - entry { - VoteRoute() + entry { voteNav -> + VoteRoute( + title = voteNav.title, + onNavigateBack = { backStack.remove(VoteScreenNav(voteNav.title)) }, + onShowSnackBar = { snackBarType, message -> + appState.showSnackBar(snackBarType, message) + } + ) } entry { RemainApplicationRoute( diff --git a/core/ui/src/dev/java/team/aliens/dms/android/core/ui/util/Date.kt b/core/ui/src/dev/java/team/aliens/dms/android/core/ui/util/Date.kt index 22bb7ff67..e0cf9f8f3 100644 --- a/core/ui/src/dev/java/team/aliens/dms/android/core/ui/util/Date.kt +++ b/core/ui/src/dev/java/team/aliens/dms/android/core/ui/util/Date.kt @@ -1,6 +1,6 @@ package team.aliens.dms.android.core.ui.util -import org.threeten.bp.LocalDateTime +import java.time.LocalDateTime fun LocalDateTime.toDateString(): String { return "${this.year}년 ${this.monthValue}월 ${this.dayOfMonth}일" diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt index 7b451c4ba..bd8eeaf16 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt @@ -3,8 +3,8 @@ package team.aliens.dms.android.feature.remain.viewmodel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch -import org.threeten.bp.LocalDateTime -import org.threeten.bp.LocalTime +import java.time.LocalDateTime +import java.time.LocalTime import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType import team.aliens.dms.android.core.ui.viewmodel.BaseStateViewModel import team.aliens.dms.android.data.remain.model.RemainsApplicationTime From 33f9f1568ebc196c87c6ede4e27d0344799184df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Mon, 29 Dec 2025 02:29:35 +0900 Subject: [PATCH 26/33] =?UTF-8?q?feat=20::=20vote=20screen=20localtime=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt | 9 ++++++--- .../dms/android/feature/vote/navigation/VoteRoute.kt | 5 +++++ .../aliens/dms/android/feature/vote/ui/VoteScreen.kt | 8 +++++--- .../dms/android/feature/vote/viewmodel/VoteViewModel.kt | 7 +++++-- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt b/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt index 1f9bf0653..361fcc6c1 100644 --- a/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt +++ b/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt @@ -35,6 +35,7 @@ import team.aliens.dms.android.feature.onboarding.navigation.OnboardingRoute import team.aliens.dms.android.feature.remain.navigation.RemainApplicationRoute import team.aliens.dms.android.feature.signin.navigation.SignInRoute import team.aliens.dms.android.feature.vote.navigation.VoteRoute +import java.time.LocalDateTime @Serializable data object OnboardingScreenNav : NavKey @@ -52,7 +53,7 @@ data object MealScreenNav : NavKey data object ApplicationScreenNav : NavKey @Serializable -data class VoteScreenNav(val title: String) : NavKey +data class VoteScreenNav(val title: String, val startTime: String, val endTime: String) : NavKey @Serializable data object RemainScreenNav : NavKey @@ -158,7 +159,7 @@ fun DmsApp( onNavigateOutingApplication = {}, onNavigateVolunteerApplication = {}, onNavigateVote = { - backStack.add(VoteScreenNav(it.topicName)) + backStack.add(VoteScreenNav(it.topicName, it.startTime.toString(), it.endTime.toString())) }, onShowSnackBar = { snackBarType, message -> appState.showSnackBar(snackBarType, message) @@ -168,7 +169,9 @@ fun DmsApp( entry { voteNav -> VoteRoute( title = voteNav.title, - onNavigateBack = { backStack.remove(VoteScreenNav(voteNav.title)) }, + startTime = voteNav.startTime, + endTime = voteNav.endTime, + onNavigateBack = { backStack.remove(VoteScreenNav(voteNav.title, voteNav.startTime, voteNav.endTime)) }, onShowSnackBar = { snackBarType, message -> appState.showSnackBar(snackBarType, message) } diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/navigation/VoteRoute.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/navigation/VoteRoute.kt index 39a03a5eb..9918b445c 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/navigation/VoteRoute.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/navigation/VoteRoute.kt @@ -3,15 +3,20 @@ package team.aliens.dms.android.feature.vote.navigation import androidx.compose.runtime.Composable import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType import team.aliens.dms.android.feature.vote.ui.Vote +import java.time.LocalDateTime @Composable fun VoteRoute( title: String, + startTime: String, + endTime: String, onShowSnackBar: (DmsSnackBarType, String) -> Unit, onNavigateBack: () -> Unit, ) { Vote( title = title, + startTime = startTime, + endTime = endTime, onShowSnackBar = onShowSnackBar, onNavigateBack = onNavigateBack, ) diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt index f6f9c420f..f69c1534c 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt @@ -24,11 +24,14 @@ import team.aliens.dms.android.feature.vote.ui.component.VoteItemContent import team.aliens.dms.android.feature.vote.viewmodel.VoteSideEffect import team.aliens.dms.android.feature.vote.viewmodel.VoteState import team.aliens.dms.android.feature.vote.viewmodel.VoteViewModel +import java.time.LocalDateTime import java.util.UUID @Composable internal fun Vote( title: String, + startTime: String, + endTime: String, onShowSnackBar: (DmsSnackBarType, String) -> Unit, onNavigateBack: () -> Unit, ) { @@ -36,6 +39,7 @@ internal fun Vote( val state by viewModel.uiState.collectAsStateWithLifecycle() LaunchedEffect(Unit) { + viewModel.initState(title, startTime, endTime) viewModel.sideEffect.collect { when (it) { is VoteSideEffect.VoteSuccess -> onShowSnackBar( @@ -63,7 +67,6 @@ internal fun Vote( VoteScreen( state = state, - title = title, onNavigateBack = onNavigateBack, onSelectItem = { selectedId -> viewModel.setSelectId(UUID.fromString(selectedId)) }, submitVote = viewModel::postVote, @@ -74,7 +77,6 @@ internal fun Vote( private fun VoteScreen( modifier: Modifier = Modifier, state: VoteState, - title: String, onNavigateBack: () -> Unit, onSelectItem: (String) -> Unit, submitVote: () -> Unit, @@ -93,7 +95,7 @@ private fun VoteScreen( .fillMaxSize() .weight(1f), vote = state.vote.voteType, - title = title, + title = state.vote.topicName, startTime = state.vote.startTime, endTime = state.vote.endTime, options = state.options, diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt index d7f569ba6..b95240c89 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt @@ -12,7 +12,10 @@ import team.aliens.dms.android.data.voting.model.AllVoteSearch import team.aliens.dms.android.data.voting.model.Vote import team.aliens.dms.android.data.voting.model.VotingItem import team.aliens.dms.android.data.voting.repository.VotingRepository +import team.aliens.dms.android.shared.date.toLocalDateTime import team.aliens.dms.android.shared.date.util.today +import java.time.LocalDate +import java.time.LocalDateTime import java.util.UUID import javax.inject.Inject @@ -26,10 +29,10 @@ internal class VoteViewModel @Inject constructor( fetchVotesByType() } - internal fun initState(vote: AllVoteSearch) { + internal fun initState(title: String, startTime: String, endTime: String) { setState { it.copy( - vote = vote, + vote = it.vote.copy(topicName = title, startTime = startTime.toLocalDateTime(), endTime = endTime.toLocalDateTime()), ) } } From 5ce938a2a554b80d4889dcba59ac9f853717593c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Mon, 29 Dec 2025 14:00:48 +0900 Subject: [PATCH 27/33] =?UTF-8?q?feat=20::=20DmsLayeredButton=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/designsystem/button/Button.kt | 40 +++++++++++++++++++ .../application/ui/component/VoteContent.kt | 4 +- .../dms/android/feature/vote/ui/VoteScreen.kt | 24 +++++------ 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/button/Button.kt b/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/button/Button.kt index b2cddb476..54c7e93a5 100644 --- a/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/button/Button.kt +++ b/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/button/Button.kt @@ -4,9 +4,15 @@ import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -21,6 +27,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import team.aliens.dms.android.core.designsystem.DmsTheme import team.aliens.dms.android.core.designsystem.bodyM @@ -349,3 +356,36 @@ fun DmsButton( } } } +@Composable +fun DmsLayeredButton( + modifier: Modifier = Modifier, + text: String, + buttonType: ButtonType, + buttonColor: ButtonColor, + enabled: Boolean = true, + shape: RoundedCornerShape, + backgroundColor: Color = DmsTheme.colorScheme.surfaceVariant, + layerOffset: Dp = 24.dp, + isLoading: Boolean, + onClick: () -> Unit, +) { + Box( + modifier = modifier + .background(color = Color(0xFFFFFFFF), shape = shape) + .windowInsetsPadding(WindowInsets.navigationBars) + .padding(layerOffset), + ) { + DmsButton( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center), + text = text, + buttonType = buttonType, + buttonColor = buttonColor, + enabled = enabled, + // 3. 버튼에도 별도의 shape를 전달합니다. + isLoading = isLoading, + onClick = onClick, + ) + } +} diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt index 381763e23..a10c1b50a 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/main/application/ui/component/VoteContent.kt @@ -10,8 +10,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import team.aliens.dms.android.core.designsystem.R import team.aliens.dms.android.core.designsystem.card.DmsApplicationCard +import team.aliens.dms.android.core.ui.util.toDateString import team.aliens.dms.android.data.voting.model.AllVoteSearch import team.aliens.dms.android.data.voting.model.Vote +import team.aliens.dms.android.shared.date.toDate @Composable internal fun VoteContent( @@ -38,7 +40,7 @@ internal fun VoteContent( DmsApplicationCard( title = vote.topicName, appliedTitle = null, - period = "${vote.startTime.toLocalTime()} ~ ${vote.endTime.toLocalTime()}", + period = "${vote.startTime.toDateString()} ~ ${vote.endTime.toDateString()}", description = vote.description, iconRes = icon, onClick = { onNavigateVote(vote) }, diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt index f69c1534c..a8d9e2625 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/ui/VoteScreen.kt @@ -1,11 +1,10 @@ package team.aliens.dms.android.feature.vote.ui -import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -17,14 +16,12 @@ import team.aliens.dms.android.core.designsystem.DmsTheme import team.aliens.dms.android.core.designsystem.appbar.DmsTopAppBar import team.aliens.dms.android.core.designsystem.button.ButtonColor import team.aliens.dms.android.core.designsystem.button.ButtonType -import team.aliens.dms.android.core.designsystem.button.DmsButton +import team.aliens.dms.android.core.designsystem.button.DmsLayeredButton import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType -import team.aliens.dms.android.feature.main.application.ui.component.VoteContent import team.aliens.dms.android.feature.vote.ui.component.VoteItemContent import team.aliens.dms.android.feature.vote.viewmodel.VoteSideEffect import team.aliens.dms.android.feature.vote.viewmodel.VoteState import team.aliens.dms.android.feature.vote.viewmodel.VoteViewModel -import java.time.LocalDateTime import java.util.UUID @Composable @@ -87,7 +84,6 @@ private fun VoteScreen( .background(DmsTheme.colorScheme.background), ) { DmsTopAppBar( - title = "투표", onBackPressed = onNavigateBack, ) VoteItemContent( @@ -104,18 +100,18 @@ private fun VoteScreen( selectItem = state.selectId.toString(), onSelect = onSelectItem, ) - DmsButton( + DmsLayeredButton( modifier = Modifier - .fillMaxWidth() - .padding( - start = 24.dp, - end = 24.dp, - top = 80.dp, - bottom = 18.dp, - ), + .fillMaxWidth(), text = "투표하기", buttonType = ButtonType.Contained, buttonColor = ButtonColor.Primary, + shape = RoundedCornerShape( + topStart = 32.dp, + topEnd = 32.dp, + bottomStart = 0.dp, + bottomEnd = 0.dp, + ), onClick = submitVote, enabled = state.buttonEnabled, isLoading = state.isLoading, From 55af2a70ea5574ef23ecd9724f87bce1ee78d00e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Mon, 29 Dec 2025 17:06:00 +0900 Subject: [PATCH 28/33] =?UTF-8?q?refactor=20::=20vote=20api,=20dataSource?= =?UTF-8?q?=20result=20=EA=B0=9D=EC=B2=B4=EB=A1=9C=20=EB=9E=98=ED=95=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../voting/repository/VotingRepositoryImpl.kt | 3 ++- .../dms/android/feature/vote/viewmodel/VoteViewModel.kt | 6 ------ .../voting/apiservice/VotingApiService.kt | 2 +- .../voting/datasource/NetworkVotingDataSource.kt | 2 +- .../voting/datasource/NetworkVotingDataSourceImpl.kt | 3 ++- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepositoryImpl.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepositoryImpl.kt index 7106995d0..8eea55482 100644 --- a/data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepositoryImpl.kt +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/voting/repository/VotingRepositoryImpl.kt @@ -1,5 +1,6 @@ package team.aliens.dms.android.data.voting.repository +import team.aliens.dms.android.core.network.exception.NotFoundException import team.aliens.dms.android.data.student.model.Student import java.time.LocalDate import team.aliens.dms.android.data.voting.mapper.toModel @@ -21,7 +22,7 @@ internal class VotingRepositoryImpl @Inject constructor( } override suspend fun fetchCreateVotingItem(votingTopicId: UUID, selectedId: UUID): Result = runCatching { - networkVotingDataSource.fetchCreateVotingItem(votingTopicId, selectedId) + networkVotingDataSource.fetchCreateVotingItem(votingTopicId, selectedId).getOrThrow() } override suspend fun fetchDeleteVotingItem(voteId: UUID): Result = runCatching { diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt index b95240c89..439dc53ce 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/vote/viewmodel/VoteViewModel.kt @@ -14,8 +14,6 @@ import team.aliens.dms.android.data.voting.model.VotingItem import team.aliens.dms.android.data.voting.repository.VotingRepository import team.aliens.dms.android.shared.date.toLocalDateTime import team.aliens.dms.android.shared.date.util.today -import java.time.LocalDate -import java.time.LocalDateTime import java.util.UUID import javax.inject.Inject @@ -51,7 +49,6 @@ internal class VoteViewModel @Inject constructor( studentRepository.fetchStudents() .onSuccess { student -> setState { it.copy(students = student) } } .onFailure { -// Logger.a(it) { it.message.toString() } sendEffect(VoteSideEffect.VoteLoadFail) } } @@ -62,7 +59,6 @@ internal class VoteViewModel @Inject constructor( voteRepository.fetchCheckVotingItem(uiState.value.vote.id) .onSuccess { voteItems -> setState { it.copy(options = voteItems) } } .onFailure { -// Logger.a(it) { it.message.toString() } sendEffect(VoteSideEffect.VoteLoadFail) } } @@ -73,7 +69,6 @@ internal class VoteViewModel @Inject constructor( voteRepository.fetchModelStudentCandidates(requestDate = today) .onSuccess { modelStudents -> setState { it.copy(modelStudent = modelStudents) } } .onFailure { -// Logger.a(it) { it.message.toString() } sendEffect(VoteSideEffect.VoteLoadFail) } } @@ -101,7 +96,6 @@ internal class VoteViewModel @Inject constructor( sendEffect(VoteSideEffect.VoteSuccess) }.onFailure { setState { uiState.value.copy(isLoading = false, buttonEnabled = true) } -// Logger.a(it) { it.message.toString() } when (it) { is ConflictException -> sendEffect(VoteSideEffect.VoteConflict) else -> sendEffect(VoteSideEffect.VoteFail) diff --git a/network/src/dev/kotlin/team.aliens.dms.android.network/voting/apiservice/VotingApiService.kt b/network/src/dev/kotlin/team.aliens.dms.android.network/voting/apiservice/VotingApiService.kt index eac7cc4a9..eeea69044 100644 --- a/network/src/dev/kotlin/team.aliens.dms.android.network/voting/apiservice/VotingApiService.kt +++ b/network/src/dev/kotlin/team.aliens.dms.android.network/voting/apiservice/VotingApiService.kt @@ -30,7 +30,7 @@ internal interface VotingApiService { suspend fun fetchCreateVotingItem( @Path("voting-topic-id") votingTopicId: UUID, @Query("selected-id") selectedId: UUID, - ): Response? + ): Result? @DELETE("/votes/student/{vote_id}") @RequiresAccessToken diff --git a/network/src/dev/kotlin/team.aliens.dms.android.network/voting/datasource/NetworkVotingDataSource.kt b/network/src/dev/kotlin/team.aliens.dms.android.network/voting/datasource/NetworkVotingDataSource.kt index 3390a7c7a..9b2150331 100644 --- a/network/src/dev/kotlin/team.aliens.dms.android.network/voting/datasource/NetworkVotingDataSource.kt +++ b/network/src/dev/kotlin/team.aliens.dms.android.network/voting/datasource/NetworkVotingDataSource.kt @@ -15,7 +15,7 @@ abstract class NetworkVotingDataSource { abstract suspend fun fetchCreateVotingItem( votingTopicId: UUID, selectedId: UUID, - ) + ): Result abstract suspend fun fetchDeleteVotingItem(voteId: UUID) diff --git a/network/src/dev/kotlin/team.aliens.dms.android.network/voting/datasource/NetworkVotingDataSourceImpl.kt b/network/src/dev/kotlin/team.aliens.dms.android.network/voting/datasource/NetworkVotingDataSourceImpl.kt index 567861f2a..74e71f68f 100644 --- a/network/src/dev/kotlin/team.aliens.dms.android.network/voting/datasource/NetworkVotingDataSourceImpl.kt +++ b/network/src/dev/kotlin/team.aliens.dms.android.network/voting/datasource/NetworkVotingDataSourceImpl.kt @@ -18,8 +18,9 @@ internal class NetworkVotingDataSourceImpl @Inject constructor( override suspend fun fetchCheckVotingItem(votingTopicId: UUID): FetchCheckVotingItemResponse = handleNetworkRequest { votingApiService.fetchCheckVotingItem(votingTopicId) } - override suspend fun fetchCreateVotingItem(votingTopicId: UUID, selectedId: UUID): Unit = + override suspend fun fetchCreateVotingItem(votingTopicId: UUID, selectedId: UUID): Result = runCatching { handleNetworkRequest { votingApiService.fetchCreateVotingItem(votingTopicId, selectedId) } + } override suspend fun fetchDeleteVotingItem(voteId: UUID): Unit = handleNetworkRequest { votingApiService.fetchDeleteVotingItem(voteId) } From 3b4680792949c20f965c60ea3e2a86190313fd73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Mon, 29 Dec 2025 21:57:47 +0900 Subject: [PATCH 29/33] =?UTF-8?q?refactor=20::=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EB=AA=A8=EB=8D=B8=20=ED=83=80=EC=9E=85=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../team/aliens/dms/android/app/DmsApp.kt | 3 -- .../remain/model/RemainsOption.kt | 2 +- .../remain/ui/RemainApplicationScreen.kt | 4 +- .../viewmodel/RemainApplicationViewModel.kt | 49 +++++++++---------- 4 files changed, 28 insertions(+), 30 deletions(-) diff --git a/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt b/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt index 361fcc6c1..b375b0ff5 100644 --- a/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt +++ b/app/src/dev/kotlin/team/aliens/dms/android/app/DmsApp.kt @@ -1,6 +1,5 @@ package team.aliens.dms.android.app -import android.util.Log import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding @@ -26,7 +25,6 @@ import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBar import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarVisuals import team.aliens.dms.android.core.ui.navigation.LocalResultStore import team.aliens.dms.android.core.ui.navigation.rememberResultStore -import team.aliens.dms.android.data.voting.model.AllVoteSearch import team.aliens.dms.android.feature.main.application.navigation.ApplicationRoute import team.aliens.dms.android.feature.main.home.navigation.HomeRoute import team.aliens.dms.android.feature.main.mypage.navigation.MyPageRoute @@ -35,7 +33,6 @@ import team.aliens.dms.android.feature.onboarding.navigation.OnboardingRoute import team.aliens.dms.android.feature.remain.navigation.RemainApplicationRoute import team.aliens.dms.android.feature.signin.navigation.SignInRoute import team.aliens.dms.android.feature.vote.navigation.VoteRoute -import java.time.LocalDateTime @Serializable data object OnboardingScreenNav : NavKey diff --git a/data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/RemainsOption.kt b/data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/RemainsOption.kt index b18f59385..3fed35775 100644 --- a/data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/RemainsOption.kt +++ b/data/src/dev/kotlin/team.aliens.dms.android.data/remain/model/RemainsOption.kt @@ -3,7 +3,7 @@ package team.aliens.dms.android.data.remain.model import java.util.UUID data class RemainsOption( - val id: UUID, + val id: UUID?, val title: String, val description: String, val applied: Boolean, diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt index af672137d..02d9798a6 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt @@ -1,6 +1,8 @@ package team.aliens.dms.android.feature.remain.ui +import android.os.Build import android.util.Log +import androidx.annotation.RequiresApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -55,7 +57,7 @@ internal fun RemainApplication( private fun RemainApplicationScreen( onNavigateBack: (String?) -> Unit, state: RemainApplicationState, - setSelectRemainsOption: (UUID) -> Unit, + setSelectRemainsOption: (UUID?) -> Unit, changeRemainsOption: () -> Unit, ) { Column( diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt index bd8eeaf16..0aa588939 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt @@ -1,5 +1,7 @@ package team.aliens.dms.android.feature.remain.viewmodel +import android.os.Build +import androidx.annotation.RequiresApi import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch @@ -18,6 +20,7 @@ import kotlin.collections.map class RemainApplicationViewModel @Inject constructor( val remainsRepository: RemainsRepository ): BaseStateViewModel(RemainApplicationState()) { + init { getRemainsOptions() getRemainsApplicationTime() @@ -46,7 +49,7 @@ class RemainApplicationViewModel @Inject constructor( } } - internal fun setSelectRemainsOption(remainsOptionId: UUID) { + internal fun setSelectRemainsOption(remainsOptionId: UUID?) { setState { it.copy(selectRemainsOptionId = remainsOptionId) } } @@ -58,29 +61,25 @@ class RemainApplicationViewModel @Inject constructor( onShowSnackBar(DmsSnackBarType.ERROR, "잔류 신청 시간이 아닙니다") return@launch } - val remainOptionId = uiState.value.selectRemainsOptionId - remainsRepository.updateRemainsOption(optionId = remainOptionId ?: UUID.randomUUID()) - .onSuccess { - val remainsOptions = uiState.value.remainsOptions.map { remainsOption -> - if (remainsOption.id == uiState.value.selectRemainsOptionId) { - setState { it.copy(selectedRemainTitle = remainsOption.title) } - remainsOption.copy(applied = true) - } else { - remainsOption.copy(applied = false) + uiState.value.selectRemainsOptionId?.let { optionId -> + remainsRepository.updateRemainsOption(optionId = optionId) + .onSuccess { + val remainsOptions = uiState.value.remainsOptions.map { remainsOption -> + remainsOption.copy(applied = remainsOption.id == uiState.value.selectRemainsOptionId) } + val appliedOption = remainsOptions.find { it.applied } + setState { + it.copy( + remainsOptions = remainsOptions, + selectedRemainTitle = appliedOption?.title + ) + } + onShowSnackBar(DmsSnackBarType.SUCCESS, "잔류 신청이 완료되었습니다") + }.onFailure { + onShowSnackBar(DmsSnackBarType.ERROR, "잔류 신청에 실패했습니다") } - val appliedOption = remainsOptions.find { it.applied } - setState { - it.copy( - remainsOptions = remainsOptions, - selectedRemainTitle = appliedOption?.title - ) - } - onShowSnackBar(DmsSnackBarType.SUCCESS, "잔류 신청이 완료되었습니다") - }.onFailure { - onShowSnackBar(DmsSnackBarType.ERROR, "잔류 신청에 실패했습니다") } - } + } } private fun isWithinApplicationTime(): Boolean { @@ -104,10 +103,10 @@ class RemainApplicationViewModel @Inject constructor( currentTime >= startTime && currentTime <= endTime } - return when { - currentDayOfWeek == startDayValue -> currentTime >= startTime - currentDayOfWeek == endDayValue -> currentTime <= endTime - currentDayOfWeek in (startDayValue + 1).. true + return when (currentDayOfWeek) { + startDayValue -> currentTime >= startTime + endDayValue -> currentTime <= endTime + in (startDayValue + 1).. true else -> false } } From cff3a34ee1b6bda4a0b4a234af1350016fed52a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Mon, 29 Dec 2025 21:58:00 +0900 Subject: [PATCH 30/33] =?UTF-8?q?refactor=20::=20minSdk=2026=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 6 +----- buildSrc/src/main/kotlin/ProjectProperties.kt | 2 +- gradle/libs.versions.toml | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 541ebab28..83af6db51 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -35,10 +35,6 @@ android { applicationIdSuffix = ".dev" versionNameSuffix = "-dev" buildConfigField("String", "ENVIRONMENT", "\"dev\"") - - compileOptions { - isCoreLibraryDesugaringEnabled = true - } } create("prod") { @@ -144,7 +140,7 @@ dependencies { implementation(libs.material) implementation(libs.javax.inject) - coreLibraryDesugaring(libs.desugar.jdk.libs) +// coreLibraryDesugaring(libs.desugar.jdk.libs) implementation(libs.okhttp) implementation(libs.okhttp.interceptor.logging) diff --git a/buildSrc/src/main/kotlin/ProjectProperties.kt b/buildSrc/src/main/kotlin/ProjectProperties.kt index daef21eb6..22e30eb61 100644 --- a/buildSrc/src/main/kotlin/ProjectProperties.kt +++ b/buildSrc/src/main/kotlin/ProjectProperties.kt @@ -1,6 +1,6 @@ object ProjectProperties { const val COMPILE_SDK = 36 - const val MIN_SDK = 24 + const val MIN_SDK = 26 const val TARGET_SDK = 36 const val VERSION_CODE = 28 const val VERSION_NAME = "1.5.3" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5a919dfb2..c4f2577dd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -39,7 +39,7 @@ navigation3 = "1.0.0" hiltNavigation = "1.1.0" coil = "2.4.0" compileSdk = "36" -minSdk = "24" +minSdk = "26" targetSdk = "34" desugarJdkLibs = "2.0.3" java = "17" From d79859dbbfc553ca55067400b9fbc50ab1e55d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Mon, 29 Dec 2025 22:13:42 +0900 Subject: [PATCH 31/33] =?UTF-8?q?refactor=20::=20JwtProviderImpl=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aliens/dms/android/core/jwt/JwtProviderImpl.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/jwt/src/main/java/team/aliens/dms/android/core/jwt/JwtProviderImpl.kt b/core/jwt/src/main/java/team/aliens/dms/android/core/jwt/JwtProviderImpl.kt index 29f19045e..4248fc837 100644 --- a/core/jwt/src/main/java/team/aliens/dms/android/core/jwt/JwtProviderImpl.kt +++ b/core/jwt/src/main/java/team/aliens/dms/android/core/jwt/JwtProviderImpl.kt @@ -113,8 +113,14 @@ internal class JwtProviderImpl @Inject constructor( }.onSuccess { tokens -> this@JwtProviderImpl.updateTokens(tokens = tokens) }.onFailure { exception -> - if (exception is retrofit2.HttpException && exception.code() == 401) { - this@JwtProviderImpl.clearCaches() + when { + exception is retrofit2.HttpException && exception.code() == 401 -> { + this@JwtProviderImpl.clearCaches() + } + exception is CannotUseRefreshTokenException -> { + this@JwtProviderImpl.clearCaches() + } + else -> {} } } this@JwtProviderImpl.refreshTokenAbility() From f08f3d1a1d4283b22d31047b3c434d50628aded5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Mon, 29 Dec 2025 22:13:51 +0900 Subject: [PATCH 32/33] =?UTF-8?q?refactor=20::=20=EC=BB=AC=EB=9F=AC=20?= =?UTF-8?q?=ED=95=98=EB=93=9C=20=EC=BD=94=EB=94=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aliens/dms/android/core/designsystem/button/Button.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/button/Button.kt b/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/button/Button.kt index 54c7e93a5..5c53513a7 100644 --- a/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/button/Button.kt +++ b/core/design-system/src/dev/java/team/aliens/dms/android/core/designsystem/button/Button.kt @@ -315,8 +315,6 @@ fun DmsButton( PaddingValues(horizontal = 20.dp, vertical = 16.dp) } - // val buttonShape = if (buttonType == ButtonType.Rounded) RoundedCornerShape(24.dp) else shape - BasicButton( modifier = modifier, backgroundColor = backgroundColor, @@ -371,7 +369,7 @@ fun DmsLayeredButton( ) { Box( modifier = modifier - .background(color = Color(0xFFFFFFFF), shape = shape) + .background(color = Color.White, shape = shape) .windowInsetsPadding(WindowInsets.navigationBars) .padding(layerOffset), ) { @@ -383,7 +381,6 @@ fun DmsLayeredButton( buttonType = buttonType, buttonColor = buttonColor, enabled = enabled, - // 3. 버튼에도 별도의 shape를 전달합니다. isLoading = isLoading, onClick = onClick, ) From 8520fe7df95c4eebc800a68575f805f021fd33d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=84=AD?= Date: Mon, 29 Dec 2025 22:53:47 +0900 Subject: [PATCH 33/33] chore :: unused import remove --- .../android/feature/remain/ui/RemainApplicationScreen.kt | 3 --- .../feature/remain/viewmodel/RemainApplicationViewModel.kt | 7 ++----- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt index 02d9798a6..5403e3c4a 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/ui/RemainApplicationScreen.kt @@ -1,8 +1,5 @@ package team.aliens.dms.android.feature.remain.ui -import android.os.Build -import android.util.Log -import androidx.annotation.RequiresApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column diff --git a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt index 0aa588939..b664dab16 100644 --- a/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt +++ b/feature/src/dev/kotlin/team/aliens/dms/android/feature/remain/viewmodel/RemainApplicationViewModel.kt @@ -1,20 +1,17 @@ package team.aliens.dms.android.feature.remain.viewmodel -import android.os.Build -import androidx.annotation.RequiresApi import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch -import java.time.LocalDateTime -import java.time.LocalTime import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType import team.aliens.dms.android.core.ui.viewmodel.BaseStateViewModel import team.aliens.dms.android.data.remain.model.RemainsApplicationTime import team.aliens.dms.android.data.remain.model.RemainsOption import team.aliens.dms.android.data.remain.repository.RemainsRepository +import java.time.LocalDateTime +import java.time.LocalTime import java.util.UUID import javax.inject.Inject -import kotlin.collections.map @HiltViewModel class RemainApplicationViewModel @Inject constructor(