diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 6fcacf81..7d387d13 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -16,8 +16,9 @@ + - + diff --git a/app/src/main/java/com/wap/wapp/navigation/TopLevelDestination.kt b/app/src/main/java/com/wap/wapp/navigation/TopLevelDestination.kt index f0ba2c0f..eabf5712 100644 --- a/app/src/main/java/com/wap/wapp/navigation/TopLevelDestination.kt +++ b/app/src/main/java/com/wap/wapp/navigation/TopLevelDestination.kt @@ -7,7 +7,7 @@ import com.wap.wapp.core.designresource.R.string import com.wap.wapp.feature.management.navigation.managementNavigationRoute import com.wap.wapp.feature.notice.navigation.noticeNavigationRoute import com.wap.wapp.feature.profile.navigation.profileNavigationRoute -import com.wap.wapp.feature.survey.navigation.surveyNavigationRoute +import com.wap.wapp.feature.survey.navigation.SurveyRoute enum class TopLevelDestination( val route: String, @@ -20,7 +20,7 @@ enum class TopLevelDestination( labelTextId = string.notice, ), SURVEY( - route = surveyNavigationRoute, + route = SurveyRoute.route, iconDrawableId = R.drawable.ic_survey, labelTextId = string.survey, ), 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 f711223c..7c8d57e0 100644 --- a/app/src/main/java/com/wap/wapp/navigation/WappNavHost.kt +++ b/app/src/main/java/com/wap/wapp/navigation/WappNavHost.kt @@ -25,7 +25,9 @@ import com.wap.wapp.feature.profile.navigation.profileScreen import com.wap.wapp.feature.profile.profilesetting.navigation.navigateToProfileSetting import com.wap.wapp.feature.splash.navigation.splashNavigationRoute import com.wap.wapp.feature.splash.navigation.splashScreen -import com.wap.wapp.feature.survey.navigation.surveyScreen +import com.wap.wapp.feature.survey.navigation.navigateToSurvey +import com.wap.wapp.feature.survey.navigation.navigateToSurveyAnswer +import com.wap.wapp.feature.survey.navigation.surveyNavGraph @Composable fun WappNavHost( @@ -58,7 +60,10 @@ fun WappNavHost( navigateToSignIn = { navController.navigateToSignIn() }, ) noticeScreen() - surveyScreen() + surveyNavGraph( + navigateToSurvey = navController::navigateToSurvey , + navigateToSurveyAnswer = navController::navigateToSurveyAnswer, + ) surveyCheckScreen( navigateToManagement = { navController.navigateToManagement() }, ) 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 a11794e4..9d74961d 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 @@ -24,10 +24,10 @@ class ManagementRepositoryImpl @Inject constructor( override suspend fun postSurveyForm(surveyForm: SurveyForm): Result { return managementDataSource.postSurveyForm( surveyFormRequest = SurveyFormRequest( - userId = surveyForm.userId, + eventId = surveyForm.eventId, title = surveyForm.title, content = surveyForm.content, - surveyQuestion = surveyForm.surveyQuestion, + surveyQuestionList = surveyForm.surveyQuestionList, deadline = surveyForm.deadline.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), ), eventId = surveyForm.eventId, diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyRepository.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyRepository.kt index 716f5e15..73f3511f 100644 --- a/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyRepository.kt +++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyRepository.kt @@ -1,9 +1,14 @@ package com.wap.wapp.core.data.repository.survey import com.wap.wapp.core.model.survey.Survey +import com.wap.wapp.core.model.survey.SurveyForm interface SurveyRepository { suspend fun getSurveyList(): Result> suspend fun getSurvey(surveyId: String): Result + + suspend fun getSurveyFormList(): Result> + + suspend fun getSurveyForm(eventId: Int): Result } diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyRepositoryImpl.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyRepositoryImpl.kt index 5410d3b8..e0baa14b 100644 --- a/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyRepositoryImpl.kt +++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/survey/SurveyRepositoryImpl.kt @@ -1,6 +1,7 @@ package com.wap.wapp.core.data.repository.survey import com.wap.wapp.core.model.survey.Survey +import com.wap.wapp.core.model.survey.SurveyForm import com.wap.wapp.core.network.source.survey.SurveyDataSource import com.wap.wapp.core.network.source.user.UserDataSource import javax.inject.Inject @@ -45,4 +46,18 @@ class SurveyRepositoryImpl @Inject constructor( // TODO 도메인 모델 구현을 위한 익스텐션, notice DataSource 구현 후 소거 private fun String.toDomain(): String = this + + override suspend fun getSurveyFormList(): Result> { + return surveyDataSource.getSurveyFormList().mapCatching { surveyFormResponseList -> + surveyFormResponseList.map { surveyFormResponse -> + surveyFormResponse.toDomain() + } + } + } + + override suspend fun getSurveyForm(eventId: Int): Result { + return surveyDataSource.getSurveyForm(eventId).mapCatching { surveyFormResponse -> + surveyFormResponse.toDomain() + } + } } diff --git a/core/designsystem/src/main/java/com/wap/designsystem/component/TextField.kt b/core/designsystem/src/main/java/com/wap/designsystem/component/TextField.kt index d1aa5eb6..d389ebb4 100644 --- a/core/designsystem/src/main/java/com/wap/designsystem/component/TextField.kt +++ b/core/designsystem/src/main/java/com/wap/designsystem/component/TextField.kt @@ -1,11 +1,14 @@ package com.wap.designsystem.component import androidx.annotation.StringRes +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import com.wap.designsystem.WappTheme @Composable @@ -42,3 +45,33 @@ fun WappTextField( }, ) } + +@Composable +fun WappRoundedTextField( + value: String, + onValueChange: (String) -> Unit, + modifier: Modifier = Modifier, + @StringRes placeholder: Int, +) { + TextField( + value = value, + onValueChange = onValueChange, + modifier = modifier, + colors = TextFieldDefaults.colors( + focusedTextColor = WappTheme.colors.white, + unfocusedTextColor = WappTheme.colors.white, + focusedContainerColor = WappTheme.colors.black25, + unfocusedContainerColor = WappTheme.colors.black25, + focusedIndicatorColor = WappTheme.colors.black25, + unfocusedIndicatorColor = WappTheme.colors.black25, + cursorColor = WappTheme.colors.yellow34, + ), + placeholder = { + androidx.compose.material.Text( + text = stringResource(id = placeholder), + color = WappTheme.colors.gray82, + ) + }, + shape = RoundedCornerShape(10.dp), + ) +} diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/management/RegisterSurveyUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/management/RegisterSurveyUseCase.kt index 393f3aa8..4c996f84 100644 --- a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/management/RegisterSurveyUseCase.kt +++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/management/RegisterSurveyUseCase.kt @@ -1,7 +1,6 @@ package com.wap.wapp.core.domain.usecase.management import com.wap.wapp.core.data.repository.management.ManagementRepository -import com.wap.wapp.core.data.repository.user.UserRepository import com.wap.wapp.core.model.event.Event import com.wap.wapp.core.model.survey.SurveyForm import com.wap.wapp.core.model.survey.SurveyQuestion @@ -12,26 +11,22 @@ import javax.inject.Inject class RegisterSurveyUseCase @Inject constructor( private val managementRepository: ManagementRepository, - private val userRepository: UserRepository, ) { suspend operator fun invoke( event: Event, title: String, content: String, - surveyQuestion: List, + surveyQuestionList: List, deadlineDate: LocalDate, deadlineTime: LocalTime, ): Result { return runCatching { - val userId = userRepository.getUserId().getOrThrow() - managementRepository.postSurveyForm( SurveyForm( eventId = event.eventId, - userId = userId, title = title, content = content, - surveyQuestion = surveyQuestion, + surveyQuestionList = surveyQuestionList, deadline = LocalDateTime.of(deadlineDate, deadlineTime), ), ) diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetSurveyFormListUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetSurveyFormListUseCase.kt new file mode 100644 index 00000000..57ffb33d --- /dev/null +++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetSurveyFormListUseCase.kt @@ -0,0 +1,10 @@ +package com.wap.wapp.core.domain.usecase.survey + +import com.wap.wapp.core.data.repository.survey.SurveyRepository +import javax.inject.Inject + +class GetSurveyFormListUseCase @Inject constructor( + private val surveyRepository: SurveyRepository, +) { + suspend operator fun invoke() = surveyRepository.getSurveyFormList() +} diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetSurveyFormUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetSurveyFormUseCase.kt new file mode 100644 index 00000000..82616de8 --- /dev/null +++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetSurveyFormUseCase.kt @@ -0,0 +1,10 @@ +package com.wap.wapp.core.domain.usecase.survey + +import com.wap.wapp.core.data.repository.survey.SurveyRepository +import javax.inject.Inject + +class GetSurveyFormUseCase @Inject constructor( + private val surveyRepository: SurveyRepository, +) { + suspend operator fun invoke(eventId: Int) = surveyRepository.getSurveyForm(eventId) +} diff --git a/core/model/src/main/java/com/wap/wapp/core/model/survey/Rating.kt b/core/model/src/main/java/com/wap/wapp/core/model/survey/Rating.kt index e5c6549c..2a024b50 100644 --- a/core/model/src/main/java/com/wap/wapp/core/model/survey/Rating.kt +++ b/core/model/src/main/java/com/wap/wapp/core/model/survey/Rating.kt @@ -4,8 +4,19 @@ enum class Rating { GOOD, MEDIOCRE, BAD } -fun Rating.toNaturalLanguage(): String = when (this) { - Rating.GOOD -> { "좋음" } - Rating.MEDIOCRE -> { "보통" } - Rating.BAD -> { "나쁨" } +data class RatingDescription( + val title: String, + val content: String, +) + +fun Rating.toDescription(): RatingDescription = when (this) { + Rating.GOOD -> { + RatingDescription("좋음", "행사 진행이 완벽하고, \n행사 내용이 많았음.") + } + Rating.MEDIOCRE -> { + RatingDescription("보통", "행사 진행이 원활하고, \n행사 내용이 적당함.") + } + Rating.BAD -> { + RatingDescription("나쁨", "행사 진행이 아쉬웠고, \n행사 내용이 부족함.") + } } 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 7c631283..5e7f6524 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 @@ -5,9 +5,16 @@ import java.time.LocalDateTime // 운영진이 등록하는 설문 모델 data class SurveyForm( val eventId: Int, - val userId: String, val title: String, val content: String, - val surveyQuestion: List, + val surveyQuestionList: List, val deadline: LocalDateTime, -) +) { + constructor() : this( + -1, + "", + "", + emptyList(), + LocalDateTime.MIN, + ) +} diff --git a/core/network/src/main/java/com/wap/wapp/core/network/model/event/EventRequest.kt b/core/network/src/main/java/com/wap/wapp/core/network/model/event/EventRequest.kt index fe15f3d5..acedfd76 100644 --- a/core/network/src/main/java/com/wap/wapp/core/network/model/event/EventRequest.kt +++ b/core/network/src/main/java/com/wap/wapp/core/network/model/event/EventRequest.kt @@ -6,5 +6,5 @@ data class EventRequest( val location: String = "", val period: String = "", val time: String = "", - val event_id: Int = 0, + val eventId: Int = 0, ) diff --git a/core/network/src/main/java/com/wap/wapp/core/network/model/event/EventResponse.kt b/core/network/src/main/java/com/wap/wapp/core/network/model/event/EventResponse.kt index 84a166b1..3669d6fe 100644 --- a/core/network/src/main/java/com/wap/wapp/core/network/model/event/EventResponse.kt +++ b/core/network/src/main/java/com/wap/wapp/core/network/model/event/EventResponse.kt @@ -1,13 +1,12 @@ package com.wap.wapp.core.network.model.event -import com.google.firebase.firestore.PropertyName import com.wap.wapp.core.model.event.Event import java.time.LocalDate import java.time.format.DateTimeFormatter data class EventResponse( val content: String = "", - @PropertyName("event_id") val eventId: Int = 0, + val eventId: Int = 0, val location: String = "", val period: String = "", val title: String = "", diff --git a/core/network/src/main/java/com/wap/wapp/core/network/model/management/SurveyFormRequest.kt b/core/network/src/main/java/com/wap/wapp/core/network/model/management/SurveyFormRequest.kt index 50cbdd9f..71c6fab3 100644 --- a/core/network/src/main/java/com/wap/wapp/core/network/model/management/SurveyFormRequest.kt +++ b/core/network/src/main/java/com/wap/wapp/core/network/model/management/SurveyFormRequest.kt @@ -3,14 +3,14 @@ package com.wap.wapp.core.network.model.management import com.wap.wapp.core.model.survey.SurveyQuestion data class SurveyFormRequest( - val userId: String, + val eventId: Int, val title: String, val content: String, - val surveyQuestion: List, + val surveyQuestionList: List, val deadline: String, ) { constructor() : this( - "", + -1, "", "", emptyList(), diff --git a/core/network/src/main/java/com/wap/wapp/core/network/model/survey/SurveyFormResponse.kt b/core/network/src/main/java/com/wap/wapp/core/network/model/survey/SurveyFormResponse.kt new file mode 100644 index 00000000..1efe9df0 --- /dev/null +++ b/core/network/src/main/java/com/wap/wapp/core/network/model/survey/SurveyFormResponse.kt @@ -0,0 +1,31 @@ +package com.wap.wapp.core.network.model.survey + +import com.wap.wapp.core.model.survey.SurveyForm +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +data class SurveyFormResponse( + val eventId: Int, + val userId: String, + val title: String, + val content: String, + val surveyQuestionList: List, + val deadline: String, +) { + constructor() : this( + -1, + "", + "", + "", + emptyList(), + "", + ) + + fun toDomain(): SurveyForm = SurveyForm( + eventId = eventId, + title = title, + content = content, + surveyQuestionList = surveyQuestionList.map { it.toDomain() }, + deadline = LocalDateTime.parse(deadline, DateTimeFormatter.ISO_LOCAL_DATE_TIME), + ) +} diff --git a/core/network/src/main/java/com/wap/wapp/core/network/model/survey/SurveyQuestionResponse.kt b/core/network/src/main/java/com/wap/wapp/core/network/model/survey/SurveyQuestionResponse.kt new file mode 100644 index 00000000..00b1a3bf --- /dev/null +++ b/core/network/src/main/java/com/wap/wapp/core/network/model/survey/SurveyQuestionResponse.kt @@ -0,0 +1,17 @@ +package com.wap.wapp.core.network.model.survey + +import com.wap.wapp.core.model.survey.SurveyQuestion + +data class SurveyQuestionResponse( + val questionTitle: String, + val questionType: QuestionTypeResponse, +) { + constructor() : this( + "", + QuestionTypeResponse.SUBJECTIVE, + ) + fun toDomain() = SurveyQuestion( + questionTitle = questionTitle, + questionType = questionType.toDomain(), + ) +} diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyDataSource.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyDataSource.kt index 6b8b693f..c8f1bc10 100644 --- a/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyDataSource.kt +++ b/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyDataSource.kt @@ -1,9 +1,14 @@ package com.wap.wapp.core.network.source.survey +import com.wap.wapp.core.network.model.survey.SurveyFormResponse import com.wap.wapp.core.network.model.survey.SurveyResponse interface SurveyDataSource { suspend fun getSurveyList(): Result> suspend fun getSurvey(surveyId: String): Result + + suspend fun getSurveyFormList(): Result> + + suspend fun getSurveyForm(eventId: Int): Result } diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyDataSourceImpl.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyDataSourceImpl.kt index 67477b24..40e1cdaf 100644 --- a/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyDataSourceImpl.kt +++ b/core/network/src/main/java/com/wap/wapp/core/network/source/survey/SurveyDataSourceImpl.kt @@ -2,6 +2,8 @@ package com.wap.wapp.core.network.source.survey import com.google.firebase.firestore.FirebaseFirestore import com.wap.wapp.core.network.constant.SURVEY_COLLECTION +import com.wap.wapp.core.network.constant.SURVEY_FORM_COLLECTION +import com.wap.wapp.core.network.model.survey.SurveyFormResponse import com.wap.wapp.core.network.model.survey.SurveyResponse import com.wap.wapp.core.network.utils.await import javax.inject.Inject @@ -39,4 +41,35 @@ class SurveyDataSourceImpl @Inject constructor( checkNotNull(surveyResponse) } } + + override suspend fun getSurveyFormList(): Result> { + return runCatching { + val result: MutableList = mutableListOf() + + val task = firebaseFirestore.collection(SURVEY_FORM_COLLECTION) + .get() + .await() + + for (document in task.documents) { + val surveyFormResponse = document.toObject(SurveyFormResponse::class.java) + checkNotNull(surveyFormResponse) + + result.add(surveyFormResponse) + } + + result + } + } + + override suspend fun getSurveyForm(eventId: Int): Result { + return runCatching { + val result = firebaseFirestore.collection(SURVEY_FORM_COLLECTION) + .document(eventId.toString()) + .get() + .await() + + val surveyFormResponse = result.toObject(SurveyFormResponse::class.java) + checkNotNull(surveyFormResponse) + } + } } diff --git a/feature/management/src/main/java/com/wap/wapp/feature/management/ManagementScreen.kt b/feature/management/src/main/java/com/wap/wapp/feature/management/ManagementScreen.kt index 0030df6b..3422085b 100644 --- a/feature/management/src/main/java/com/wap/wapp/feature/management/ManagementScreen.kt +++ b/feature/management/src/main/java/com/wap/wapp/feature/management/ManagementScreen.kt @@ -43,6 +43,7 @@ internal fun ManagementRoute( ManagementScreen( showManageCodeDialog = { isShowDialog = true }, + viewModel = viewModel, navigateToEventRegistration = navigateToEventRegistration, navigateToSurveyRegistration = navigateToSurveyRegistration, onCardClicked = {}, @@ -66,7 +67,7 @@ private fun showToast(text: String, context: Context) { @Composable internal fun ManagementScreen( showManageCodeDialog: () -> Unit, - viewModel: ManagementViewModel = hiltViewModel(), + viewModel: ManagementViewModel, navigateToEventRegistration: () -> Unit, navigateToSurveyRegistration: () -> Unit, onCardClicked: (String) -> Unit, diff --git a/feature/management/src/main/java/com/wap/wapp/feature/management/check/SurveyCheckScreen.kt b/feature/management/src/main/java/com/wap/wapp/feature/management/check/SurveyCheckScreen.kt index 8e7bd33c..83c20c7b 100644 --- a/feature/management/src/main/java/com/wap/wapp/feature/management/check/SurveyCheckScreen.kt +++ b/feature/management/src/main/java/com/wap/wapp/feature/management/check/SurveyCheckScreen.kt @@ -46,7 +46,7 @@ import com.wap.wapp.core.designresource.R.drawable import com.wap.wapp.core.model.survey.QuestionType import com.wap.wapp.core.model.survey.Rating import com.wap.wapp.core.model.survey.SurveyAnswer -import com.wap.wapp.core.model.survey.toNaturalLanguage +import com.wap.wapp.core.model.survey.toDescription import com.wap.wapp.feature.management.R import kotlinx.coroutines.flow.collectLatest @@ -59,6 +59,7 @@ internal fun SurveyCheckRoute( val surveyUiState by viewModel.surveyUiState.collectAsStateWithLifecycle() SurveyCheckScreen( + viewModel = viewModel, surveyId = surveyId, surveyUiState = surveyUiState, onDoneButtonClicked = { navigateToManagement() }, @@ -69,7 +70,7 @@ internal fun SurveyCheckRoute( @OptIn(ExperimentalMaterial3Api::class) @Composable internal fun SurveyCheckScreen( - viewModel: SurveyCheckViewModel = hiltViewModel(), + viewModel: SurveyCheckViewModel, surveyId: String, surveyUiState: SurveyCheckViewModel.SurveyUiState, onDoneButtonClicked: () -> Unit, @@ -329,7 +330,7 @@ private fun ObjectiveAnswerIndicator( horizontalAlignment = Alignment.CenterHorizontally, ) { Text( - text = rating.toNaturalLanguage(), + text = rating.toDescription().title, style = WappTheme.typography.captionRegular, color = WappTheme.colors.white, ) diff --git a/feature/management/src/main/java/com/wap/wapp/feature/management/registration/survey/SurveyRegistrationScreen.kt b/feature/management/src/main/java/com/wap/wapp/feature/management/registration/survey/SurveyRegistrationScreen.kt index eca25d51..802e79ac 100644 --- a/feature/management/src/main/java/com/wap/wapp/feature/management/registration/survey/SurveyRegistrationScreen.kt +++ b/feature/management/src/main/java/com/wap/wapp/feature/management/registration/survey/SurveyRegistrationScreen.kt @@ -66,6 +66,7 @@ internal fun SurveyRegistrationRoute( time = time, date = date, onBackButtonClicked = { navigateToManagement() }, + viewModel = viewModel, registerSurveyForm = { navigateToManagement() }, @@ -86,7 +87,7 @@ internal fun SurveyRegistrationScreen( time: LocalTime, date: LocalDate, registerSurveyForm: () -> Unit, - viewModel: SurveyRegistrationViewModel = hiltViewModel(), + viewModel: SurveyRegistrationViewModel, onBackButtonClicked: () -> Unit, ) { val snackBarHostState = remember { SnackbarHostState() } diff --git a/feature/management/src/main/java/com/wap/wapp/feature/management/registration/survey/SurveyRegistrationViewModel.kt b/feature/management/src/main/java/com/wap/wapp/feature/management/registration/survey/SurveyRegistrationViewModel.kt index 12257726..0e80633c 100644 --- a/feature/management/src/main/java/com/wap/wapp/feature/management/registration/survey/SurveyRegistrationViewModel.kt +++ b/feature/management/src/main/java/com/wap/wapp/feature/management/registration/survey/SurveyRegistrationViewModel.kt @@ -99,7 +99,8 @@ class SurveyRegistrationViewModel @Inject constructor( onSuccess = { eventList -> _eventList.value = eventList }, - onFailure = { exception -> + onFailure = { throwable -> + _surveyRegistrationEvent.emit(SurveyRegistrationEvent.Failure(throwable)) }, ) } @@ -112,7 +113,7 @@ class SurveyRegistrationViewModel @Inject constructor( event = surveyEventSelection.value, title = _surveyTitle.value, content = _surveyContent.value, - surveyQuestion = _surveyQuestionList.value, + surveyQuestionList = _surveyQuestionList.value, deadlineDate = _surveyDateDeadline.value, deadlineTime = _surveyTimeDeadline.value, ).onSuccess { @@ -181,6 +182,7 @@ class SurveyRegistrationViewModel @Inject constructor( } private fun isValidSurveyQuestion() = _surveyQuestion.value.isNotBlank() + private fun isNotValidEventSelection() = _surveyEventSelection.value.eventId == EVENT_SELECTION_INIT.eventId diff --git a/feature/management/src/main/java/com/wap/wapp/feature/management/registration/survey/question/SurveyQuestionContent.kt b/feature/management/src/main/java/com/wap/wapp/feature/management/registration/survey/question/SurveyQuestionContent.kt index 777a1d15..89db3432 100644 --- a/feature/management/src/main/java/com/wap/wapp/feature/management/registration/survey/question/SurveyQuestionContent.kt +++ b/feature/management/src/main/java/com/wap/wapp/feature/management/registration/survey/question/SurveyQuestionContent.kt @@ -219,7 +219,7 @@ private fun SurveyQuestionTypeCard( color = WappTheme.colors.white, style = WappTheme.typography.contentBold, textAlign = TextAlign.Center, - ) + ) Text( text = content, diff --git a/feature/survey/build.gradle.kts b/feature/survey/build.gradle.kts index 3c45e76c..6f7d13e8 100644 --- a/feature/survey/build.gradle.kts +++ b/feature/survey/build.gradle.kts @@ -24,6 +24,9 @@ android { dependencies { implementation(project(":core:designresource")) implementation(project(":core:designsystem")) + implementation(project(":core:domain")) + implementation(project(":core:model")) + implementation(project(":core:common")) implementation(libs.bundles.androidx) implementation(libs.material) 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 0ebf5579..87986be4 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 @@ -1,18 +1,167 @@ package com.wap.wapp.feature.survey +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row 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.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +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.remember 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.WappTitle +import com.wap.wapp.core.commmon.extensions.toSupportingText +import com.wap.wapp.core.commmon.util.DateUtil.yyyyMMddFormatter +import com.wap.wapp.core.model.survey.SurveyForm +import kotlinx.coroutines.flow.collectLatest +import java.time.Duration +import java.time.LocalDateTime @Composable -internal fun SurveyRoute() { - SurveyScreen() +internal fun SurveyScreen( + viewModel: SurveyViewModel, + selectedSurveyForm: (Int) -> Unit, +) { + val surveyFormListUiState = viewModel.surveyFormListUiState.collectAsState().value + val snackBarHostState = remember { SnackbarHostState() } + + LaunchedEffect(true) { + viewModel.surveyEvent.collectLatest { + when (it) { + is SurveyViewModel.SurveyUiEvent.Failure -> { + snackBarHostState.showSnackbar(it.throwable.toSupportingText()) + } + } + } + } + + when (surveyFormListUiState) { + is SurveyViewModel.SurveyFormListUiState.Init -> { } + is SurveyViewModel.SurveyFormListUiState.Success -> { + SurveyContent( + surveyFormList = surveyFormListUiState.surveyFormList, + snackBarHostState = snackBarHostState, + selectedSurveyForm = selectedSurveyForm, + ) + } + } } +@OptIn(ExperimentalMaterial3Api::class) @Composable -internal fun SurveyScreen() { - Column( +private fun SurveyContent( + surveyFormList: List, + snackBarHostState: SnackbarHostState, + selectedSurveyForm: (Int) -> Unit, +) { + Scaffold( modifier = Modifier.fillMaxSize(), - ) {} + containerColor = WappTheme.colors.backgroundBlack, + snackbarHost = { SnackbarHost(snackBarHostState) }, + ) { paddingValues -> + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier + .padding(paddingValues) + .padding(vertical = 16.dp, horizontal = 8.dp), + ) { + WappTitle( + title = stringResource(R.string.survey_title), + content = stringResource(R.string.survey_content), + ) + + LazyColumn( + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + items(surveyFormList) { surveyForm -> + SurveyFormItemCard( + surveyForm = surveyForm, + selectedSurveyForm = selectedSurveyForm, + ) + } + } + } + } +} + +@Composable +private fun SurveyFormItemCard( + surveyForm: SurveyForm, + selectedSurveyForm: (Int) -> Unit, +) { + Card( + colors = CardDefaults.cardColors( + containerColor = WappTheme.colors.black25, + ), + modifier = Modifier + .fillMaxWidth() + .clickable { selectedSurveyForm(surveyForm.eventId) }, + ) { + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier + .padding(16.dp), + ) { + Row( + modifier = Modifier.fillMaxWidth(), + ) { + Text( + text = surveyForm.title, + color = WappTheme.colors.white, + style = WappTheme.typography.titleBold, + ) + Text( + text = calculateDeadline(surveyForm.deadline), + color = WappTheme.colors.yellow34, + style = WappTheme.typography.captionMedium, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.End, + ) + } + + Text( + text = surveyForm.content, + color = WappTheme.colors.grayBD, + style = WappTheme.typography.contentMedium, + ) + } + } +} + +private fun calculateDeadline(deadline: LocalDateTime): 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(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 new file mode 100644 index 00000000..562d2629 --- /dev/null +++ b/feature/survey/src/main/java/com/wap/wapp/feature/survey/SurveyViewModel.kt @@ -0,0 +1,50 @@ +package com.wap.wapp.feature.survey + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.wap.wapp.core.domain.usecase.survey.GetSurveyFormListUseCase +import com.wap.wapp.core.model.survey.SurveyForm +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class SurveyViewModel @Inject constructor( + private val getSurveyFormListUseCase: GetSurveyFormListUseCase, +) : ViewModel() { + private val _surveyFormListUiState: MutableStateFlow = + MutableStateFlow(SurveyFormListUiState.Init) + val surveyFormListUiState = _surveyFormListUiState.asStateFlow() + + private val _surveyEvent: MutableSharedFlow = MutableSharedFlow() + val surveyEvent = _surveyEvent.asSharedFlow() + + init { + getSurveyFormList() + } + + private fun getSurveyFormList() { + viewModelScope.launch { + getSurveyFormListUseCase() + .onSuccess { surveyFormList -> + _surveyFormListUiState.value = SurveyFormListUiState.Success(surveyFormList) + } + .onFailure { throwable -> + _surveyEvent.emit(SurveyUiEvent.Failure(throwable)) + } + } + } + + sealed class SurveyFormListUiState { + data object Init : SurveyFormListUiState() + data class Success(val surveyFormList: List) : SurveyFormListUiState() + } + + sealed class SurveyUiEvent { + data class Failure(val throwable: Throwable) : SurveyUiEvent() + } +} diff --git a/feature/survey/src/main/java/com/wap/wapp/feature/survey/answer/ObjectiveSurveyForm.kt b/feature/survey/src/main/java/com/wap/wapp/feature/survey/answer/ObjectiveSurveyForm.kt new file mode 100644 index 00000000..bfca8e3a --- /dev/null +++ b/feature/survey/src/main/java/com/wap/wapp/feature/survey/answer/ObjectiveSurveyForm.kt @@ -0,0 +1,109 @@ +package com.wap.wapp.feature.survey.answer + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.wap.designsystem.WappTheme +import com.wap.wapp.core.model.survey.Rating +import com.wap.wapp.core.model.survey.toDescription + +@Composable +internal fun ObjectiveSurveyForm( + questionTitle: String, + answer: Rating, + onAnswerSelected: (Rating) -> Unit, +) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(32.dp), + ) { + Text( + text = questionTitle, + style = WappTheme.typography.titleRegular, + color = WappTheme.colors.white, + fontSize = 22.sp, + ) + + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + ObjectiveAnswerCard( + rating = Rating.GOOD, + selectedRating = answer, + onCardSelected = onAnswerSelected, + ) + + ObjectiveAnswerCard( + rating = Rating.MEDIOCRE, + selectedRating = answer, + onCardSelected = onAnswerSelected, + ) + + ObjectiveAnswerCard( + rating = Rating.BAD, + selectedRating = answer, + onCardSelected = onAnswerSelected, + ) + } + } +} + +@Composable +private fun ObjectiveAnswerCard( + rating: Rating, + selectedRating: Rating, + onCardSelected: (Rating) -> Unit, +) { + Card( + colors = CardDefaults.cardColors(containerColor = WappTheme.colors.black25), + modifier = Modifier + .fillMaxWidth() + .clickable { onCardSelected(rating) }, + border = BorderStroke( + color = if (rating == selectedRating) { + WappTheme.colors.yellow34 + } else { + WappTheme.colors.black25 + }, + width = 1.dp, + ), + ) { + Row( + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .padding(8.dp) + .fillMaxWidth(), + ) { + Text( + modifier = Modifier.weight(1f), + text = rating.toDescription().title, + color = WappTheme.colors.white, + style = WappTheme.typography.contentBold, + textAlign = TextAlign.Center, + ) + + Text( + text = rating.toDescription().content, + color = WappTheme.colors.white, + style = WappTheme.typography.labelRegular, + modifier = Modifier.weight(4f), + textAlign = TextAlign.End, + ) + } + } +} diff --git a/feature/survey/src/main/java/com/wap/wapp/feature/survey/answer/SubjectiveSurveyForm.kt b/feature/survey/src/main/java/com/wap/wapp/feature/survey/answer/SubjectiveSurveyForm.kt new file mode 100644 index 00000000..1860cfc5 --- /dev/null +++ b/feature/survey/src/main/java/com/wap/wapp/feature/survey/answer/SubjectiveSurveyForm.kt @@ -0,0 +1,76 @@ +package com.wap.wapp.feature.survey.answer + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.wap.designsystem.WappTheme +import com.wap.designsystem.component.WappRoundedTextField +import com.wap.wapp.feature.survey.R + +@Composable +internal fun SubjectiveSurveyForm( + questionTitle: String, + answer: String, + onAnswerChanged: (String) -> Unit, +) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(32.dp), + ) { + Text( + text = questionTitle, + style = WappTheme.typography.titleRegular, + color = WappTheme.colors.white, + fontSize = 22.sp, + ) + + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + WappRoundedTextField( + value = answer, + onValueChange = onAnswerChanged, + placeholder = R.string.subjective_answer_hint, + modifier = Modifier + .height(200.dp) + .fillMaxWidth(), + ) + + val textCount = answer.length + TextCounter(textCount = textCount) + } + } +} + +@Composable +private fun TextCounter( + textCount: Int, +) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = textCount.toString(), + color = WappTheme.colors.yellow34, + style = WappTheme.typography.labelMedium, + ) + + Text( + text = stringResource(R.string.text_counter_content), + color = WappTheme.colors.white, + style = WappTheme.typography.labelMedium, + ) + } +} diff --git a/feature/survey/src/main/java/com/wap/wapp/feature/survey/answer/SurveyAnswerScreen.kt b/feature/survey/src/main/java/com/wap/wapp/feature/survey/answer/SurveyAnswerScreen.kt new file mode 100644 index 00000000..acaadf29 --- /dev/null +++ b/feature/survey/src/main/java/com/wap/wapp/feature/survey/answer/SurveyAnswerScreen.kt @@ -0,0 +1,203 @@ +package com.wap.wapp.feature.survey.answer + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +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.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +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.WappButton +import com.wap.wapp.core.commmon.extensions.toSupportingText +import com.wap.wapp.core.designresource.R.drawable +import com.wap.wapp.core.model.survey.QuestionType +import com.wap.wapp.core.model.survey.Rating +import com.wap.wapp.core.model.survey.SurveyQuestion +import com.wap.wapp.feature.survey.R +import com.wap.wapp.feature.survey.answer.SurveyAnswerViewModel.SurveyFormUiState +import kotlinx.coroutines.flow.collectLatest + +@Composable +internal fun SurveyAnswerScreen( + viewModel: SurveyAnswerViewModel, + onSubmitButtonClicked: () -> Unit, + onBackButtonClicked: () -> Unit, + eventId: Int, +) { + val surveyFormUiState = viewModel.surveyFormUiState.collectAsStateWithLifecycle().value + val questionNumber = viewModel.questionNumber.collectAsStateWithLifecycle().value + val subjectiveAnswer = viewModel.subjectiveAnswer.collectAsStateWithLifecycle().value + val objectiveAnswer = viewModel.objectiveAnswer.collectAsStateWithLifecycle().value + val snackBarHostState = remember { SnackbarHostState() } + + LaunchedEffect(true) { + viewModel.getSurveyForm(eventId) + + viewModel.surveyAnswerEvent.collectLatest { + when (it) { + is SurveyAnswerViewModel.SurveyAnswerUiEvent.SubmitSuccess -> { + onSubmitButtonClicked() + } + + is SurveyAnswerViewModel.SurveyAnswerUiEvent.Failure -> { + snackBarHostState.showSnackbar(it.throwable.toSupportingText()) + } + } + } + } + + Scaffold( + topBar = { + SurveyAnswerTopBar(onBackButtonClicked = onBackButtonClicked) + }, + snackbarHost = { SnackbarHost(snackBarHostState) }, + containerColor = WappTheme.colors.backgroundBlack, + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .padding(horizontal = 16.dp) + .padding(bottom = 16.dp) + .fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(32.dp), + ) { + when (surveyFormUiState) { + is SurveyFormUiState.Init -> {} + + is SurveyFormUiState.Success -> { + val questionList = surveyFormUiState.surveyForm.surveyQuestionList + val surveyQuestion = questionList[questionNumber] + val lastQuestionNumber = questionList.lastIndex + + SurveyAnswerForm( + surveyQuestion = surveyQuestion, + questionNumber = questionNumber, + lastQuestionNumber = lastQuestionNumber, + subjectiveAnswer = subjectiveAnswer, + objectiveAnswer = objectiveAnswer, + onSubjectiveAnswerChanged = viewModel::setSubjectiveAnswer, + onObjectiveAnswerSelected = viewModel::setObjectiveAnswer, + onNextButtonClicked = { + viewModel.addSurveyAnswer() + viewModel.setNextQuestion() + }, + ) + } + } + } + } +} + +@Composable +private fun SurveyAnswerForm( + surveyQuestion: SurveyQuestion, + questionNumber: Int, + lastQuestionNumber: Int, + subjectiveAnswer: String, + objectiveAnswer: Rating, + onSubjectiveAnswerChanged: (String) -> Unit, + onObjectiveAnswerSelected: (Rating) -> Unit, + onNextButtonClicked: () -> Unit, +) { + Column( + verticalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxSize(), + ) { + SurveyAnswerStateIndicator( + index = questionNumber + 1, + size = lastQuestionNumber + 1, + ) + + when (surveyQuestion.questionType) { + QuestionType.SUBJECTIVE -> { + SubjectiveSurveyForm( + questionTitle = surveyQuestion.questionTitle, + answer = subjectiveAnswer, + onAnswerChanged = onSubjectiveAnswerChanged, + ) + } + + QuestionType.OBJECTIVE -> { + ObjectiveSurveyForm( + questionTitle = surveyQuestion.questionTitle, + answer = objectiveAnswer, + onAnswerSelected = onObjectiveAnswerSelected, + ) + } + } + + val isLastQuestion = questionNumber == lastQuestionNumber // 마지막 응답일 경우, 완료로 변경 + SurveyAnswerButton( + isLastQuestion = isLastQuestion, + onButtonClicked = onNextButtonClicked, + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun SurveyAnswerTopBar( + onBackButtonClicked: () -> Unit, +) { + CenterAlignedTopAppBar( + title = { + Text( + text = stringResource(R.string.survey_answer), + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + style = WappTheme.typography.contentBold, + color = WappTheme.colors.white, + ) + }, + navigationIcon = { + Icon( + painter = painterResource(id = drawable.ic_back), + contentDescription = stringResource( + id = com.wap.wapp.core.designsystem.R.string.back_button, + ), + tint = WappTheme.colors.white, + modifier = Modifier + .padding(start = 16.dp) + .clickable { onBackButtonClicked() }, + ) + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = WappTheme.colors.backgroundBlack, + ), + ) +} + +@Composable +private fun SurveyAnswerButton( + isLastQuestion: Boolean, + onButtonClicked: () -> Unit, +) { + if (isLastQuestion) { + WappButton( + textRes = R.string.submit, + onClick = { onButtonClicked() }, + ) + } else { + WappButton( + textRes = R.string.next, + onClick = { onButtonClicked() }, + ) + } +} diff --git a/feature/survey/src/main/java/com/wap/wapp/feature/survey/answer/SurveyAnswerStateIndicator.kt b/feature/survey/src/main/java/com/wap/wapp/feature/survey/answer/SurveyAnswerStateIndicator.kt new file mode 100644 index 00000000..c341a737 --- /dev/null +++ b/feature/survey/src/main/java/com/wap/wapp/feature/survey/answer/SurveyAnswerStateIndicator.kt @@ -0,0 +1,55 @@ +package com.wap.wapp.feature.survey.answer + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.LinearProgressIndicator +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.StrokeCap +import androidx.compose.ui.unit.dp +import com.wap.designsystem.WappTheme + +// TODO 공용 모듈로 옮길 예정 +@Composable +internal fun SurveyAnswerStateIndicator(index: Int, size: Int) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + SurveyAnswerStateProgressBar(index = index, size = size) + SurveyAnswerStateText(index = index, size = size) + } +} + +@Composable +private fun SurveyAnswerStateText(index: Int, size: Int) { + Row { + Text( + text = index.toString(), + style = WappTheme.typography.contentMedium, + color = WappTheme.colors.yellow34, + ) + Text( + text = "/ $size", + style = WappTheme.typography.contentMedium, + color = WappTheme.colors.white, + ) + } +} + +@Composable +private fun SurveyAnswerStateProgressBar(index: Int, size: Int) { + LinearProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .height(10.dp), + color = WappTheme.colors.yellow34, + progress = index.toFloat() / size.toFloat(), + strokeCap = StrokeCap.Round, + ) +} diff --git a/feature/survey/src/main/java/com/wap/wapp/feature/survey/answer/SurveyAnswerViewModel.kt b/feature/survey/src/main/java/com/wap/wapp/feature/survey/answer/SurveyAnswerViewModel.kt new file mode 100644 index 00000000..0ff9897b --- /dev/null +++ b/feature/survey/src/main/java/com/wap/wapp/feature/survey/answer/SurveyAnswerViewModel.kt @@ -0,0 +1,123 @@ +package com.wap.wapp.feature.survey.answer + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.wap.wapp.core.domain.usecase.survey.GetSurveyFormUseCase +import com.wap.wapp.core.model.survey.QuestionType +import com.wap.wapp.core.model.survey.Rating +import com.wap.wapp.core.model.survey.SurveyAnswer +import com.wap.wapp.core.model.survey.SurveyForm +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class SurveyAnswerViewModel @Inject constructor( + private val getSurveyFormUseCase: GetSurveyFormUseCase, +) : ViewModel() { + private val _surveyFormUiState: MutableStateFlow = + MutableStateFlow(SurveyFormUiState.Init) + val surveyFormUiState = _surveyFormUiState.asStateFlow() + + private val _surveyForm: MutableStateFlow = MutableStateFlow(SurveyForm()) + + private val _surveyAnswerEvent: MutableSharedFlow = MutableSharedFlow() + val surveyAnswerEvent = _surveyAnswerEvent.asSharedFlow() + + private val _questionNumber: MutableStateFlow = MutableStateFlow(0) + val questionNumber = _questionNumber.asStateFlow() + + // 설문 응답 리스트 + private val surveyAnswerList: MutableStateFlow> = + MutableStateFlow(mutableListOf()) + + private val _subjectiveAnswer: MutableStateFlow = MutableStateFlow("") + val subjectiveAnswer = _subjectiveAnswer.asStateFlow() + + private val _objectiveAnswer: MutableStateFlow = MutableStateFlow(Rating.GOOD) + val objectiveAnswer = _objectiveAnswer.asStateFlow() + + fun getSurveyForm(eventId: Int) { + viewModelScope.launch { + getSurveyFormUseCase(eventId = eventId) + .onSuccess { surveyForm -> + _surveyFormUiState.value = SurveyFormUiState.Success(surveyForm) + _surveyForm.value = surveyForm + } + .onFailure { throwable -> + _surveyAnswerEvent.emit(SurveyAnswerUiEvent.Failure(throwable)) + } + } + } + + fun addSurveyAnswer() { + val questionNumber = _questionNumber.value + val surveyQuestion = _surveyForm.value.surveyQuestionList[questionNumber] + + when (surveyQuestion.questionType) { + QuestionType.SUBJECTIVE -> { + surveyAnswerList.value.add( // 현재 질문 답변 리스트에 저장 + SurveyAnswer( + questionType = surveyQuestion.questionType, + questionTitle = surveyQuestion.questionTitle, + questionAnswer = _subjectiveAnswer.value, + ), + ) + clearSubjectiveAnswer() // 질문 상태 초기화 + } + + QuestionType.OBJECTIVE -> { + surveyAnswerList.value.add( + SurveyAnswer( + questionType = surveyQuestion.questionType, + questionTitle = surveyQuestion.questionTitle, + questionAnswer = _objectiveAnswer.value.toString(), + ), + ) + clearObjectiveAnswer() + } + } + } + + fun setNextQuestion() { + val currentQuestionNumber = _questionNumber.value + val lastQuestionNumber = surveyAnswerList.value.size + + if(currentQuestionNumber == lastQuestionNumber) { // 마지막 질문일 경우 제출 + submitSurvey() + return + } + + addQuestionNumber() // 다음 질문 넘기기 + } + + private fun submitSurvey() { + viewModelScope.launch { + + } + } + + fun setSubjectiveAnswer(answer: String) { _subjectiveAnswer.value = answer } + + fun setObjectiveAnswer(answer: Rating) { _objectiveAnswer.value = answer } + + private fun addQuestionNumber() { _questionNumber.value += 1 } + + private fun clearSubjectiveAnswer() { _subjectiveAnswer.value = "" } + + private fun clearObjectiveAnswer() { _objectiveAnswer.value = Rating.GOOD } + + sealed class SurveyFormUiState { + data object Init : SurveyFormUiState() + data class Success(val surveyForm: SurveyForm) : SurveyFormUiState() + } + + sealed class SurveyAnswerUiEvent { + data class Failure(val throwable: Throwable) : SurveyAnswerUiEvent() + data object SubmitSuccess : SurveyAnswerUiEvent() + } +} 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 3cc613fd..24afd078 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 @@ -1,20 +1,57 @@ package com.wap.wapp.feature.survey.navigation +import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions +import androidx.navigation.NavType import androidx.navigation.compose.composable +import androidx.navigation.navArgument import androidx.navigation.navOptions -import com.wap.wapp.feature.survey.SurveyRoute - -const val surveyNavigationRoute = "survey_route" +import com.wap.wapp.feature.survey.SurveyScreen +import com.wap.wapp.feature.survey.answer.SurveyAnswerScreen fun NavController.navigateToSurvey(navOptions: NavOptions? = navOptions {}) { - this.navigate(surveyNavigationRoute, navOptions) + this.navigate(SurveyRoute.route, navOptions) +} + +fun NavController.navigateToSurveyAnswer(eventId: Int, navOptions: NavOptions? = navOptions {}) { + navigate(SurveyRoute.answerRoute(eventId.toString()), navOptions) } -fun NavGraphBuilder.surveyScreen() { - composable(route = surveyNavigationRoute) { - SurveyRoute() +fun NavGraphBuilder.surveyNavGraph( + navigateToSurveyAnswer: (Int) -> Unit, + navigateToSurvey: () -> Unit, +) { + composable(route = SurveyRoute.route) { + SurveyScreen( + viewModel = hiltViewModel(), + selectedSurveyForm = { eventId -> + navigateToSurveyAnswer(eventId) + }, + ) + } + + composable( + route = SurveyRoute.answerRoute("{id}"), + arguments = listOf( + navArgument("id") { + type = NavType.StringType + }, + ), + ) { navBackStackEntry -> + val eventId = navBackStackEntry.arguments?.getString("id") ?: "" + SurveyAnswerScreen( + viewModel = hiltViewModel(), + onSubmitButtonClicked = navigateToSurvey, + onBackButtonClicked = navigateToSurvey, + eventId = eventId.toInt(), + ) } } + +object SurveyRoute { + const val route: String = "survey" + + fun answerRoute(eventId: String): String = "$route/$eventId" +} diff --git a/feature/survey/src/main/res/values/strings.xml b/feature/survey/src/main/res/values/strings.xml index 6048840e..8ac185b5 100644 --- a/feature/survey/src/main/res/values/strings.xml +++ b/feature/survey/src/main/res/values/strings.xml @@ -1,4 +1,12 @@ Hello blank fragment - \ No newline at end of file + 설문 + 제출 + 다음 + 질문에 대한 응답을 작성하세요 + 설문 작성 + 참가한 행사에 대해 설문에 응답하세요! + 설문 응답 + /10자 이상 +