diff --git a/app/src/main/java/com/threegap/bitnagil/MainNavHost.kt b/app/src/main/java/com/threegap/bitnagil/MainNavHost.kt index 4810d5b1..39fd583d 100644 --- a/app/src/main/java/com/threegap/bitnagil/MainNavHost.kt +++ b/app/src/main/java/com/threegap/bitnagil/MainNavHost.kt @@ -14,6 +14,7 @@ import com.threegap.bitnagil.presentation.onboarding.OnBoardingScreenContainer import com.threegap.bitnagil.presentation.onboarding.OnBoardingViewModel import com.threegap.bitnagil.presentation.onboarding.model.navarg.OnBoardingScreenArg import com.threegap.bitnagil.presentation.report.ReportScreenContainer +import com.threegap.bitnagil.presentation.reporthistory.ReportHistoryScreenContainer import com.threegap.bitnagil.presentation.routinelist.RoutineListScreenContainer import com.threegap.bitnagil.presentation.setting.SettingScreenContainer import com.threegap.bitnagil.presentation.splash.SplashScreenContainer @@ -140,6 +141,11 @@ fun MainNavHost( launchSingleTop = true } }, + navigateToReportHistory = { + navigator.navController.navigate(Route.ReportHistory) { + launchSingleTop = true + } + }, ) } @@ -305,5 +311,17 @@ fun MainNavHost( }, ) } + + composable { + ReportHistoryScreenContainer( + navigateToBack = { + if (navigator.navController.previousBackStackEntry != null) { + navigator.navController.popBackStack() + } + }, + navigateToReportDetail = { + }, + ) + } } } diff --git a/app/src/main/java/com/threegap/bitnagil/Route.kt b/app/src/main/java/com/threegap/bitnagil/Route.kt index 93e1cd0e..5a9ad70e 100644 --- a/app/src/main/java/com/threegap/bitnagil/Route.kt +++ b/app/src/main/java/com/threegap/bitnagil/Route.kt @@ -51,4 +51,7 @@ sealed interface Route { @Serializable data object Report : Route + + @Serializable + data object ReportHistory : Route } diff --git a/app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavHost.kt b/app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavHost.kt index 249a9aef..07a01787 100644 --- a/app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavHost.kt +++ b/app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavHost.kt @@ -44,6 +44,7 @@ fun HomeNavHost( navigateToEmotion: () -> Unit, navigateToRoutineList: (String) -> Unit, navigateToReport: () -> Unit, + navigateToReportHistory: () -> Unit, ) { val navigator = rememberHomeNavigator() var showFloatingOverlay by remember { mutableStateOf(false) } @@ -89,6 +90,7 @@ fun HomeNavHost( navigateToOnBoarding = navigateToOnBoarding, navigateToNotice = navigateToNotice, navigateToQnA = navigateToQnA, + navigateToReportHistory = navigateToReportHistory, ) } } diff --git a/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/color/BitnagilColors.kt b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/color/BitnagilColors.kt index c295efca..3176a19a 100644 --- a/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/color/BitnagilColors.kt +++ b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/color/BitnagilColors.kt @@ -12,8 +12,10 @@ data class BitnagilColors( val error: Color = Error, val error10: Color = Error10, val skyBlue10: Color = SkyBlue10, + val blue300: Color = Blue300, val purple10: Color = Purple10, val green10: Color = Green10, + val green300: Color = Green300, val pink10: Color = Pink10, val yellow10: Color = Yellow10, val progressBarGradientStartColor: Color = ProgressBarGradientStartColor, diff --git a/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/color/Color.kt b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/color/Color.kt index 851d91b3..23112455 100644 --- a/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/color/Color.kt +++ b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/color/Color.kt @@ -8,8 +8,10 @@ val Kakao = Color(0xFFFEE500) val Error = Color(0xFFFF6868) val Error10 = Color(0xFFFF5858) val SkyBlue10 = Color(0xFFDBF1FF) +val Blue300 = Color(0xFF0093F5) val Purple10 = Color(0xFFE6E2FF) val Green10 = Color(0xFFE6F5C6) +val Green300 = Color(0xFF3FAF00) val Pink10 = Color(0xFFFEE3E9) val Yellow10 = Color(0xFFFFF5C7) val ProgressBarGradientStartColor = Color(0xFFA9CFFF) diff --git a/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilChip.kt b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilChip.kt new file mode 100644 index 00000000..4da43069 --- /dev/null +++ b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilChip.kt @@ -0,0 +1,60 @@ +package com.threegap.bitnagil.designsystem.component.atom + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.threegap.bitnagil.designsystem.BitnagilTheme +import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple + +@Composable +fun BitnagilChip( + title: String, + isSelected: Boolean, + onCategorySelected: () -> Unit, + modifier: Modifier = Modifier, +) { + Box( + contentAlignment = Alignment.Center, + modifier = modifier + .background( + color = if (!isSelected) BitnagilTheme.colors.white else BitnagilTheme.colors.coolGray10, + shape = RoundedCornerShape(20.dp), + ) + .height(36.dp) + .clickableWithoutRipple(onClick = onCategorySelected) + .padding( + vertical = 6.dp, + horizontal = 14.dp, + ), + ) { + Text( + text = title, + color = if (!isSelected) BitnagilTheme.colors.coolGray60 else BitnagilTheme.colors.white, + style = if (!isSelected) BitnagilTheme.typography.caption1Medium else BitnagilTheme.typography.caption1SemiBold, + ) + } +} + +@Preview +@Composable +private fun RecommendCategoryChipStatesPreview() { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + BitnagilChip( + title = "맞춤 추천", + isSelected = true, + onCategorySelected = {}, + ) + } +} diff --git a/data/src/main/java/com/threegap/bitnagil/data/report/datasource/ReportDataSource.kt b/data/src/main/java/com/threegap/bitnagil/data/report/datasource/ReportDataSource.kt index 2c2170f7..e672d191 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/report/datasource/ReportDataSource.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/report/datasource/ReportDataSource.kt @@ -1,7 +1,9 @@ package com.threegap.bitnagil.data.report.datasource import com.threegap.bitnagil.data.report.model.request.ReportRequestDto +import com.threegap.bitnagil.data.report.model.response.ReportHistoriesPerDateDto interface ReportDataSource { suspend fun submitReport(reportRequestDto: ReportRequestDto): Result + suspend fun getReports(): Result } diff --git a/data/src/main/java/com/threegap/bitnagil/data/report/datasourceImpl/ReportDataSourceImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/report/datasourceImpl/ReportDataSourceImpl.kt index 14305d47..c980beef 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/report/datasourceImpl/ReportDataSourceImpl.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/report/datasourceImpl/ReportDataSourceImpl.kt @@ -3,6 +3,7 @@ package com.threegap.bitnagil.data.report.datasourceImpl import com.threegap.bitnagil.data.common.safeApiCall import com.threegap.bitnagil.data.report.datasource.ReportDataSource import com.threegap.bitnagil.data.report.model.request.ReportRequestDto +import com.threegap.bitnagil.data.report.model.response.ReportHistoriesPerDateDto import com.threegap.bitnagil.data.report.service.ReportService import javax.inject.Inject @@ -12,4 +13,8 @@ class ReportDataSourceImpl @Inject constructor( override suspend fun submitReport(reportRequestDto: ReportRequestDto): Result { return safeApiCall { reportService.submitReport(reportRequestDto) } } + + override suspend fun getReports(): Result { + return safeApiCall { reportService.getReports() } + } } diff --git a/data/src/main/java/com/threegap/bitnagil/data/report/model/response/ReportHistoriesPerDateDto.kt b/data/src/main/java/com/threegap/bitnagil/data/report/model/response/ReportHistoriesPerDateDto.kt new file mode 100644 index 00000000..27577b76 --- /dev/null +++ b/data/src/main/java/com/threegap/bitnagil/data/report/model/response/ReportHistoriesPerDateDto.kt @@ -0,0 +1,19 @@ +package com.threegap.bitnagil.data.report.model.response + +import com.threegap.bitnagil.domain.report.model.ReportItem +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import java.time.LocalDate + +@Serializable +data class ReportHistoriesPerDateDto( + @SerialName("reportInfos") + val reportInfos: Map>, +) + +fun ReportHistoriesPerDateDto.toDomainMap(): Map> = + this.reportInfos + .mapKeys { LocalDate.parse(it.key) } + .mapValues { entry -> + entry.value.map { it.toDomain() } + } diff --git a/data/src/main/java/com/threegap/bitnagil/data/report/model/response/ReportItemDto.kt b/data/src/main/java/com/threegap/bitnagil/data/report/model/response/ReportItemDto.kt new file mode 100644 index 00000000..1b26ff2a --- /dev/null +++ b/data/src/main/java/com/threegap/bitnagil/data/report/model/response/ReportItemDto.kt @@ -0,0 +1,33 @@ +package com.threegap.bitnagil.data.report.model.response + +import com.threegap.bitnagil.domain.report.model.ReportCategory +import com.threegap.bitnagil.domain.report.model.ReportItem +import com.threegap.bitnagil.domain.report.model.ReportStatus +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ReportItemDto( + @SerialName("reportId") + val reportId: Int, + @SerialName("reportStatus") + val reportStatus: String, + @SerialName("reportTitle") + val reportTitle: String, + @SerialName("reportCategory") + val reportCategory: String, + @SerialName("reportLocation") + val reportLocation: String, + @SerialName("reportImageUrl") + val reportImageUrl: String, +) + +fun ReportItemDto.toDomain(): ReportItem = + ReportItem( + id = this.reportId, + status = ReportStatus.fromString(this.reportStatus), + title = this.reportTitle, + category = ReportCategory.fromString(this.reportCategory), + address = this.reportLocation, + imageUrl = this.reportImageUrl, + ) diff --git a/data/src/main/java/com/threegap/bitnagil/data/report/repositoryImpl/ReportRepositoryImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/report/repositoryImpl/ReportRepositoryImpl.kt index f0261faf..dcfe9e36 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/report/repositoryImpl/ReportRepositoryImpl.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/report/repositoryImpl/ReportRepositoryImpl.kt @@ -2,8 +2,11 @@ package com.threegap.bitnagil.data.report.repositoryImpl import com.threegap.bitnagil.data.report.datasource.ReportDataSource import com.threegap.bitnagil.data.report.model.request.toDto +import com.threegap.bitnagil.data.report.model.response.toDomainMap import com.threegap.bitnagil.domain.report.model.Report +import com.threegap.bitnagil.domain.report.model.ReportItem import com.threegap.bitnagil.domain.report.repository.ReportRepository +import java.time.LocalDate import javax.inject.Inject class ReportRepositoryImpl @Inject constructor( @@ -12,4 +15,8 @@ class ReportRepositoryImpl @Inject constructor( override suspend fun submitReport(report: Report): Result { return reportDataSource.submitReport(report.toDto()) } + + override suspend fun getReportHistories(): Result>> { + return reportDataSource.getReports().map { it.toDomainMap() } + } } diff --git a/data/src/main/java/com/threegap/bitnagil/data/report/service/ReportService.kt b/data/src/main/java/com/threegap/bitnagil/data/report/service/ReportService.kt index 27a697f0..f6d25128 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/report/service/ReportService.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/report/service/ReportService.kt @@ -1,8 +1,10 @@ package com.threegap.bitnagil.data.report.service import com.threegap.bitnagil.data.report.model.request.ReportRequestDto +import com.threegap.bitnagil.data.report.model.response.ReportHistoriesPerDateDto import com.threegap.bitnagil.network.model.BaseResponse import retrofit2.http.Body +import retrofit2.http.GET import retrofit2.http.POST interface ReportService { @@ -10,4 +12,7 @@ interface ReportService { suspend fun submitReport( @Body reportRequestDto: ReportRequestDto, ): BaseResponse + + @GET("/api/v2/reports") + suspend fun getReports(): BaseResponse } diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/report/model/ReportItem.kt b/domain/src/main/java/com/threegap/bitnagil/domain/report/model/ReportItem.kt new file mode 100644 index 00000000..92fa471a --- /dev/null +++ b/domain/src/main/java/com/threegap/bitnagil/domain/report/model/ReportItem.kt @@ -0,0 +1,10 @@ +package com.threegap.bitnagil.domain.report.model + +data class ReportItem( + val id: Int, + val title: String, + val category: ReportCategory, + val status: ReportStatus, + val imageUrl: String, + val address: String, +) diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/report/model/ReportStatus.kt b/domain/src/main/java/com/threegap/bitnagil/domain/report/model/ReportStatus.kt new file mode 100644 index 00000000..a618d0db --- /dev/null +++ b/domain/src/main/java/com/threegap/bitnagil/domain/report/model/ReportStatus.kt @@ -0,0 +1,27 @@ +package com.threegap.bitnagil.domain.report.model + +enum class ReportStatus { + Pending, + InProgress, + Completed, + ; + + companion object { + fun fromString(value: String): ReportStatus { + return when (value) { + "PENDING" -> Pending + "IN_PROGRESS" -> InProgress + "COMPLETED" -> Completed + else -> throw IllegalArgumentException("Invalid ReportStatus value: $value") + } + } + + fun toString(value: ReportStatus): String { + return when (value) { + Pending -> "PENDING" + InProgress -> "IN_PROGRESS" + Completed -> "COMPLETED" + } + } + } +} diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/report/repository/ReportRepository.kt b/domain/src/main/java/com/threegap/bitnagil/domain/report/repository/ReportRepository.kt index b6cd018a..39a68033 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/report/repository/ReportRepository.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/report/repository/ReportRepository.kt @@ -1,7 +1,10 @@ package com.threegap.bitnagil.domain.report.repository import com.threegap.bitnagil.domain.report.model.Report +import com.threegap.bitnagil.domain.report.model.ReportItem +import java.time.LocalDate interface ReportRepository { suspend fun submitReport(report: Report): Result + suspend fun getReportHistories(): Result>> } diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/report/usecase/GetReportHistoriesUseCase.kt b/domain/src/main/java/com/threegap/bitnagil/domain/report/usecase/GetReportHistoriesUseCase.kt new file mode 100644 index 00000000..97af0197 --- /dev/null +++ b/domain/src/main/java/com/threegap/bitnagil/domain/report/usecase/GetReportHistoriesUseCase.kt @@ -0,0 +1,14 @@ +package com.threegap.bitnagil.domain.report.usecase + +import com.threegap.bitnagil.domain.report.model.ReportItem +import com.threegap.bitnagil.domain.report.repository.ReportRepository +import java.time.LocalDate +import javax.inject.Inject + +class GetReportHistoriesUseCase @Inject constructor( + private val reportRepository: ReportRepository, +) { + suspend operator fun invoke(): Result>> { + return reportRepository.getReportHistories() + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/mypage/MyPageScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/mypage/MyPageScreen.kt index 3feabcac..6bb355ae 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/mypage/MyPageScreen.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/mypage/MyPageScreen.kt @@ -38,6 +38,7 @@ fun MyPageScreenContainer( navigateToNotice: () -> Unit, navigateToQnA: () -> Unit, navigateToOnBoarding: () -> Unit, + navigateToReportHistory: () -> Unit, ) { val state by myPageViewModel.stateFlow.collectAsState() @@ -47,6 +48,7 @@ fun MyPageScreenContainer( onClickNotice = navigateToNotice, onClickResetOnBoarding = navigateToOnBoarding, onClickQnA = navigateToQnA, + onClickReportHistory = navigateToReportHistory, ) } @@ -57,6 +59,7 @@ private fun MyPageScreen( onClickNotice: () -> Unit, onClickResetOnBoarding: () -> Unit, onClickQnA: () -> Unit, + onClickReportHistory: () -> Unit, ) { Column( modifier = Modifier @@ -100,6 +103,11 @@ private fun MyPageScreen( Spacer(modifier = Modifier.height(16.dp)) + BitnagilOptionButton( + title = "내 제보 기록", + onClick = onClickReportHistory, + ) + BitnagilOptionButton( title = "내 목표 재설정", onClick = onClickResetOnBoarding, @@ -127,6 +135,7 @@ fun MyPageScreenPreview() { onClickNotice = { }, onClickResetOnBoarding = { }, onClickQnA = { }, + onClickReportHistory = { }, ) } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/ReportHistoryScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/ReportHistoryScreen.kt new file mode 100644 index 00000000..a9280669 --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/ReportHistoryScreen.kt @@ -0,0 +1,229 @@ +package com.threegap.bitnagil.presentation.reporthistory + +import androidx.compose.foundation.background +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.rememberScrollState +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.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.threegap.bitnagil.designsystem.BitnagilTheme +import com.threegap.bitnagil.designsystem.R +import com.threegap.bitnagil.designsystem.component.atom.BitnagilChip +import com.threegap.bitnagil.designsystem.component.atom.BitnagilIcon +import com.threegap.bitnagil.designsystem.component.block.BitnagilTopBar +import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple +import com.threegap.bitnagil.presentation.reporthistory.component.block.ReportHistoryItem +import com.threegap.bitnagil.presentation.reporthistory.component.template.ReportCategoryBottomSheet +import com.threegap.bitnagil.presentation.reporthistory.model.ReportCategory +import com.threegap.bitnagil.presentation.reporthistory.model.ReportHistoriesPerDayUiModel +import com.threegap.bitnagil.presentation.reporthistory.model.ReportHistoryState +import com.threegap.bitnagil.presentation.reporthistory.model.ReportHistoryUiModel +import com.threegap.bitnagil.presentation.reporthistory.model.ReportProcess +import com.threegap.bitnagil.presentation.reporthistory.util.toPresentationFormat +import org.orbitmvi.orbit.compose.collectAsState + +@Composable +fun ReportHistoryScreenContainer( + viewModel: ReportHistoryViewModel = hiltViewModel(), + navigateToBack: () -> Unit, + navigateToReportDetail: (String) -> Unit, +) { + val state by viewModel.collectAsState() + + if (state.showSelectReportCategoryBottomSheet) { + ReportCategoryBottomSheet( + selectedCategory = state.selectedReportCategory, + onDismiss = viewModel::hideReportCategoryBottomSheet, + onSelected = viewModel::selectReportCategory, + ) + } + + ReportHistoryScreen( + state = state, + onClickPreviousButton = navigateToBack, + onClickReportProcessChip = viewModel::selectReportProcess, + onClickReportCategoryButton = viewModel::showReportCategoryBottomSheet, + onClickReportItem = navigateToReportDetail, + ) +} + +@Composable +private fun ReportHistoryScreen( + modifier: Modifier = Modifier, + state: ReportHistoryState, + onClickPreviousButton: () -> Unit, + onClickReportProcessChip: (ReportProcess) -> Unit, + onClickReportCategoryButton: () -> Unit, + onClickReportItem: (String) -> Unit, +) { + val horizontalScreenState = rememberScrollState() + + Column( + modifier = modifier + .fillMaxSize() + .background(color = BitnagilTheme.colors.coolGray99) + .statusBarsPadding(), + ) { + BitnagilTopBar( + title = "내 제보 기록", + showBackButton = true, + onBackClick = onClickPreviousButton, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Row( + modifier = Modifier.horizontalScroll(horizontalScreenState), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Spacer(modifier = Modifier.width(8.dp)) + + state.reportProcessWithCounts.forEach { reportProcessWithCount -> + BitnagilChip( + title = reportProcessWithCount.titleWithCount, + isSelected = state.selectedReportProcess == reportProcessWithCount.process, + onCategorySelected = { + onClickReportProcessChip(reportProcessWithCount.process) + }, + ) + } + + Spacer(modifier = Modifier.width(8.dp)) + } + + Spacer(modifier = Modifier.height(24.dp)) + + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + ) { + if (state.filteredReportHistoriesPerDays.isNotEmpty()) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(horizontal = 16.dp), + ) { + state.filteredReportHistoriesPerDays.forEach { reportHistoriesPerDay -> + stickyHeader { + Box( + modifier = Modifier + .fillMaxWidth() + .height(40.dp) + .background(color = BitnagilTheme.colors.coolGray99), + ) { + Text( + text = reportHistoriesPerDay.date.toPresentationFormat(), + modifier = Modifier.align(Alignment.CenterStart), + style = BitnagilTheme.typography.body2SemiBold, + ) + } + } + + itemsIndexed(reportHistoriesPerDay.reports) { index, report -> + ReportHistoryItem( + modifier = Modifier.padding(bottom = if (index == reportHistoriesPerDay.reports.lastIndex) 24.dp else 10.dp), + report = report, + onClick = { + onClickReportItem(report.id) + }, + ) + } + } + } + } else { + Column( + modifier = Modifier.align(Alignment.Center), + verticalArrangement = Arrangement.spacedBy(2.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text(text = "제보한 내역이 없어요.", style = BitnagilTheme.typography.subtitle1SemiBold) + Text(text = "원하는 카테고리로 제보를 시작해 보세요.", style = BitnagilTheme.typography.body2Regular, color = BitnagilTheme.colors.coolGray70) + } + } + + if (state.showCategorySelectButton) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(5.dp), + modifier = Modifier + .height(40.dp) + .align(Alignment.TopEnd) + .clickableWithoutRipple(onClick = onClickReportCategoryButton), + ) { + Text( + text = state.selectedReportCategory?.title ?: "카테고리", + color = BitnagilTheme.colors.coolGray40, + style = BitnagilTheme.typography.body2Medium, + modifier = Modifier.padding(start = 10.dp), + ) + + BitnagilIcon( + id = R.drawable.ic_down_arrow, + tint = BitnagilTheme.colors.coolGray40, + modifier = Modifier + .padding(end = 13.dp) + .size(16.dp), + ) + } + } + } + } +} + +@Composable +@Preview +private fun ReportHistoryScreenPreview() { + BitnagilTheme { + ReportHistoryScreen( + state = ReportHistoryState.Init.copy( + reportHistoriesPerDays = List(10) { + ReportHistoriesPerDayUiModel( + date = java.time.LocalDate.now(), + reports = listOf( + ReportHistoryUiModel( + id = "1", + title = "제보 1", + imageUrl = "-", + location = "서울특별시 성북구 안암로 106", + process = ReportProcess.Reported, + category = ReportCategory.Amenities, + ), + ReportHistoryUiModel( + id = "1", + title = "제보 1", + imageUrl = "-", + location = "서울특별시 성북구 안암로 106", + process = ReportProcess.Progress, + category = ReportCategory.Amenities, + ), + ), + ) + }, + ), + onClickPreviousButton = {}, + onClickReportProcessChip = {}, + onClickReportCategoryButton = {}, + onClickReportItem = {}, + ) + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/ReportHistoryViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/ReportHistoryViewModel.kt new file mode 100644 index 00000000..28028bae --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/ReportHistoryViewModel.kt @@ -0,0 +1,87 @@ +package com.threegap.bitnagil.presentation.reporthistory + +import androidx.lifecycle.ViewModel +import com.threegap.bitnagil.domain.report.usecase.GetReportHistoriesUseCase +import com.threegap.bitnagil.presentation.reporthistory.model.ReportCategory +import com.threegap.bitnagil.presentation.reporthistory.model.ReportHistoriesPerDayUiModel +import com.threegap.bitnagil.presentation.reporthistory.model.ReportHistorySideEffect +import com.threegap.bitnagil.presentation.reporthistory.model.ReportHistoryState +import com.threegap.bitnagil.presentation.reporthistory.model.ReportHistoryUiModel +import com.threegap.bitnagil.presentation.reporthistory.model.ReportProcess +import dagger.hilt.android.lifecycle.HiltViewModel +import org.orbitmvi.orbit.Container +import org.orbitmvi.orbit.ContainerHost +import org.orbitmvi.orbit.viewmodel.container +import javax.inject.Inject + +@HiltViewModel +class ReportHistoryViewModel @Inject constructor( + private val getReportHistoriesUseCase: GetReportHistoriesUseCase, +) : ContainerHost, ViewModel() { + override val container: Container = container(initialState = ReportHistoryState.Init) + + init { + loadReportHistories() + } + + private fun loadReportHistories() = intent { + getReportHistoriesUseCase().fold( + onSuccess = { reportHistoriesPerDate -> + val reportHistoriesPerDays = reportHistoriesPerDate + .map { reportHistoryPerDateMap -> + ReportHistoriesPerDayUiModel( + date = reportHistoryPerDateMap.key, + reports = reportHistoryPerDateMap.value.map { + ReportHistoryUiModel.fromDomain(it) + }, + ) + } + .sortedByDescending { reportHistoryPerDate -> + reportHistoryPerDate.date + } + + reduce { + state.copy( + reportHistoriesPerDays = reportHistoriesPerDays, + ) + } + }, + onFailure = { + }, + ) + } + + fun selectReportCategory(reportCategory: ReportCategory) = intent { + val currentSelectedReportCategory = state.selectedReportCategory + + reduce { + state.copy( + selectedReportCategory = if (currentSelectedReportCategory == reportCategory) null else reportCategory, + ) + } + } + + fun selectReportProcess(reportProcess: ReportProcess) = intent { + reduce { + state.copy( + selectedReportProcess = reportProcess, + ) + } + } + + fun showReportCategoryBottomSheet() = intent { + reduce { + state.copy( + showSelectReportCategoryBottomSheet = true, + ) + } + } + + fun hideReportCategoryBottomSheet() = intent { + reduce { + state.copy( + showSelectReportCategoryBottomSheet = false, + ) + } + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/component/atom/ReportProcessBadge.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/component/atom/ReportProcessBadge.kt new file mode 100644 index 00000000..b93bf260 --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/component/atom/ReportProcessBadge.kt @@ -0,0 +1,60 @@ +package com.threegap.bitnagil.presentation.reporthistory.component.atom + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.threegap.bitnagil.designsystem.BitnagilTheme +import com.threegap.bitnagil.presentation.reporthistory.model.ReportProcess + +@Composable +fun ReportProcessBadge( + modifier: Modifier = Modifier, + reportProcess: ReportProcess, +) { + Text( + text = reportProcess.title, + style = BitnagilTheme.typography.caption1SemiBold, + color = reportProcess.getProcessBadgeTextColor(), + modifier = modifier + .background(color = reportProcess.getProcessBadgeBackgroundColor(), shape = RoundedCornerShape(6.dp)) + .padding(horizontal = 10.dp, vertical = 4.dp), + ) +} + +@Composable +private fun ReportProcess.getProcessBadgeBackgroundColor(): Color = + when (this) { + ReportProcess.Reported -> BitnagilTheme.colors.green10 + ReportProcess.Progress -> BitnagilTheme.colors.skyBlue10 + else -> BitnagilTheme.colors.coolGray95 + } + +@Composable +private fun ReportProcess.getProcessBadgeTextColor(): Color = + when (this) { + ReportProcess.Reported -> BitnagilTheme.colors.green300 + ReportProcess.Progress -> BitnagilTheme.colors.blue300 + else -> BitnagilTheme.colors.coolGray40 + } + +@Composable +@Preview +private fun ReportProcessBadgePreview() { + BitnagilTheme { + Column( + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + ReportProcessBadge(reportProcess = ReportProcess.Progress) + ReportProcessBadge(reportProcess = ReportProcess.Reported) + ReportProcessBadge(reportProcess = ReportProcess.Complete) + } + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/component/block/ReportHistoryItem.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/component/block/ReportHistoryItem.kt new file mode 100644 index 00000000..88841c41 --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/component/block/ReportHistoryItem.kt @@ -0,0 +1,135 @@ +package com.threegap.bitnagil.presentation.reporthistory.component.block + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import coil3.request.ImageRequest +import com.threegap.bitnagil.designsystem.BitnagilTheme +import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple +import com.threegap.bitnagil.presentation.reporthistory.component.atom.ReportProcessBadge +import com.threegap.bitnagil.presentation.reporthistory.model.ReportCategory +import com.threegap.bitnagil.presentation.reporthistory.model.ReportHistoryUiModel +import com.threegap.bitnagil.presentation.reporthistory.model.ReportProcess + +@Composable +fun ReportHistoryItem( + modifier: Modifier = Modifier, + report: ReportHistoryUiModel, + onClick: (String) -> Unit, +) { + Row( + modifier = modifier + .background(color = BitnagilTheme.colors.white, shape = RoundedCornerShape(12.dp)) + .clickableWithoutRipple(onClick = { onClick(report.id) }) + .padding(vertical = 14.dp, horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Column( + modifier = Modifier.weight(1f), + ) { + ReportProcessBadge(reportProcess = report.process) + + Spacer(modifier = Modifier.height(8.dp)) + + Text(text = report.title, style = BitnagilTheme.typography.body2Medium, maxLines = 2) + + Spacer(modifier = Modifier.height(12.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text(text = report.category.title, style = BitnagilTheme.typography.caption1Medium.copy(color = BitnagilTheme.colors.coolGray50)) + + Box( + modifier = Modifier + .size(4.dp) + .background(color = BitnagilTheme.colors.coolGray90, shape = CircleShape), + ) + + Text( + text = report.location, + modifier = Modifier.weight(1f), + style = BitnagilTheme.typography.caption1Medium.copy(color = BitnagilTheme.colors.coolGray50), + maxLines = 1, + ) + } + } + + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(report.imageUrl) + .build(), + modifier = Modifier + .size(74.dp) + .clip(shape = RoundedCornerShape(9.dp)) + .background(color = BitnagilTheme.colors.black), + contentDescription = null, + ) + } +} + +@Composable +@Preview +private fun ReportHistoryItemPreview() { + BitnagilTheme { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + ReportHistoryItem( + report = ReportHistoryUiModel( + id = "1", + title = "다이소 덕소점 앞 가로등 심해요", + imageUrl = "-", + location = "서울특별시 성북구 안암로 106", + process = ReportProcess.Reported, + category = ReportCategory.TrafficFacilities, + ), + onClick = {}, + ) + + ReportHistoryItem( + report = ReportHistoryUiModel( + id = "1", + title = "다이소 덕소점 앞 가로등 깜빡이는데 어쩌죠 이거 진짜로 심각한 문제인데 이 뒷부분은 figma에서 짤려 보여서 임의로 채웁니다", + imageUrl = "-", + location = "서울특별시 성북구 안암로 106", + process = ReportProcess.Progress, + category = ReportCategory.TrafficFacilities, + ), + onClick = {}, + ) + + ReportHistoryItem( + report = ReportHistoryUiModel( + id = "1", + title = "퇴근하고 싶어요", + imageUrl = "-", + location = "서울특별시 성북구 안암로 106 서울특별시 성북구 안암로 106 서울특별시 성북구 안암로 106", + process = ReportProcess.Complete, + category = ReportCategory.Amenities, + ), + onClick = {}, + ) + } + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/component/template/ReportCategoryBottomSheet.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/component/template/ReportCategoryBottomSheet.kt new file mode 100644 index 00000000..1e1ce5d5 --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/component/template/ReportCategoryBottomSheet.kt @@ -0,0 +1,142 @@ +package com.threegap.bitnagil.presentation.reporthistory.component.template + +import androidx.annotation.DrawableRes +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.width +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.threegap.bitnagil.designsystem.BitnagilTheme +import com.threegap.bitnagil.designsystem.R +import com.threegap.bitnagil.designsystem.component.atom.BitnagilIcon +import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple +import com.threegap.bitnagil.presentation.reporthistory.model.ReportCategory +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ReportCategoryBottomSheet( + selectedCategory: ReportCategory?, + onDismiss: () -> Unit, + onSelected: (ReportCategory) -> Unit, + modifier: Modifier = Modifier, +) { + val sheetState = rememberModalBottomSheetState() + val coroutineScope = rememberCoroutineScope() + + ModalBottomSheet( + sheetState = sheetState, + onDismissRequest = onDismiss, + containerColor = BitnagilTheme.colors.white, + contentColor = BitnagilTheme.colors.white, + modifier = modifier, + ) { + Column( + modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 28.dp), + ) { + ReportCategory.entries.forEachIndexed { index, category -> + ReportCategoryItem( + icon = category.iconResourceId, + title = category.title, + description = category.description, + isSelected = selectedCategory == category, + onClick = { + onSelected(category) + coroutineScope + .launch { sheetState.hide() } + .invokeOnCompletion { + if (!sheetState.isVisible) onDismiss() + } + }, + ) + + if (index < ReportCategory.entries.lastIndex) { + HorizontalDivider( + color = BitnagilTheme.colors.coolGray97, + ) + } + } + } + } +} + +@Composable +private fun ReportCategoryItem( + title: String, + description: String, + @DrawableRes icon: Int, + isSelected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .clickableWithoutRipple { onClick() } + .padding(vertical = 14.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + BitnagilIcon(id = icon, tint = null) + + Spacer(modifier = Modifier.width(14.dp)) + + Column { + Text( + text = title, + color = BitnagilTheme.colors.coolGray10, + style = BitnagilTheme.typography.body1Medium, + ) + + Text( + text = description, + color = BitnagilTheme.colors.coolGray70, + style = BitnagilTheme.typography.body2Medium, + ) + } + + Spacer(modifier = Modifier.weight(1f)) + + if (isSelected) { + BitnagilIcon( + id = R.drawable.ic_check_md, + tint = BitnagilTheme.colors.orange500, + ) + } + } +} + +@Preview +@Composable +private fun ReportCategoryBottomSheetPreview() { + ReportCategoryBottomSheet( + selectedCategory = ReportCategory.WaterFacilities, + onDismiss = {}, + onSelected = {}, + ) +} + +@Preview(showBackground = true) +@Composable +private fun ReportCategoryItemPreview() { + ReportCategoryItem( + title = "교통 시설", + description = "어쩌구", + icon = R.drawable.ic_check_md, + isSelected = true, + onClick = {}, + ) +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportCategory.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportCategory.kt new file mode 100644 index 00000000..642eca1c --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportCategory.kt @@ -0,0 +1,43 @@ +package com.threegap.bitnagil.presentation.reporthistory.model + +import com.threegap.bitnagil.designsystem.R +import com.threegap.bitnagil.domain.report.model.ReportCategory as DomainReportCategory + +enum class ReportCategory( + val title: String, + val description: String, + val iconResourceId: Int, +) { + TrafficFacilities( + title = "교통 시설", + description = "신호등 고장, 표지판 파손, 횡단보도 등", + iconResourceId = R.drawable.ic_car, + ), + LightingFacilities( + title = "조명 시설", + description = "가로등, 보안등 파손 등", + iconResourceId = R.drawable.ic_light, + ), + WaterFacilities( + title = "상하수도 시설", + description = "맨홀 뚜껑 손상 등", + iconResourceId = R.drawable.ic_water, + ), + Amenities( + title = "편의 시설", + description = "벤치 파손, 휴지통 넘침 등", + iconResourceId = R.drawable.ic_hammer, + ), + ; + + companion object { + fun fromDomain(domainReportCategory: DomainReportCategory): ReportCategory { + return when (domainReportCategory) { + DomainReportCategory.TRANSPORTATION -> TrafficFacilities + DomainReportCategory.LIGHTING -> LightingFacilities + DomainReportCategory.WATERFACILITY -> WaterFacilities + DomainReportCategory.AMENITY -> Amenities + } + } + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportHistoriesPerDayUiModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportHistoriesPerDayUiModel.kt new file mode 100644 index 00000000..4f233ba4 --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportHistoriesPerDayUiModel.kt @@ -0,0 +1,8 @@ +package com.threegap.bitnagil.presentation.reporthistory.model + +import java.time.LocalDate + +data class ReportHistoriesPerDayUiModel( + val date: LocalDate, + val reports: List, +) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportHistorySideEffect.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportHistorySideEffect.kt new file mode 100644 index 00000000..9bf7b78a --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportHistorySideEffect.kt @@ -0,0 +1,3 @@ +package com.threegap.bitnagil.presentation.reporthistory.model + +sealed interface ReportHistorySideEffect diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportHistoryState.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportHistoryState.kt new file mode 100644 index 00000000..80de9be4 --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportHistoryState.kt @@ -0,0 +1,62 @@ +package com.threegap.bitnagil.presentation.reporthistory.model + +data class ReportHistoryState( + val selectedReportCategory: ReportCategory?, + val selectedReportProcess: ReportProcess, + val reportHistoriesPerDays: List, + val showSelectReportCategoryBottomSheet: Boolean, +) { + val filteredReportHistoriesPerDays: List = reportHistoriesPerDays + .map { reportHistoriesPerDay -> + reportHistoriesPerDay.copy( + reports = reportHistoriesPerDay.reports.filter { + val processMatched = when (selectedReportProcess) { + ReportProcess.Total -> true + ReportProcess.Reported -> it.process == ReportProcess.Reported + ReportProcess.Progress -> it.process == ReportProcess.Progress + ReportProcess.Complete -> it.process == ReportProcess.Complete + } + + val categoryMatched = when (selectedReportCategory) { + ReportCategory.TrafficFacilities -> it.category == ReportCategory.TrafficFacilities + ReportCategory.LightingFacilities -> it.category == ReportCategory.LightingFacilities + ReportCategory.WaterFacilities -> it.category == ReportCategory.WaterFacilities + ReportCategory.Amenities -> it.category == ReportCategory.Amenities + null -> true + } + + processMatched && categoryMatched + }, + ) + } + .filter { reportHistoriesPerDay -> + reportHistoriesPerDay.reports.isNotEmpty() + } + + val reportProcessWithCounts: List = listOf( + ReportProcessWithCount(ReportProcess.Total, reportHistoriesPerDays.sumOf { it.reports.size }), + ReportProcessWithCount( + ReportProcess.Reported, + reportHistoriesPerDays.sumOf { it.reports.filter { report -> report.process == ReportProcess.Reported }.size }, + ), + ReportProcessWithCount( + ReportProcess.Progress, + reportHistoriesPerDays.sumOf { it.reports.filter { report -> report.process == ReportProcess.Progress }.size }, + ), + ReportProcessWithCount( + ReportProcess.Complete, + reportHistoriesPerDays.sumOf { it.reports.filter { report -> report.process == ReportProcess.Complete }.size }, + ), + ) + + val showCategorySelectButton: Boolean = reportHistoriesPerDays.isNotEmpty() + + companion object { + val Init = ReportHistoryState( + selectedReportCategory = null, + selectedReportProcess = ReportProcess.Total, + reportHistoriesPerDays = listOf(), + showSelectReportCategoryBottomSheet = false, + ) + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportHistoryUiModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportHistoryUiModel.kt new file mode 100644 index 00000000..ee6f32af --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportHistoryUiModel.kt @@ -0,0 +1,25 @@ +package com.threegap.bitnagil.presentation.reporthistory.model + +import com.threegap.bitnagil.domain.report.model.ReportItem + +data class ReportHistoryUiModel( + val id: String, + val title: String, + val imageUrl: String, + val location: String, + val process: ReportProcess, + val category: ReportCategory, +) { + companion object { + fun fromDomain(reportItem: ReportItem): ReportHistoryUiModel { + return ReportHistoryUiModel( + id = "${reportItem.id}", + title = reportItem.title, + imageUrl = reportItem.imageUrl, + location = reportItem.address, + process = ReportProcess.fromDomain(reportItem.status), + category = ReportCategory.fromDomain(reportItem.category), + ) + } + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportProcess.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportProcess.kt new file mode 100644 index 00000000..f4456f59 --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportProcess.kt @@ -0,0 +1,23 @@ +package com.threegap.bitnagil.presentation.reporthistory.model + +import com.threegap.bitnagil.domain.report.model.ReportStatus + +enum class ReportProcess( + val title: String, +) { + Total(title = "전체"), + Reported(title = "제보 완료"), + Progress(title = "처리 중"), + Complete(title = "처리 완료"), + ; + + companion object { + fun fromDomain(status: ReportStatus): ReportProcess { + return when (status) { + ReportStatus.Pending -> Reported + ReportStatus.InProgress -> Progress + ReportStatus.Completed -> Complete + } + } + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportProcessWithCount.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportProcessWithCount.kt new file mode 100644 index 00000000..df6409b4 --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/model/ReportProcessWithCount.kt @@ -0,0 +1,15 @@ +package com.threegap.bitnagil.presentation.reporthistory.model + +data class ReportProcessWithCount( + val process: ReportProcess, + val count: Int, +) { + val titleWithCount: String = if (count == 0) process.title else "${process.title} $count" + + companion object { + val Init = ReportProcessWithCount( + process = ReportProcess.Total, + count = 0, + ) + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/util/LocalDateUtils.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/util/LocalDateUtils.kt new file mode 100644 index 00000000..9d9b54fd --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/reporthistory/util/LocalDateUtils.kt @@ -0,0 +1,10 @@ +package com.threegap.bitnagil.presentation.reporthistory.util + +import java.time.LocalDate +import java.time.format.DateTimeFormatter +import java.util.Locale + +fun LocalDate.toPresentationFormat(): String { + val formatter = DateTimeFormatter.ofPattern("yy.MM.dd E", Locale.KOREAN) + return this.format(formatter) +}