diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 2b62de26..7d387d13 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -16,6 +16,7 @@ + diff --git a/app/src/main/java/com/wap/wapp/navigation/WappNavHost.kt b/app/src/main/java/com/wap/wapp/navigation/WappNavHost.kt index 22f7ef35..a012a1f9 100644 --- a/app/src/main/java/com/wap/wapp/navigation/WappNavHost.kt +++ b/app/src/main/java/com/wap/wapp/navigation/WappNavHost.kt @@ -70,6 +70,7 @@ fun WappNavHost( surveyNavGraph( navigateToSurvey = navController::navigateToSurvey, navigateToSurveyAnswer = navController::navigateToSurveyAnswer, + navigateToSignIn = navController::navigateToSignIn, ) surveyCheckScreen( navigateToManagement = navController::navigateToManagement, diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/management/ManagementRepository.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/management/ManagementRepository.kt index 9092de50..d4c5b169 100644 --- a/core/data/src/main/java/com/wap/wapp/core/data/repository/management/ManagementRepository.kt +++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/management/ManagementRepository.kt @@ -1,7 +1,7 @@ package com.wap.wapp.core.data.repository.management interface ManagementRepository { - suspend fun getManager(userId: String): Result + suspend fun isManager(userId: String): Result suspend fun postManager(userId: String): Result diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/management/ManagementRepositoryImpl.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/management/ManagementRepositoryImpl.kt index 05e3afed..f9f782dd 100644 --- a/core/data/src/main/java/com/wap/wapp/core/data/repository/management/ManagementRepositoryImpl.kt +++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/management/ManagementRepositoryImpl.kt @@ -6,8 +6,8 @@ import javax.inject.Inject class ManagementRepositoryImpl @Inject constructor( private val managementDataSource: ManagementDataSource, ) : ManagementRepository { - override suspend fun getManager(userId: String): Result = - managementDataSource.getManager(userId) + override suspend fun isManager(userId: String): Result = + managementDataSource.isManager(userId) override suspend fun postManager(userId: String): Result = managementDataSource.postManager(userId) diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/management/HasManagerStateUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/management/HasManagerStateUseCase.kt index 23c3c573..d9421b2e 100644 --- a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/management/HasManagerStateUseCase.kt +++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/management/HasManagerStateUseCase.kt @@ -13,7 +13,7 @@ class HasManagerStateUseCase @Inject constructor( suspend operator fun invoke(): Result = runCatching { val userId = userRepository.getUserId().getOrThrow() - managementRepository.getManager(userId).fold( + managementRepository.isManager(userId).fold( onSuccess = { hasManagerState -> hasManagerState }, onFailure = { throw (it) }, ) diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/user/GetUserRoleUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/user/GetUserRoleUseCase.kt new file mode 100644 index 00000000..9357ff26 --- /dev/null +++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/user/GetUserRoleUseCase.kt @@ -0,0 +1,36 @@ +package com.wap.wapp.core.domain.usecase.user + +import com.wap.wapp.core.data.repository.management.ManagementRepository +import com.wap.wapp.core.data.repository.user.UserRepository +import com.wap.wapp.core.model.user.UserRole +import javax.inject.Inject + +class GetUserRoleUseCase @Inject constructor( + private val userRepository: UserRepository, + private val managementRepository: ManagementRepository, +) { + suspend operator fun invoke(): Result { + return runCatching { + val userId = userRepository.getUserId() + .getOrElse { exception -> + if (exception is IllegalStateException) { // 회원이 아닌 경우 + return@runCatching UserRole.GUEST + } + throw exception + } + + managementRepository.isManager(userId) + .fold( + onSuccess = { isManager -> + if (isManager) { // 매니저인 경우 + return@fold UserRole.MANAGER + } + UserRole.MEMBER + }, + onFailure = { exception -> + throw exception + }, + ) + } + } +} diff --git a/core/model/src/main/java/com/wap/wapp/core/model/survey/SurveyForm.kt b/core/model/src/main/java/com/wap/wapp/core/model/survey/SurveyForm.kt index d105c51a..164c298f 100644 --- a/core/model/src/main/java/com/wap/wapp/core/model/survey/SurveyForm.kt +++ b/core/model/src/main/java/com/wap/wapp/core/model/survey/SurveyForm.kt @@ -1,6 +1,8 @@ package com.wap.wapp.core.model.survey +import java.time.Duration import java.time.LocalDateTime +import java.time.format.DateTimeFormatter // 운영진이 등록하는 설문 모델 data class SurveyForm( @@ -17,4 +19,31 @@ data class SurveyForm( emptyList(), LocalDateTime.MIN, ) + + fun calculateDeadline(): String { + val currentDateTime = LocalDateTime.now() + val duration = Duration.between(currentDateTime, deadline) + + if (duration.toMinutes() < 60) { + val leftMinutes = duration.toMinutes().toString() + return leftMinutes + "분 후 마감" + } + + if (duration.toHours() < 24) { + val leftHours = duration.toHours().toString() + return leftHours + "시간 후 마감" + } + + if (duration.toDays() < 31) { + val leftDays = duration.toDays().toString() + return leftDays + "일 후 마감" + } + + return deadline.format(DateTimeFormatter.ofPattern("yyyy.MM.dd")) + " 마감" + } + + fun isAfterDeadline(): Boolean { + val currentDateTime = LocalDateTime.now() + return deadline.isAfter(currentDateTime) + } } diff --git a/core/model/src/main/java/com/wap/wapp/core/model/user/UserRole.kt b/core/model/src/main/java/com/wap/wapp/core/model/user/UserRole.kt new file mode 100644 index 00000000..a159c242 --- /dev/null +++ b/core/model/src/main/java/com/wap/wapp/core/model/user/UserRole.kt @@ -0,0 +1,5 @@ +package com.wap.wapp.core.model.user + +enum class UserRole { + GUEST, MEMBER, MANAGER +} diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/management/ManagementDataSource.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/management/ManagementDataSource.kt index 20a26f06..c8d3c528 100644 --- a/core/network/src/main/java/com/wap/wapp/core/network/source/management/ManagementDataSource.kt +++ b/core/network/src/main/java/com/wap/wapp/core/network/source/management/ManagementDataSource.kt @@ -1,7 +1,7 @@ package com.wap.wapp.core.network.source.management interface ManagementDataSource { - suspend fun getManager(userId: String): Result + suspend fun isManager(userId: String): Result suspend fun postManager(userId: String): Result diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/management/ManagementDataSourceImpl.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/management/ManagementDataSourceImpl.kt index 1d166301..a36389c7 100644 --- a/core/network/src/main/java/com/wap/wapp/core/network/source/management/ManagementDataSourceImpl.kt +++ b/core/network/src/main/java/com/wap/wapp/core/network/source/management/ManagementDataSourceImpl.kt @@ -10,7 +10,7 @@ import javax.inject.Inject class ManagementDataSourceImpl @Inject constructor( private val firebaseFirestore: FirebaseFirestore, ) : ManagementDataSource { - override suspend fun getManager(userId: String): Result = runCatching { + override suspend fun isManager(userId: String): Result = runCatching { val result = firebaseFirestore.collection(MANAGER_COLLECTION) .whereEqualTo("userId", userId) .get() diff --git a/feature/survey/src/main/java/com/wap/wapp/feature/survey/SurveyGuestDialog.kt b/feature/survey/src/main/java/com/wap/wapp/feature/survey/SurveyGuestDialog.kt new file mode 100644 index 00000000..55bca4ef --- /dev/null +++ b/feature/survey/src/main/java/com/wap/wapp/feature/survey/SurveyGuestDialog.kt @@ -0,0 +1,59 @@ +package com.wap.wapp.feature.survey + +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.AlertDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.wap.designsystem.WappTheme +import com.wap.designsystem.component.WappButton + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun SurveyGuestDialog( + onDismissRequest: () -> Unit, + onButtonClicked: () -> Unit, +) { + AlertDialog( + onDismissRequest = { onDismissRequest() }, + modifier = Modifier + .padding(16.dp) + .fillMaxWidth(), + ) { + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth(), + ) { + Column { + Text( + text = stringResource(R.string.survey_guset_title), + style = WappTheme.typography.titleBold, + color = WappTheme.colors.white, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + ) + Text( + text = stringResource(R.string.survey_guest_content), + style = WappTheme.typography.captionMedium, + color = WappTheme.colors.white, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + ) + } + + WappButton( + textRes = R.string.go_to_signin, + onClick = onButtonClicked, + ) + } + } +} diff --git a/feature/survey/src/main/java/com/wap/wapp/feature/survey/SurveyScreen.kt b/feature/survey/src/main/java/com/wap/wapp/feature/survey/SurveyScreen.kt index 054e20e1..2120b626 100644 --- a/feature/survey/src/main/java/com/wap/wapp/feature/survey/SurveyScreen.kt +++ b/feature/survey/src/main/java/com/wap/wapp/feature/survey/SurveyScreen.kt @@ -19,30 +19,33 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.wap.designsystem.WappTheme import com.wap.designsystem.component.WappMainTopBar import com.wap.wapp.core.commmon.extensions.toSupportingText -import com.wap.wapp.core.commmon.util.DateUtil -import com.wap.wapp.core.commmon.util.DateUtil.yyyyMMddFormatter import com.wap.wapp.core.model.survey.SurveyForm +import com.wap.wapp.core.model.user.UserRole import kotlinx.coroutines.flow.collectLatest -import java.time.Duration -import java.time.LocalDateTime @Composable internal fun SurveyScreen( viewModel: SurveyViewModel, + navigateToSignIn: () -> Unit, navigateToSurveyAnswer: (String) -> Unit, ) { val context = LocalContext.current - val surveyFormListUiState = viewModel.surveyFormListUiState.collectAsState().value + val surveyFormListUiState = viewModel.surveyFormListUiState.collectAsStateWithLifecycle().value val snackBarHostState = remember { SnackbarHostState() } + var isShowGuestDialog by rememberSaveable { mutableStateOf(false) } LaunchedEffect(true) { viewModel.surveyEvent.collectLatest { @@ -64,10 +67,36 @@ internal fun SurveyScreen( } } + LaunchedEffect(true) { + viewModel.userRoleUiState.collectLatest { userRoleUiState -> + when (userRoleUiState) { + is SurveyViewModel.UserRoleUiState.Init -> {} + is SurveyViewModel.UserRoleUiState.Success -> { + when (userRoleUiState.userRole) { + UserRole.GUEST -> { + isShowGuestDialog = true + } + + // 비회원이 아닌 경우, 목록 호출 + UserRole.MEMBER, UserRole.MANAGER -> { + viewModel.getSurveyFormList() + } + } + } + } + } + } + Scaffold( modifier = Modifier.fillMaxSize(), containerColor = WappTheme.colors.backgroundBlack, snackbarHost = { SnackbarHost(snackBarHostState) }, + topBar = { + WappMainTopBar( + titleRes = R.string.survey, + contentRes = R.string.survey_content, + ) + }, contentWindowInsets = WindowInsets(0.dp), ) { paddingValues -> when (surveyFormListUiState) { @@ -81,6 +110,13 @@ internal fun SurveyScreen( } } } + + if (isShowGuestDialog) { + SurveyGuestDialog( + onDismissRequest = { isShowGuestDialog = false }, + onButtonClicked = navigateToSignIn, + ) + } } @Composable @@ -93,11 +129,6 @@ private fun SurveyContent( verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.padding(paddingValues), ) { - WappMainTopBar( - titleRes = R.string.survey, - contentRes = R.string.survey_content, - ) - LazyColumn( verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(horizontal = 16.dp), @@ -138,14 +169,13 @@ private fun SurveyFormItemCard( style = WappTheme.typography.titleBold, ) Text( - text = calculateDeadline(surveyForm.deadline), + text = surveyForm.calculateDeadline(), color = WappTheme.colors.yellow34, style = WappTheme.typography.captionMedium, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.End, ) } - Text( text = surveyForm.content, color = WappTheme.colors.grayBD, @@ -154,25 +184,3 @@ private fun SurveyFormItemCard( } } } - -private fun calculateDeadline(deadline: LocalDateTime): String { - val currentDateTime = DateUtil.generateNowDateTime() - val duration = Duration.between(currentDateTime, deadline) - - if (duration.toMinutes() < 60) { - val leftMinutes = duration.toMinutes().toString() - return leftMinutes + "분 후 마감" - } - - if (duration.toHours() < 24) { - val leftHours = duration.toHours().toString() - return leftHours + "시간 후 마감" - } - - if (duration.toDays() < 31) { - val leftDays = duration.toDays().toString() - return leftDays + "일 후 마감" - } - - return deadline.format(yyyyMMddFormatter) + " 마감" -} diff --git a/feature/survey/src/main/java/com/wap/wapp/feature/survey/SurveyViewModel.kt b/feature/survey/src/main/java/com/wap/wapp/feature/survey/SurveyViewModel.kt index f94f0d41..6b1ea72f 100644 --- a/feature/survey/src/main/java/com/wap/wapp/feature/survey/SurveyViewModel.kt +++ b/feature/survey/src/main/java/com/wap/wapp/feature/survey/SurveyViewModel.kt @@ -4,7 +4,9 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.wap.wapp.core.domain.usecase.survey.GetSurveyFormListUseCase import com.wap.wapp.core.domain.usecase.survey.IsSubmittedSurveyUseCase +import com.wap.wapp.core.domain.usecase.user.GetUserRoleUseCase import com.wap.wapp.core.model.survey.SurveyForm +import com.wap.wapp.core.model.user.UserRole import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -15,6 +17,7 @@ import javax.inject.Inject @HiltViewModel class SurveyViewModel @Inject constructor( + private val getUserRoleUseCase: GetUserRoleUseCase, private val getSurveyFormListUseCase: GetSurveyFormListUseCase, private val isSubmittedSurveyUseCase: IsSubmittedSurveyUseCase, ) : ViewModel() { @@ -25,11 +28,27 @@ class SurveyViewModel @Inject constructor( private val _surveyEvent: MutableSharedFlow = MutableSharedFlow() val surveyEvent = _surveyEvent.asSharedFlow() + private val _userRoleUiState: MutableStateFlow = + MutableStateFlow(UserRoleUiState.Init) + val userRoleUiState = _userRoleUiState.asStateFlow() + init { - getSurveyFormList() + getUserRole() } - private fun getSurveyFormList() { + private fun getUserRole() { + viewModelScope.launch { + getUserRoleUseCase() + .onSuccess { userRole -> + _userRoleUiState.value = UserRoleUiState.Success(userRole) + } + .onFailure { throwable -> + _surveyEvent.emit(SurveyUiEvent.Failure(throwable)) + } + } + } + + fun getSurveyFormList() { viewModelScope.launch { getSurveyFormListUseCase() .onSuccess { surveyFormList -> @@ -57,6 +76,11 @@ class SurveyViewModel @Inject constructor( } } + sealed class UserRoleUiState { + data object Init : UserRoleUiState() + data class Success(val userRole: UserRole) : UserRoleUiState() + } + sealed class SurveyFormListUiState { data object Init : SurveyFormListUiState() data class Success(val surveyFormList: List) : SurveyFormListUiState() diff --git a/feature/survey/src/main/java/com/wap/wapp/feature/survey/navigation/SurveyNavigation.kt b/feature/survey/src/main/java/com/wap/wapp/feature/survey/navigation/SurveyNavigation.kt index 306778ed..2a9394f4 100644 --- a/feature/survey/src/main/java/com/wap/wapp/feature/survey/navigation/SurveyNavigation.kt +++ b/feature/survey/src/main/java/com/wap/wapp/feature/survey/navigation/SurveyNavigation.kt @@ -22,10 +22,12 @@ fun NavController.navigateToSurveyAnswer(eventId: String, navOptions: NavOptions fun NavGraphBuilder.surveyNavGraph( navigateToSurveyAnswer: (String) -> Unit, navigateToSurvey: () -> Unit, + navigateToSignIn: () -> Unit, ) { composable(route = SurveyRoute.route) { SurveyScreen( viewModel = hiltViewModel(), + navigateToSignIn = navigateToSignIn, navigateToSurveyAnswer = { eventId -> navigateToSurveyAnswer(eventId) }, diff --git a/feature/survey/src/main/res/values/strings.xml b/feature/survey/src/main/res/values/strings.xml index 6f82b662..2855ada2 100644 --- a/feature/survey/src/main/res/values/strings.xml +++ b/feature/survey/src/main/res/values/strings.xml @@ -10,4 +10,8 @@ 설문 응답 /10자 이상 이미 작성한 설문입니다. + 로그인 하러가기 + 앗 회원이 아니시네요 ! + 회원만 설문을 작성할 수 있어요. + 비회원으로 둘러보기