diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index 867745c29..0c0c33838 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -3,20 +3,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 80ba7518b..6b1280927 100644 --- a/app/src/main/java/com/wap/wapp/navigation/WappNavHost.kt +++ b/app/src/main/java/com/wap/wapp/navigation/WappNavHost.kt @@ -9,6 +9,7 @@ import androidx.navigation.navOptions import com.wap.designsystem.WappTheme import com.wap.wapp.core.domain.usecase.auth.SignInUseCase import com.wap.wapp.feature.auth.signin.navigation.navigateToSignIn +import com.wap.wapp.feature.auth.signin.navigation.signInNavigationRoute import com.wap.wapp.feature.auth.signin.navigation.signInScreen import com.wap.wapp.feature.auth.signup.navigation.navigateToSignUp import com.wap.wapp.feature.auth.signup.navigation.signUpScreen @@ -17,9 +18,9 @@ import com.wap.wapp.feature.management.event.navigation.navigateToEventEdit import com.wap.wapp.feature.management.event.navigation.navigateToEventRegistration import com.wap.wapp.feature.management.navigation.managementScreen import com.wap.wapp.feature.management.navigation.navigateToManagement +import com.wap.wapp.feature.management.survey.navigation.managementSurveyNavGraph import com.wap.wapp.feature.management.survey.navigation.navigateToSurveyFormEdit import com.wap.wapp.feature.management.survey.navigation.navigateToSurveyFormRegistration -import com.wap.wapp.feature.management.survey.navigation.managementSurveyNavGraph import com.wap.wapp.feature.notice.navigation.navigateToNotice import com.wap.wapp.feature.notice.navigation.noticeScreen import com.wap.wapp.feature.profile.navigation.navigateToProfile @@ -93,7 +94,7 @@ fun WappNavHost( ) profileScreen( navigateToProfileSetting = navController::navigateToProfileSetting, - navigateToSignInScreen = { + navigateToSignIn = { navController.navigateToSignIn( navOptions { popUpTo(profileNavigationRoute) @@ -102,6 +103,13 @@ fun WappNavHost( }, ) profileSettingScreen( + navigateToSignIn = { + navController.navigateToSignIn( + navOptions { + popUpTo(signInNavigationRoute) { inclusive = true } + }, + ) + }, navigateToProfile = { navController.navigateToProfile( navOptions { diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/event/EventRepository.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/event/EventRepository.kt index 77e299eb0..a052f9131 100644 --- a/core/data/src/main/java/com/wap/wapp/core/data/repository/event/EventRepository.kt +++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/event/EventRepository.kt @@ -5,10 +5,14 @@ import java.time.LocalDate import java.time.LocalDateTime interface EventRepository { - suspend fun getEventList(): Result> - suspend fun getMonthEventList(date: LocalDate): Result> + suspend fun getDateEventList(date: LocalDate): Result> + + suspend fun getEventListFromDate(date: LocalDate): Result> + + suspend fun getEventList(): Result> + suspend fun getEvent(eventId: String): Result suspend fun postEvent( diff --git a/core/data/src/main/java/com/wap/wapp/core/data/repository/event/EventRepositoryImpl.kt b/core/data/src/main/java/com/wap/wapp/core/data/repository/event/EventRepositoryImpl.kt index 454c5a67e..cecbfd55d 100644 --- a/core/data/src/main/java/com/wap/wapp/core/data/repository/event/EventRepositoryImpl.kt +++ b/core/data/src/main/java/com/wap/wapp/core/data/repository/event/EventRepositoryImpl.kt @@ -10,18 +10,32 @@ import javax.inject.Inject class EventRepositoryImpl @Inject constructor( private val eventDataSource: EventDataSource, ) : EventRepository { + override suspend fun getMonthEventList(date: LocalDate): Result> = + eventDataSource.getMonthEventList(date).mapCatching { eventResponses -> + eventResponses.map { eventResponse -> + eventResponse.toDomain() + }.sortedBy { it.startDateTime } + } + override suspend fun getEventList(): Result> = eventDataSource.getEventList().mapCatching { eventResponses -> eventResponses.map { eventResponse -> eventResponse.toDomain() - } + }.sortedBy { it.startDateTime } } - override suspend fun getMonthEventList(date: LocalDate): Result> = - eventDataSource.getMonthEventList(date).mapCatching { eventResponses -> + override suspend fun getDateEventList(date: LocalDate): Result> = + eventDataSource.getDateEventList(date).mapCatching { eventResponses -> + eventResponses.map { eventResponse -> + eventResponse.toDomain() + }.sortedBy { it.startDateTime } + } + + override suspend fun getEventListFromDate(date: LocalDate): Result> = + eventDataSource.getEventListFromDate(date).mapCatching { eventResponses -> eventResponses.map { eventResponse -> eventResponse.toDomain() - } + }.sortedBy { it.startDateTime } } override suspend fun getEvent(eventId: String): Result = 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 46fbfd7ce..79f635b8e 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 @@ -7,6 +7,8 @@ import java.time.LocalDateTime interface SurveyRepository { suspend fun getSurveyList(): Result> + suspend fun getUserRespondedSurveyList(userId: String): Result> + suspend fun getSurvey(surveyId: String): Result suspend fun postSurvey( 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 ad5825839..be67814c7 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 @@ -28,6 +28,22 @@ class SurveyRepositoryImpl @Inject constructor( } } + override suspend fun getUserRespondedSurveyList(userId: String): Result> = + surveyDataSource.getUserRespondedSurveyList(userId).mapCatching { surveyList -> + surveyList.map { surveyResponse -> + userDataSource.getUserProfile(userId = surveyResponse.userId) + .mapCatching { userProfileResponse -> + val userName = userProfileResponse.toDomain().userName + + noticeNameResponse.mapCatching { noticeNameResponse -> + val eventName = noticeNameResponse.toDomain() + + surveyResponse.toDomain(userName = userName, eventName = eventName) + }.getOrThrow() + }.getOrThrow() + } + } + override suspend fun getSurvey(surveyId: String): Result = surveyDataSource.getSurvey(surveyId).mapCatching { surveyResponse -> userDataSource.getUserProfile(userId = surveyResponse.userId) diff --git a/core/designsystem/src/main/java/com/wap/designsystem/component/MainTopBar.kt b/core/designsystem/src/main/java/com/wap/designsystem/component/MainTopBar.kt index ad19a3282..31cac16e4 100644 --- a/core/designsystem/src/main/java/com/wap/designsystem/component/MainTopBar.kt +++ b/core/designsystem/src/main/java/com/wap/designsystem/component/MainTopBar.kt @@ -92,7 +92,6 @@ fun WappMainTopBarWithButton() { titleRes = R.string.notice, contentRes = R.string.notice, showSettingButton = true, - onClickSettingButton = {}, ) } } diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetDateEventListUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetDateEventListUseCase.kt new file mode 100644 index 000000000..f3406db4f --- /dev/null +++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetDateEventListUseCase.kt @@ -0,0 +1,13 @@ +package com.wap.wapp.core.domain.usecase.event + +import com.wap.wapp.core.data.repository.event.EventRepository +import com.wap.wapp.core.model.event.Event +import java.time.LocalDate +import javax.inject.Inject + +class GetDateEventListUseCase @Inject constructor( + private val eventRepository: EventRepository, +) { + suspend operator fun invoke(date: LocalDate): Result> = + eventRepository.getMonthEventList(date) +} diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetRecentEventListUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetRecentEventListUseCase.kt new file mode 100644 index 000000000..7cd5445d3 --- /dev/null +++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/event/GetRecentEventListUseCase.kt @@ -0,0 +1,19 @@ +package com.wap.wapp.core.domain.usecase.event + +import com.wap.wapp.core.data.repository.event.EventRepository +import com.wap.wapp.core.model.event.Event +import java.time.LocalDate +import java.time.ZoneId +import java.time.temporal.ChronoUnit +import javax.inject.Inject + +class GetRecentEventListUseCase @Inject constructor( + private val eventRepository: EventRepository, +) { + suspend operator fun invoke(registrationDate: LocalDate): Result> { + val currentDate = LocalDate.now(ZoneId.of("Asia/Seoul")) + val minimumDate = currentDate.minus(3, ChronoUnit.MONTHS) + val selectedDate = maxOf(registrationDate, minimumDate) + return eventRepository.getEventListFromDate(selectedDate) + } +} diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetUserRespondedSurveyListUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetUserRespondedSurveyListUseCase.kt new file mode 100644 index 000000000..d4697af5f --- /dev/null +++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/survey/GetUserRespondedSurveyListUseCase.kt @@ -0,0 +1,12 @@ +package com.wap.wapp.core.domain.usecase.survey + +import com.wap.wapp.core.data.repository.survey.SurveyRepository +import com.wap.wapp.core.model.survey.Survey +import javax.inject.Inject + +class GetUserRespondedSurveyListUseCase @Inject constructor( + private val surveyRepository: SurveyRepository, +) { + suspend operator fun invoke(userId: String): Result> = + surveyRepository.getUserRespondedSurveyList(userId) +} diff --git a/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/user/GetUserProfileUseCase.kt b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/user/GetUserProfileUseCase.kt new file mode 100644 index 000000000..3df73744c --- /dev/null +++ b/core/domain/src/main/java/com/wap/wapp/core/domain/usecase/user/GetUserProfileUseCase.kt @@ -0,0 +1,16 @@ +package com.wap.wapp.core.domain.usecase.user + +import com.wap.wapp.core.data.repository.user.UserRepository +import com.wap.wapp.core.model.user.UserProfile +import javax.inject.Inject + +class GetUserProfileUseCase @Inject constructor(private val userRepository: UserRepository) { + suspend operator fun invoke(): Result = runCatching { + val userId = userRepository.getUserId().getOrThrow() + + userRepository.getUserProfile(userId).fold( + onSuccess = { userProfile -> userProfile }, + onFailure = { throw it }, + ) + } +} diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/event/EventDataSource.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/event/EventDataSource.kt index f0cc3efb6..95a36997a 100644 --- a/core/network/src/main/java/com/wap/wapp/core/network/source/event/EventDataSource.kt +++ b/core/network/src/main/java/com/wap/wapp/core/network/source/event/EventDataSource.kt @@ -4,9 +4,13 @@ import com.wap.wapp.core.network.model.event.EventResponse import java.time.LocalDate interface EventDataSource { + suspend fun getMonthEventList(date: LocalDate): Result> + + suspend fun getDateEventList(date: LocalDate): Result> + suspend fun getEventList(): Result> - suspend fun getMonthEventList(date: LocalDate): Result> + suspend fun getEventListFromDate(date: LocalDate): Result> suspend fun getEvent(eventId: String): Result diff --git a/core/network/src/main/java/com/wap/wapp/core/network/source/event/EventDataSourceImpl.kt b/core/network/src/main/java/com/wap/wapp/core/network/source/event/EventDataSourceImpl.kt index 18549dc5c..54b513502 100644 --- a/core/network/src/main/java/com/wap/wapp/core/network/source/event/EventDataSourceImpl.kt +++ b/core/network/src/main/java/com/wap/wapp/core/network/source/event/EventDataSourceImpl.kt @@ -31,6 +31,27 @@ class EventDataSourceImpl @Inject constructor( result } + override suspend fun getEventListFromDate(date: LocalDate): Result> = + runCatching { + val result = mutableListOf() + + // 선택된 날짜 1일 00시 00분 00초 + val startDateTime = date.atStartOfDay().toISOLocalDateTimeString() + + val task = firebaseFirestore.collection(EVENT_COLLECTION) + .whereGreaterThanOrEqualTo("startDateTime", startDateTime) + .get() + .await() + + for (document in task.documents) { + val event = document.toObject() + checkNotNull(event) + result.add(event) + } + + result + } + override suspend fun getMonthEventList(date: LocalDate): Result> = runCatching { val result = mutableListOf() @@ -59,6 +80,28 @@ class EventDataSourceImpl @Inject constructor( result } + override suspend fun getDateEventList(date: LocalDate): Result> = + runCatching { + val result = mutableListOf() + + val startDateTime = date.atStartOfDay().toISOLocalDateTimeString() + val endDateTime = date.atTime(LocalTime.MAX).toISOLocalDateTimeString() + + val task = firebaseFirestore.collection(EVENT_COLLECTION) + .whereGreaterThanOrEqualTo("startDateTime", startDateTime) + .whereLessThanOrEqualTo("startDateTime", endDateTime) + .get() + .await() + + for (document in task.documents) { + val event = document.toObject() + checkNotNull(event) + result.add(event) + } + + result + } + override suspend fun getEvent(eventId: String): Result = runCatching { val document = firebaseFirestore.collection(EVENT_COLLECTION) 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 20b3efcdd..6022af5e1 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 @@ -11,6 +11,8 @@ interface SurveyDataSource { suspend fun getSurveyList(): Result> + suspend fun getUserRespondedSurveyList(userId: String): Result> + suspend fun getSurvey(surveyId: String): Result suspend fun postSurvey( 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 ff7f7c90b..2916973f2 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 @@ -29,6 +29,25 @@ class SurveyDataSourceImpl @Inject constructor( result } + override suspend fun getUserRespondedSurveyList(userId: String): Result> = + runCatching { + val result: MutableList = mutableListOf() + + val task = firebaseFirestore.collection(SURVEY_COLLECTION) + .whereEqualTo("userId", userId) + .get() + .await() + + for (document in task.documents) { + val surveyResponse = document.toObject(SurveyResponse::class.java) + checkNotNull(surveyResponse) + + result.add(surveyResponse) + } + + result + } + override suspend fun getSurvey(surveyId: String): Result = runCatching { val result = firebaseFirestore.collection(SURVEY_COLLECTION) .document(surveyId) diff --git a/feature/management-survey/src/main/java/com/wap/wapp/feature/management/survey/edit/SurveyFormEditViewModel.kt b/feature/management-survey/src/main/java/com/wap/wapp/feature/management/survey/edit/SurveyFormEditViewModel.kt index 4dcbb7e11..d04800da4 100644 --- a/feature/management-survey/src/main/java/com/wap/wapp/feature/management/survey/edit/SurveyFormEditViewModel.kt +++ b/feature/management-survey/src/main/java/com/wap/wapp/feature/management/survey/edit/SurveyFormEditViewModel.kt @@ -113,12 +113,11 @@ class SurveyFormEditViewModel @Inject constructor( } fun getEventList() = viewModelScope.launch { - getEventListUseCase() - .onSuccess { eventList -> - _eventList.value = EventsState.Success(eventList) - }.onFailure { throwable -> - _surveyFormEditUiEvent.emit(SurveyFormEditUiEvent.Failure(throwable)) - } + getEventListUseCase().onSuccess { eventList -> + _eventList.value = EventsState.Success(eventList) + }.onFailure { throwable -> + _surveyFormEditUiEvent.emit(SurveyFormEditUiEvent.Failure(throwable)) + } } fun validateSurveyForm(currentState: SurveyFormState): Boolean { diff --git a/feature/management-survey/src/main/java/com/wap/wapp/feature/management/survey/registration/SurveyFormRegistrationViewModel.kt b/feature/management-survey/src/main/java/com/wap/wapp/feature/management/survey/registration/SurveyFormRegistrationViewModel.kt index 5eedc8b64..308d4dd7c 100644 --- a/feature/management-survey/src/main/java/com/wap/wapp/feature/management/survey/registration/SurveyFormRegistrationViewModel.kt +++ b/feature/management-survey/src/main/java/com/wap/wapp/feature/management/survey/registration/SurveyFormRegistrationViewModel.kt @@ -67,12 +67,11 @@ class SurveyFormRegistrationViewModel @Inject constructor( val surveyDateDeadline = _surveyDateDeadline.asStateFlow() fun getEventList() = viewModelScope.launch { - getEventListUseCase() - .onSuccess { eventList -> - _eventList.value = EventsState.Success(eventList) - }.onFailure { throwable -> - _surveyRegistrationEvent.emit(SurveyRegistrationEvent.Failure(throwable)) - } + getEventListUseCase().onSuccess { eventList -> + _eventList.value = EventsState.Success(eventList) + }.onFailure { throwable -> + _surveyRegistrationEvent.emit(SurveyRegistrationEvent.Failure(throwable)) + } } fun registerSurvey() = viewModelScope.launch { diff --git a/feature/notice/src/main/java/com/wap/wapp/feature/notice/BottomSheetContent.kt b/feature/notice/src/main/java/com/wap/wapp/feature/notice/BottomSheetContent.kt index df47cf482..02999c1af 100644 --- a/feature/notice/src/main/java/com/wap/wapp/feature/notice/BottomSheetContent.kt +++ b/feature/notice/src/main/java/com/wap/wapp/feature/notice/BottomSheetContent.kt @@ -12,7 +12,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Divider import androidx.compose.material.Text @@ -33,7 +33,6 @@ import com.wap.wapp.core.commmon.util.DateUtil.MONTH_DATE_START_INDEX import com.wap.wapp.core.commmon.util.DateUtil.yyyyMMddFormatter import com.wap.wapp.core.model.event.Event import java.time.LocalDate -import java.time.format.DateTimeFormatter import java.time.format.TextStyle import java.util.Locale @@ -72,16 +71,16 @@ private fun HandleEventsState(events: NoticeViewModel.EventsState) = when (event @Composable private fun EventsList(events: List) { - if (events.size > 0) { + if (events.isNotEmpty()) { LazyColumn( contentPadding = PaddingValues(horizontal = 15.dp), verticalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.fillMaxWidth(), ) { - itemsIndexed( + items( items = events, - key = { _, event -> event.eventId }, - ) { _, event -> + key = { event -> event.eventId }, + ) { event -> EventItem(event = event) } } @@ -110,8 +109,6 @@ private fun EventsList(events: List) { @Composable private fun EventItem(event: Event) { - val formatter = DateTimeFormatter.ofPattern("MM-dd") - Column { Row( modifier = Modifier diff --git a/feature/notice/src/main/java/com/wap/wapp/feature/notice/NoticeViewModel.kt b/feature/notice/src/main/java/com/wap/wapp/feature/notice/NoticeViewModel.kt index 1d3027735..fc3dfd97a 100644 --- a/feature/notice/src/main/java/com/wap/wapp/feature/notice/NoticeViewModel.kt +++ b/feature/notice/src/main/java/com/wap/wapp/feature/notice/NoticeViewModel.kt @@ -3,6 +3,7 @@ package com.wap.wapp.feature.notice import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.wap.wapp.core.commmon.util.DateUtil +import com.wap.wapp.core.domain.usecase.event.GetDateEventListUseCase import com.wap.wapp.core.domain.usecase.event.GetMonthEventListUseCase import com.wap.wapp.core.model.event.Event import dagger.hilt.android.lifecycle.HiltViewModel @@ -16,6 +17,7 @@ import javax.inject.Inject @HiltViewModel class NoticeViewModel @Inject constructor( private val getMonthEventListUseCase: GetMonthEventListUseCase, + private val getDateEventListUseCase: GetDateEventListUseCase, ) : ViewModel() { private val _monthEvents = MutableStateFlow(EventsState.Loading) val monthEvents: StateFlow = _monthEvents.asStateFlow() @@ -43,10 +45,8 @@ class NoticeViewModel @Inject constructor( fun getSelectedDateEvents() { _selectedDateEvents.value = EventsState.Loading viewModelScope.launch { - getMonthEventListUseCase(_selectedDate.value).onSuccess { - _selectedDateEvents.value = EventsState.Success( - it.filter { it.startDateTime.toLocalDate() == _selectedDate.value }, - ) + getDateEventListUseCase(_selectedDate.value).onSuccess { eventList -> + _selectedDateEvents.value = EventsState.Success(eventList) }.onFailure { _selectedDateEvents.value = EventsState.Failure(it) } } } diff --git a/feature/profile/src/main/java/com/wap/wapp/feature/profile/ProfileScreen.kt b/feature/profile/src/main/java/com/wap/wapp/feature/profile/ProfileScreen.kt index 13954b45d..fc3383ebc 100644 --- a/feature/profile/src/main/java/com/wap/wapp/feature/profile/ProfileScreen.kt +++ b/feature/profile/src/main/java/com/wap/wapp/feature/profile/ProfileScreen.kt @@ -2,116 +2,180 @@ package com.wap.wapp.feature.profile import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.wap.designsystem.WappTheme +import com.wap.designsystem.component.CircleLoader import com.wap.designsystem.component.WappMainTopBar +import com.wap.wapp.core.commmon.extensions.toSupportingText import com.wap.wapp.core.designresource.R.drawable import com.wap.wapp.core.designresource.R.string +import com.wap.wapp.core.model.user.UserProfile +import com.wap.wapp.core.model.user.UserRole +import com.wap.wapp.feature.profile.ProfileViewModel.UserRoleState import com.wap.wapp.feature.profile.component.WappProfileCard -import com.wap.wapp.feature.profile.screen.GuestProfile -import com.wap.wapp.feature.profile.screen.UserProfile +import com.wap.wapp.feature.profile.profilesetting.component.GuestProfile +import com.wap.wapp.feature.profile.profilesetting.component.UserProfile +import kotlinx.coroutines.flow.collectLatest @Composable internal fun ProfileRoute( viewModel: ProfileViewModel = hiltViewModel(), - navigateToProfileSetting: () -> Unit, - navigateToSignInScreen: () -> Unit, + navigateToProfileSetting: (String) -> Unit, + navigateToSignIn: () -> Unit, ) { - val eventsState by viewModel.todayEvents.collectAsStateWithLifecycle() + val todayEventsState by viewModel.todayEvents.collectAsStateWithLifecycle() + val recentEventsState by viewModel.recentEvents.collectAsStateWithLifecycle() + val userRespondedSurveysState by viewModel.userRespondedSurveys.collectAsStateWithLifecycle() + val userRoleState by viewModel.userRole.collectAsStateWithLifecycle() + val userProfile by viewModel.userProfile.collectAsStateWithLifecycle() + val snackBarHostState = remember { SnackbarHostState() } + + LaunchedEffect(true) { + viewModel.errorFlow.collectLatest { throwable -> + snackBarHostState.showSnackbar( + message = throwable.toSupportingText(), + ) + } + } ProfileScreen( - eventsState = eventsState, + todayEventsState = todayEventsState, + recentEventsState = recentEventsState, + userRoleState = userRoleState, + userProfile = userProfile, + userRespondedSurveysState = userRespondedSurveysState, + snackBarHostState = snackBarHostState, navigateToProfileSetting = navigateToProfileSetting, - navigateToSignInScreen = navigateToSignInScreen, + navigateToSignIn = navigateToSignIn, ) } @Composable internal fun ProfileScreen( - role: Role = Role.MANAGER, - userName: String = "", - eventsState: ProfileViewModel.EventsState, - navigateToProfileSetting: () -> Unit, - navigateToSignInScreen: () -> Unit, + userRoleState: UserRoleState, + userProfile: UserProfile, + todayEventsState: ProfileViewModel.EventsState, + recentEventsState: ProfileViewModel.EventsState, + userRespondedSurveysState: ProfileViewModel.SurveysState, + snackBarHostState: SnackbarHostState, + navigateToProfileSetting: (String) -> Unit, + navigateToSignIn: () -> Unit, ) { val scrollState = rememberScrollState() - Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(scrollState) - .background(WappTheme.colors.backgroundBlack), - ) { - WappMainTopBar( - titleRes = string.profile, - contentRes = R.string.profile_content, - settingButtonDescriptionRes = R.string.profile_setting_description, - showSettingButton = role != Role.GUEST, - onClickSettingButton = navigateToProfileSetting, - ) + Scaffold( + contentWindowInsets = WindowInsets(0.dp), + snackbarHost = { SnackbarHost(snackBarHostState) }, + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .verticalScroll(scrollState) + .background(WappTheme.colors.backgroundBlack), + ) { + when (userRoleState) { + is UserRoleState.Loading -> { + Spacer(modifier = Modifier.weight(1f)) + CircleLoader( + modifier = Modifier + .fillMaxSize() + .weight(1f), + ) + Spacer(modifier = Modifier.weight(1f)) + } - when (role) { - Role.MANAGER -> { - WappProfileCard( - position = stringResource(R.string.manager), - githubImage = drawable.ic_manager_github, - catImage = drawable.ic_manager_cat, - brush = Brush.horizontalGradient( - listOf( - WappTheme.colors.blue2FF, - WappTheme.colors.blue4FF, - WappTheme.colors.blue1FF, - ), - ), - userName = "$userName 님", - ) + is UserRoleState.Success -> { + WappMainTopBar( + titleRes = string.profile, + contentRes = R.string.profile_content, + settingButtonDescriptionRes = R.string.profile_setting_description, + showSettingButton = userRoleState.userRole != UserRole.GUEST, + onClickSettingButton = { navigateToProfileSetting(userProfile.userId) }, + ) - UserProfile(eventsState = eventsState) - } + when (userRoleState.userRole) { + UserRole.MANAGER -> { + WappProfileCard( + position = stringResource(R.string.manager), + githubImage = drawable.ic_manager_github, + catImage = drawable.ic_manager_cat, + brush = Brush.horizontalGradient( + listOf( + WappTheme.colors.blue2FF, + WappTheme.colors.blue4FF, + WappTheme.colors.blue1FF, + ), + ), + userName = "${userProfile.userName} 님", + ) - Role.NORMAL -> { - WappProfileCard( - position = stringResource(R.string.normal), - githubImage = drawable.ic_normal_github, - catImage = drawable.ic_normal_cat, - brush = Brush.horizontalGradient( - listOf( - WappTheme.colors.yellow3C, - WappTheme.colors.yellow34, - WappTheme.colors.yellowA4, - ), - ), - userName = "$userName 님", - ) + UserProfile( + todayEventsState = todayEventsState, + recentEventsState = recentEventsState, + userRespondedSurveysState = userRespondedSurveysState, + ) + } - UserProfile(eventsState = eventsState) - } + UserRole.MEMBER -> { + WappProfileCard( + position = stringResource(R.string.normal), + githubImage = drawable.ic_normal_github, + catImage = drawable.ic_normal_cat, + brush = Brush.horizontalGradient( + listOf( + WappTheme.colors.yellow3C, + WappTheme.colors.yellow34, + WappTheme.colors.yellowA4, + ), + ), + userName = "${userProfile.userName} 님", + ) + + UserProfile( + todayEventsState = todayEventsState, + recentEventsState = recentEventsState, + userRespondedSurveysState = userRespondedSurveysState, + ) + } - Role.GUEST -> { - WappProfileCard( - position = stringResource(R.string.guest), - githubImage = drawable.ic_guest_github, - catImage = drawable.ic_guest_cat, - brush = Brush.horizontalGradient( - listOf( - WappTheme.colors.grayA2, - WappTheme.colors.gray7C, - WappTheme.colors.gray4A, - ), - ), - userName = stringResource(id = R.string.non_user), - ) + UserRole.GUEST -> { + WappProfileCard( + position = stringResource(R.string.guest), + githubImage = drawable.ic_guest_github, + catImage = drawable.ic_guest_cat, + brush = Brush.horizontalGradient( + listOf( + WappTheme.colors.grayA2, + WappTheme.colors.gray7C, + WappTheme.colors.gray4A, + ), + ), + userName = stringResource(id = R.string.non_user), + ) - GuestProfile(navigateToSignInScreen = navigateToSignInScreen) + GuestProfile(navigateToSignIn = navigateToSignIn) + } + } + } } } } diff --git a/feature/profile/src/main/java/com/wap/wapp/feature/profile/ProfileViewModel.kt b/feature/profile/src/main/java/com/wap/wapp/feature/profile/ProfileViewModel.kt index 1c464d9f6..063bdb87f 100644 --- a/feature/profile/src/main/java/com/wap/wapp/feature/profile/ProfileViewModel.kt +++ b/feature/profile/src/main/java/com/wap/wapp/feature/profile/ProfileViewModel.kt @@ -3,46 +3,135 @@ package com.wap.wapp.feature.profile import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.wap.wapp.core.commmon.util.DateUtil -import com.wap.wapp.core.domain.usecase.event.GetMonthEventListUseCase +import com.wap.wapp.core.domain.usecase.event.GetDateEventListUseCase +import com.wap.wapp.core.domain.usecase.event.GetRecentEventListUseCase +import com.wap.wapp.core.domain.usecase.survey.GetUserRespondedSurveyListUseCase +import com.wap.wapp.core.domain.usecase.user.GetUserProfileUseCase +import com.wap.wapp.core.domain.usecase.user.GetUserRoleUseCase import com.wap.wapp.core.model.event.Event +import com.wap.wapp.core.model.survey.Survey +import com.wap.wapp.core.model.user.UserProfile +import com.wap.wapp.core.model.user.UserRole import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import java.time.LocalDate import javax.inject.Inject @HiltViewModel class ProfileViewModel @Inject constructor( - private val getMonthEventListUseCase: GetMonthEventListUseCase, + private val getUserRoleUseCase: GetUserRoleUseCase, + private val getUserProfileUseCase: GetUserProfileUseCase, + private val getRecentEventListUseCase: GetRecentEventListUseCase, + private val getDateEventListUseCase: GetDateEventListUseCase, + private val getUserRespondedSurveyListUseCase: GetUserRespondedSurveyListUseCase, ) : ViewModel() { + private val _errorFlow: MutableSharedFlow = MutableSharedFlow() + val errorFlow: SharedFlow = _errorFlow.asSharedFlow() + private val _todayEvents = MutableStateFlow(EventsState.Loading) val todayEvents: StateFlow = _todayEvents.asStateFlow() + private val _recentEvents = MutableStateFlow(EventsState.Loading) + val recentEvents: StateFlow = _recentEvents.asStateFlow() + + private val _userRespondedSurveys = MutableStateFlow(SurveysState.Loading) + val userRespondedSurveys: StateFlow = _userRespondedSurveys.asStateFlow() + + private val _userRole = MutableStateFlow(UserRoleState.Loading) + val userRole: StateFlow = _userRole.asStateFlow() + + private val _userProfile = MutableStateFlow(DEFAULT_USER_PROFILE) + val userProfile: StateFlow = _userProfile.asStateFlow() + init { - getTodayDateEvents() + checkUserInformationAndGetEvents() + } + + private fun checkUserInformationAndGetEvents() = viewModelScope.launch { + getUserRoleUseCase() + .onFailure { exception -> _errorFlow.emit(exception) } + .onSuccess { userRole -> + when (userRole) { + // 비회원 일 경우, 바로 UserCard 갱신 + UserRole.GUEST -> _userRole.value = UserRoleState.Success(userRole) + + // 일반 회원 혹은 운영진 일 경우, + // 오늘 일정 정보, UserProfile 정보를 가져온 뒤 한꺼번에 갱신 + UserRole.MEMBER, UserRole.MANAGER -> { + getTodayDateEvents() + val userProfile = async { getUserProfileUseCase() } + + userProfile.await().onSuccess { + _userRole.value = UserRoleState.Success(userRole) + _userProfile.value = it + launch { getRecentEventsForAttendanceCheck() } + getUserRespondedSurveys() + }.onFailure { exception -> _errorFlow.emit(exception) } + } + } + } } private fun getTodayDateEvents() { _todayEvents.value = EventsState.Loading viewModelScope.launch { - getMonthEventListUseCase(DateUtil.generateNowDate()).fold( - onSuccess = { - _todayEvents.value = - EventsState.Success( - it.filter { - it.endDateTime == DateUtil.generateNowDateTime() - }, - ) - }, - onFailure = { _todayEvents.value = EventsState.Failure(it) }, - ) + getDateEventListUseCase(DateUtil.generateNowDate()).onSuccess { eventList -> + _todayEvents.value = EventsState.Success(eventList) + }.onFailure { exception -> _errorFlow.emit(exception) } + } + } + + private suspend fun getUserRespondedSurveys() { + getUserRespondedSurveyListUseCase(_userProfile.value.userId).onSuccess { surveyList -> + _userRespondedSurveys.value = SurveysState.Success(surveyList) + }.onFailure { exception -> _errorFlow.emit(exception) } + } + + private suspend fun getRecentEventsForAttendanceCheck() { + val registeredAt = _userProfile.value.registeredAt + val (registeredYear, registeredSemester) = registeredAt.split(" ") + val registrationDate = + createRegistrationDate(registeredYear.toInt(), registeredSemester) + + getRecentEventListUseCase(registrationDate) + .onSuccess { + _recentEvents.value = EventsState.Success(it) + }.onFailure { _errorFlow.emit(it) } + } + + private fun createRegistrationDate(year: Int, semester: String): LocalDate { + // 학기에 따른 기준 날짜 설정 (예: 1학기는 3월 1일, 2학기는 9월 1일) + val semesterNumber = semester.removeSuffix("학기").toInt() + + if (semesterNumber == 1) { + return LocalDate.of(year, 3, 1) } + return LocalDate.of(year, 9, 1) } sealed class EventsState { data object Loading : EventsState() data class Success(val events: List) : EventsState() - data class Failure(val throwable: Throwable) : EventsState() + } + + sealed class SurveysState { + data object Loading : SurveysState() + data class Success(val surveys: List) : SurveysState() + } + + sealed class UserRoleState { + data object Loading : UserRoleState() + data class Success(val userRole: UserRole) : UserRoleState() + } + + companion object { + val DEFAULT_USER_PROFILE = UserProfile("", "", "", "") } } diff --git a/feature/profile/src/main/java/com/wap/wapp/feature/profile/Role.kt b/feature/profile/src/main/java/com/wap/wapp/feature/profile/Role.kt deleted file mode 100644 index 94048aa68..000000000 --- a/feature/profile/src/main/java/com/wap/wapp/feature/profile/Role.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.wap.wapp.feature.profile - -enum class Role { - MANAGER, NORMAL, GUEST -} diff --git a/feature/profile/src/main/java/com/wap/wapp/feature/profile/component/WappAttendanceRow.kt b/feature/profile/src/main/java/com/wap/wapp/feature/profile/component/WappAttendanceRow.kt index 5df5054b4..60d1fc991 100644 --- a/feature/profile/src/main/java/com/wap/wapp/feature/profile/component/WappAttendanceRow.kt +++ b/feature/profile/src/main/java/com/wap/wapp/feature/profile/component/WappAttendanceRow.kt @@ -12,19 +12,20 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.wap.designsystem.WappTheme +import com.wap.wapp.core.commmon.util.DateUtil import com.wap.wapp.core.designresource.R +import com.wap.wapp.core.model.event.Event @Composable internal fun WappAttendacneRow( + event: Event, isAttendance: Boolean, onClick: () -> Unit = {}, modifier: Modifier = Modifier, ) { Row( verticalAlignment = Alignment.CenterVertically, - modifier = modifier - .padding(horizontal = 10.dp) - .clickable { onClick() }, + modifier = modifier.clickable { onClick() }, ) { Row( verticalAlignment = Alignment.CenterVertically, @@ -32,7 +33,7 @@ internal fun WappAttendacneRow( ) { WappAttendanceBadge(isAttendance = isAttendance) Text( - text = "프로젝트 세미나", + text = event.title, style = WappTheme.typography.labelRegular, color = WappTheme.colors.white, maxLines = 1, @@ -41,7 +42,7 @@ internal fun WappAttendacneRow( ) } Text( - text = "09월 04일", + text = event.startDateTime.format(DateUtil.HHmmFormatter), style = WappTheme.typography.labelRegular, color = WappTheme.colors.gray95, modifier = Modifier.padding(start = 10.dp), diff --git a/feature/profile/src/main/java/com/wap/wapp/feature/profile/component/WappSurveyHistoryRow.kt b/feature/profile/src/main/java/com/wap/wapp/feature/profile/component/WappSurveyHistoryRow.kt index 318dae019..da585a5f1 100644 --- a/feature/profile/src/main/java/com/wap/wapp/feature/profile/component/WappSurveyHistoryRow.kt +++ b/feature/profile/src/main/java/com/wap/wapp/feature/profile/component/WappSurveyHistoryRow.kt @@ -13,9 +13,11 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.wap.designsystem.WappTheme import com.wap.wapp.core.designresource.R +import com.wap.wapp.core.model.survey.Survey @Composable internal fun WappSurveyHistoryRow( + survey: Survey, modifier: Modifier = Modifier, onClick: () -> Unit = {}, ) { @@ -35,7 +37,7 @@ internal fun WappSurveyHistoryRow( ) Text( - text = "프로젝트 세미나에서 보완해야 할 점이 많아요 아싸라비야", + text = survey.title, style = WappTheme.typography.labelRegular, color = WappTheme.colors.white, maxLines = 1, diff --git a/feature/profile/src/main/java/com/wap/wapp/feature/profile/navigation/ProfileNavigation.kt b/feature/profile/src/main/java/com/wap/wapp/feature/profile/navigation/ProfileNavigation.kt index fc1a8337a..8f1fe340c 100644 --- a/feature/profile/src/main/java/com/wap/wapp/feature/profile/navigation/ProfileNavigation.kt +++ b/feature/profile/src/main/java/com/wap/wapp/feature/profile/navigation/ProfileNavigation.kt @@ -14,13 +14,13 @@ fun NavController.navigateToProfile(navOptions: NavOptions? = navOptions {}) { } fun NavGraphBuilder.profileScreen( - navigateToProfileSetting: () -> Unit, - navigateToSignInScreen: () -> Unit, + navigateToProfileSetting: (String) -> Unit, + navigateToSignIn: () -> Unit, ) { composable(route = profileNavigationRoute) { ProfileRoute( navigateToProfileSetting = navigateToProfileSetting, - navigateToSignInScreen = navigateToSignInScreen, + navigateToSignIn = navigateToSignIn, ) } } diff --git a/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/ProfileSettingScreen.kt b/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/ProfileSettingScreen.kt index 5c870c026..d64724280 100644 --- a/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/ProfileSettingScreen.kt +++ b/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/ProfileSettingScreen.kt @@ -7,13 +7,22 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Divider import androidx.compose.material.Text +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource @@ -25,179 +34,166 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.wap.designsystem.WappTheme import com.wap.designsystem.component.WappRowBar import com.wap.designsystem.component.WappSubTopBar +import com.wap.wapp.core.commmon.extensions.toSupportingText import com.wap.wapp.core.designresource.R import com.wap.wapp.feature.profile.R.string +import com.wap.wapp.feature.profile.profilesetting.ProfileSettingViewModel.EventResult.Failure +import com.wap.wapp.feature.profile.profilesetting.ProfileSettingViewModel.EventResult.Success +import com.wap.wapp.feature.profile.profilesetting.component.ProfileSettingDialog @Composable internal fun ProfileSettingRoute( + userId: String, navigateToProfile: () -> Unit, + navigateToSignIn: () -> Unit, viewModel: ProfileSettingViewModel = hiltViewModel(), ) { - val context = LocalContext.current + val snackBarHostState = remember { SnackbarHostState() } + + LaunchedEffect(true) { + viewModel.eventFlow.collect { eventResult -> + when (eventResult) { + is Failure -> + snackBarHostState.showSnackbar(eventResult.throwable.toSupportingText()) + is Success -> navigateToSignIn() + } + } + } ProfileSettingScreen( + withdrawal = { viewModel.withdrawal(userId) }, + signOut = viewModel::signOut, + snackBarHostState = snackBarHostState, navigateToProfile = navigateToProfile, - onClickedPrivacyPolicy = { - navigateToUri( - context, - PRIVACY_POLICY_URL, - ) - }, - onClickedFAQ = { - navigateToUri( - context, - FAQ_URL, - ) - }, - onClickedInquiry = { - navigateToUri( - context, - INQUIRY_URL, - ) - }, - onClickedTermsAndPolicies = { - navigateToUri( - context, - TERMS_AND_POLICIES_URL, - ) - }, ) } -private fun navigateToUri(context: Context, url: String) = startActivity( - context, - generateUriIntent(url), - null, -) - -private fun generateUriIntent(url: String) = Intent(Intent.ACTION_VIEW, url.toUri()) - @Composable internal fun ProfileSettingScreen( navigateToProfile: () -> Unit, - onClickedAlarmSetting: () -> Unit = {}, - onClickedSignOut: () -> Unit = {}, - onClickedWithdrawal: () -> Unit = {}, - onClickedInquiry: () -> Unit, - onClickedFAQ: () -> Unit, - onClickedTermsAndPolicies: () -> Unit, - onClickedPrivacyPolicy: () -> Unit, + withdrawal: () -> Unit, + signOut: () -> Unit, + snackBarHostState: SnackbarHostState, ) { - val dividerThickness = 1.dp + var showWithdrawalDialog by remember { mutableStateOf(false) } + var showLogoutDialog by remember { mutableStateOf(false) } val dividerColor = WappTheme.colors.black42 val scrollState = rememberScrollState() + val context = LocalContext.current - Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(scrollState) - .background(color = WappTheme.colors.backgroundBlack), - ) { - WappSubTopBar( - titleRes = string.more, - showLeftButton = true, - onClickLeftButton = navigateToProfile, - modifier = Modifier.padding(top = 20.dp), - ) - - Row( - horizontalArrangement = Arrangement.spacedBy(15.dp), - modifier = Modifier.padding(start = 15.dp, top = 20.dp, bottom = 25.dp), - ) { - Image( - painter = painterResource(id = R.drawable.ic_account_setting), - contentDescription = "", - ) - Text( - text = stringResource(id = com.wap.wapp.feature.profile.R.string.account_setting), - style = WappTheme.typography.titleBold, - color = WappTheme.colors.white, - ) - } - - WappRowBar( - title = stringResource(id = com.wap.wapp.feature.profile.R.string.alarm_setting), - onClicked = onClickedAlarmSetting, - ) - - Divider( - color = dividerColor, - thickness = dividerThickness, + if (showWithdrawalDialog) { + ProfileSettingDialog( + onDismissRequest = { showWithdrawalDialog = false }, + onConfirmRequest = withdrawal, + title = string.withdrawal, ) + } - WappRowBar( - title = stringResource(id = com.wap.wapp.feature.profile.R.string.sign_out), - onClicked = onClickedSignOut, + if (showLogoutDialog) { + ProfileSettingDialog( + onDismissRequest = { showLogoutDialog = false }, + onConfirmRequest = signOut, + title = string.logout, ) + } - Divider( - color = dividerColor, - thickness = dividerThickness, - ) + Scaffold( + contentWindowInsets = WindowInsets(0.dp), + snackbarHost = { SnackbarHost(snackBarHostState) }, + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .verticalScroll(scrollState) + .background(color = WappTheme.colors.backgroundBlack), + ) { + WappSubTopBar( + titleRes = string.more, + showLeftButton = true, + onClickLeftButton = navigateToProfile, + modifier = Modifier.padding(top = 20.dp), + ) - WappRowBar( - title = stringResource(id = com.wap.wapp.feature.profile.R.string.withdrawal), - onClicked = onClickedWithdrawal, - ) + Row( + horizontalArrangement = Arrangement.spacedBy(15.dp), + modifier = Modifier.padding(start = 15.dp, top = 20.dp, bottom = 25.dp), + ) { + Image( + painter = painterResource(id = R.drawable.ic_account_setting), + contentDescription = "", + ) + Text( + text = stringResource(id = string.account_setting), + style = WappTheme.typography.titleBold, + color = WappTheme.colors.white, + ) + } + + WappRowBar( + title = stringResource(id = string.logout), + onClicked = { showLogoutDialog = true }, + ) - Divider( - color = dividerColor, - thickness = dividerThickness, - ) + Divider(color = dividerColor) - Row( - horizontalArrangement = Arrangement.spacedBy(15.dp), - modifier = Modifier.padding(start = 15.dp, top = 25.dp, bottom = 25.dp), - ) { - Image( - painter = painterResource(id = R.drawable.ic_profile_more), - contentDescription = "", - ) - Text( - text = stringResource(id = com.wap.wapp.feature.profile.R.string.more), - style = WappTheme.typography.titleBold, - color = WappTheme.colors.white, + WappRowBar( + title = stringResource(id = string.withdrawal), + onClicked = { showWithdrawalDialog = true }, ) - } - WappRowBar( - title = stringResource(id = com.wap.wapp.feature.profile.R.string.inquiry), - onClicked = onClickedInquiry, - ) + Divider(color = dividerColor) + + Row( + horizontalArrangement = Arrangement.spacedBy(15.dp), + modifier = Modifier.padding(start = 15.dp, top = 25.dp, bottom = 25.dp), + ) { + Image( + painter = painterResource(id = R.drawable.ic_profile_more), + contentDescription = "", + ) + Text( + text = stringResource(id = string.more), + style = WappTheme.typography.titleBold, + color = WappTheme.colors.white, + ) + } + + WappRowBar( + title = stringResource(id = string.inquiry), + onClicked = { navigateToUri(context, INQUIRY_URL) }, + ) - Divider( - color = dividerColor, - thickness = dividerThickness, - ) + Divider(color = dividerColor) - WappRowBar( - title = stringResource(id = com.wap.wapp.feature.profile.R.string.faq), - onClicked = onClickedFAQ, - ) + WappRowBar( + title = stringResource(id = string.faq), + onClicked = { navigateToUri(context, FAQ_URL) }, + ) - Divider( - color = dividerColor, - thickness = dividerThickness, - ) + Divider(color = dividerColor) - WappRowBar( - title = stringResource(id = com.wap.wapp.feature.profile.R.string.terms_and_policies), - onClicked = onClickedTermsAndPolicies, - ) + WappRowBar( + title = stringResource(id = string.terms_and_policies), + onClicked = { navigateToUri(context, TERMS_AND_POLICIES_URL) }, + ) - Divider( - color = dividerColor, - thickness = dividerThickness, - ) + Divider(color = dividerColor) - WappRowBar( - title = stringResource(id = com.wap.wapp.feature.profile.R.string.privacy_policy), - onClicked = onClickedPrivacyPolicy, - ) + WappRowBar( + title = stringResource(id = string.privacy_policy), + onClicked = { navigateToUri(context, PRIVACY_POLICY_URL) }, + ) - Divider( - color = dividerColor, - thickness = dividerThickness, - ) + Divider(color = dividerColor) + } } } + +private fun navigateToUri(context: Context, url: String) = startActivity( + context, + generateUriIntent(url), + null, +) + +private fun generateUriIntent(url: String) = Intent(Intent.ACTION_VIEW, url.toUri()) diff --git a/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/ProfileSettingViewModel.kt b/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/ProfileSettingViewModel.kt index e8c255eb9..a0e11e3ce 100644 --- a/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/ProfileSettingViewModel.kt +++ b/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/ProfileSettingViewModel.kt @@ -1,8 +1,36 @@ package com.wap.wapp.feature.profile.profilesetting import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.wap.wapp.core.domain.usecase.auth.DeleteUserUseCase +import com.wap.wapp.core.domain.usecase.auth.SignOutUseCase import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class ProfileSettingViewModel @Inject constructor() : ViewModel() +class ProfileSettingViewModel @Inject constructor( + private val signOutUseCase: SignOutUseCase, + private val deleteUserUseCase: DeleteUserUseCase, +) : ViewModel() { + private val _eventFlow: MutableSharedFlow = MutableSharedFlow() + val eventFlow: SharedFlow = _eventFlow.asSharedFlow() + + fun signOut() = viewModelScope.launch { + signOutUseCase().onSuccess { _eventFlow.emit(EventResult.Success) } + .onFailure { _eventFlow.emit(EventResult.Failure(it)) } + } + + fun withdrawal(userId: String) = viewModelScope.launch { + deleteUserUseCase(userId).onSuccess { _eventFlow.emit(EventResult.Success) } + .onFailure { _eventFlow.emit(EventResult.Failure(it)) } + } + + sealed class EventResult { + data class Failure(val throwable: Throwable) : EventResult() + data object Success : EventResult() + } +} diff --git a/feature/profile/src/main/java/com/wap/wapp/feature/profile/screen/GuestProfile.kt b/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/component/GuestProfile.kt similarity index 93% rename from feature/profile/src/main/java/com/wap/wapp/feature/profile/screen/GuestProfile.kt rename to feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/component/GuestProfile.kt index 54c1f9bfb..62ba49dda 100644 --- a/feature/profile/src/main/java/com/wap/wapp/feature/profile/screen/GuestProfile.kt +++ b/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/component/GuestProfile.kt @@ -1,4 +1,4 @@ -package com.wap.wapp.feature.profile.screen +package com.wap.wapp.feature.profile.profilesetting.component import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxWidth @@ -22,7 +22,7 @@ import com.wap.designsystem.WappTheme import com.wap.wapp.feature.profile.R @Composable -internal fun GuestProfile(navigateToSignInScreen: () -> Unit) { +internal fun GuestProfile(navigateToSignIn: () -> Unit) { Text( text = SpannableGuestText(), color = WappTheme.colors.white, @@ -41,7 +41,7 @@ internal fun GuestProfile(navigateToSignInScreen: () -> Unit) { .padding(top = 40.dp) .height(50.dp) .fillMaxWidth() - .clickable { navigateToSignInScreen() }, + .clickable { navigateToSignIn() }, ) { Text( text = stringResource(id = R.string.navigate_to_login), diff --git a/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/component/NoContentColumn.kt b/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/component/NoContentColumn.kt new file mode 100644 index 000000000..d25dc9f6b --- /dev/null +++ b/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/component/NoContentColumn.kt @@ -0,0 +1,36 @@ +package com.wap.wapp.feature.profile.profilesetting.component + +import androidx.annotation.StringRes +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.wap.designsystem.WappTheme + +@Composable +internal fun NothingToShow(@StringRes title: Int) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .padding(10.dp) + .height(130.dp), + ) { + Spacer(modifier = Modifier.weight(1f)) + Text( + text = stringResource(id = title), + style = WappTheme.typography.contentRegular.copy(fontSize = 20.sp), + color = WappTheme.colors.white, + textAlign = TextAlign.Center, + modifier = Modifier.weight(1f), + ) + Spacer(modifier = Modifier.weight(1f)) + } +} diff --git a/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/component/ProfileSettingDialog.kt b/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/component/ProfileSettingDialog.kt new file mode 100644 index 000000000..0796aa7a9 --- /dev/null +++ b/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/component/ProfileSettingDialog.kt @@ -0,0 +1,140 @@ +package com.wap.wapp.feature.profile.profilesetting.component + +import androidx.annotation.StringRes +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Divider +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.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import com.wap.designsystem.WappTheme +import com.wap.wapp.feature.profile.R.string + +@Composable +internal fun ProfileSettingDialog( + onDismissRequest: () -> Unit, + onConfirmRequest: () -> Unit = {}, + @StringRes title: Int, +) { + Dialog( + onDismissRequest = onDismissRequest, + properties = DialogProperties( + usePlatformDefaultWidth = false, + ), + ) { + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .wrapContentSize() + .padding(horizontal = 30.dp) + .clip(RoundedCornerShape(8.dp)) + .background(WappTheme.colors.black25), + ) { + Text( + text = stringResource(title), + style = WappTheme.typography.contentBold.copy(fontSize = 20.sp), + color = WappTheme.colors.yellow34, + modifier = Modifier.padding(top = 16.dp), + ) + + Divider( + color = WappTheme.colors.gray82, + modifier = Modifier.padding(horizontal = 12.dp), + ) + + Text( + text = generateDialogContentString(title = title), + style = WappTheme.typography.contentRegular, + color = WappTheme.colors.white, + modifier = Modifier.padding(top = 12.dp, start = 12.dp, end = 12.dp), + ) + + Row( + horizontalArrangement = Arrangement.spacedBy(20.dp), + modifier = Modifier.padding(horizontal = 12.dp, vertical = 16.dp), + ) { + Button( + onClick = onConfirmRequest, + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors( + containerColor = WappTheme.colors.yellow34, + ), + contentPadding = PaddingValues(vertical = 12.dp), + modifier = Modifier.weight(1f), + ) { + Text( + text = stringResource(string.complete), + style = WappTheme.typography.titleRegular, + color = WappTheme.colors.black, + ) + } + + Button( + onClick = onDismissRequest, + shape = RoundedCornerShape(10.dp), + colors = ButtonDefaults.buttonColors( + containerColor = WappTheme.colors.black25, + ), + contentPadding = PaddingValues(vertical = 12.dp), + modifier = Modifier + .weight(1f) + .border( + width = 1.dp, + color = WappTheme.colors.yellow34, + shape = RoundedCornerShape(8.dp), + ), + ) { + Text( + text = stringResource(string.cancel), + style = WappTheme.typography.titleRegular, + color = WappTheme.colors.yellow34, + ) + } + } + } + } +} + +@Composable +private fun generateDialogContentString(@StringRes title: Int) = buildAnnotatedString { + append("정말로 ") + withStyle( + style = SpanStyle( + textDecoration = TextDecoration.Underline, + color = WappTheme.colors.yellow34, + ), + ) { + append(stringResource(title)) + } + append("을 원하신다면 ") + withStyle( + style = SpanStyle( + textDecoration = TextDecoration.Underline, + color = WappTheme.colors.yellow34, + ), + ) { + append("완료") + } + append(" 버튼을 눌러주세요.") +} diff --git a/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/component/UserProfile.kt b/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/component/UserProfile.kt new file mode 100644 index 000000000..00fc9949f --- /dev/null +++ b/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/component/UserProfile.kt @@ -0,0 +1,237 @@ +package com.wap.wapp.feature.profile.profilesetting.component + +import androidx.compose.foundation.Image +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.wap.designsystem.WappTheme +import com.wap.designsystem.component.CircleLoader +import com.wap.designsystem.component.WappCard +import com.wap.wapp.core.commmon.util.DateUtil +import com.wap.wapp.core.designresource.R.drawable +import com.wap.wapp.core.model.event.Event +import com.wap.wapp.feature.profile.ProfileViewModel +import com.wap.wapp.feature.profile.R +import com.wap.wapp.feature.profile.component.WappAttendacneRow +import com.wap.wapp.feature.profile.component.WappSurveyHistoryRow + +@Composable +internal fun UserProfile( + todayEventsState: ProfileViewModel.EventsState, + recentEventsState: ProfileViewModel.EventsState, + userRespondedSurveysState: ProfileViewModel.SurveysState, +) { + Column(modifier = Modifier.padding(horizontal = 10.dp)) { + ProfileAttendanceCard( + todayEventsState = todayEventsState, + modifier = Modifier.padding(top = 20.dp), + ) + + MyAttendanceStatus( + recentEventsState = recentEventsState, + modifier = Modifier.padding(top = 20.dp), + ) + + MySurveyHistory( + userRespondedSurveysState = userRespondedSurveysState, + modifier = Modifier.padding(vertical = 20.dp), + ) + } +} + +@Composable +private fun ProfileAttendanceCard( + todayEventsState: ProfileViewModel.EventsState, + modifier: Modifier, +) { + when (todayEventsState) { + is ProfileViewModel.EventsState.Loading -> CircleLoader(modifier = Modifier.fillMaxSize()) + is ProfileViewModel.EventsState.Success -> { + WappCard( + modifier = modifier + .fillMaxWidth() + .wrapContentHeight(), + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 15.dp, vertical = 10.dp), + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = stringResource(id = R.string.wap_attendance), + style = WappTheme.typography.captionBold.copy(fontSize = 20.sp), + color = WappTheme.colors.white, + ) + + Image( + painter = painterResource(id = drawable.ic_check), + contentDescription = "", + modifier = Modifier.padding(start = 10.dp), + ) + } + Text( + text = DateUtil.generateNowDate().format(DateUtil.yyyyMMddFormatter), + style = WappTheme.typography.contentRegular, + color = WappTheme.colors.white, + modifier = Modifier.padding(top = 20.dp), + ) + + if (todayEventsState.events.isEmpty()) { + Text( + text = stringResource(id = R.string.no_event_today), + style = WappTheme.typography.contentRegular.copy(fontSize = 20.sp), + color = WappTheme.colors.white, + modifier = Modifier.padding(top = 5.dp), + ) + } else { + Text( + text = generateTodayEventString(events = todayEventsState.events), + style = WappTheme.typography.contentRegular.copy(fontSize = 20.sp), + color = WappTheme.colors.white, + modifier = Modifier.padding(top = 5.dp), + ) + } + } + } + } + } +} + +@Composable +private fun MyAttendanceStatus( + recentEventsState: ProfileViewModel.EventsState, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier) { + Text( + text = stringResource(id = R.string.my_attendance), + style = WappTheme.typography.titleBold.copy(fontSize = 20.sp), + color = WappTheme.colors.white, + modifier = Modifier.padding(start = 5.dp), + ) + + WappCard( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(top = 10.dp), + ) { + when (recentEventsState) { + is ProfileViewModel.EventsState.Loading -> CircleLoader( + modifier = Modifier + .padding(vertical = 10.dp) + .height(130.dp), + ) + + is ProfileViewModel.EventsState.Success -> { + if (recentEventsState.events.isEmpty()) { + NothingToShow(title = R.string.no_events_recently) + return@WappCard + } + + LazyColumn( + verticalArrangement = Arrangement.spacedBy(10.dp), + modifier = Modifier + .padding(10.dp) + .height(130.dp), + ) { + items( + items = recentEventsState.events, + key = { event -> event.eventId }, + ) { event -> + WappAttendacneRow(isAttendance = true, event = event) + } + } + } + } + } + } +} + +@Composable +private fun MySurveyHistory( + userRespondedSurveysState: ProfileViewModel.SurveysState, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier) { + Text( + text = stringResource(id = R.string.survey_i_did), + style = WappTheme.typography.titleBold.copy(fontSize = 20.sp), + color = WappTheme.colors.white, + modifier = Modifier.padding(start = 5.dp), + ) + + WappCard( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(top = 10.dp), + ) { + when (userRespondedSurveysState) { + is ProfileViewModel.SurveysState.Loading -> CircleLoader( + modifier = Modifier + .padding(vertical = 10.dp) + .height(130.dp), + ) + + is ProfileViewModel.SurveysState.Success -> { + if (userRespondedSurveysState.surveys.isEmpty()) { + NothingToShow(title = R.string.no_surveys_after_sign_up) + return@WappCard + } + + LazyColumn( + verticalArrangement = Arrangement.spacedBy(10.dp), + modifier = Modifier + .padding(10.dp) + .height(130.dp), + ) { + items( + items = userRespondedSurveysState.surveys, + key = { survey -> survey.surveyId }, + ) { survey -> + WappSurveyHistoryRow(survey) + } + } + } + } + } + } +} + +@Composable +private fun generateTodayEventString(events: List) = buildAnnotatedString { + append("오늘은 ") + + withStyle( + style = SpanStyle( + fontWeight = FontWeight.Bold, + textDecoration = TextDecoration.Underline, + ), + ) { + append(events.map { it.title }.joinToString(separator = ", ")) + } + + append(" 날 이에요!") +} diff --git a/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/navigation/ProfileSettingNavigation.kt b/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/navigation/ProfileSettingNavigation.kt index fbea9ac86..620e3d00b 100644 --- a/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/navigation/ProfileSettingNavigation.kt +++ b/feature/profile/src/main/java/com/wap/wapp/feature/profile/profilesetting/navigation/ProfileSettingNavigation.kt @@ -3,18 +3,36 @@ package com.wap.wapp.feature.profile.profilesetting.navigation 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.profile.profilesetting.ProfileSettingRoute -const val profileSettingNavigationRoute = "profile_setting_route" +const val profileSettingNavigationRoute = "profile_setting_route/{userId}" -fun NavController.navigateToProfileSetting(navOptions: NavOptions? = navOptions {}) { - this.navigate(profileSettingNavigationRoute, navOptions) +fun NavController.navigateToProfileSetting( + userId: String, + navOptions: NavOptions? = navOptions {}, +) { + this.navigate("profile_setting_route/$userId", navOptions) } -fun NavGraphBuilder.profileSettingScreen(navigateToProfile: () -> Unit) { - composable(route = profileSettingNavigationRoute) { - ProfileSettingRoute(navigateToProfile) +fun NavGraphBuilder.profileSettingScreen( + navigateToSignIn: () -> Unit, + navigateToProfile: () -> Unit, +) { + composable( + route = profileSettingNavigationRoute, + arguments = listOf( + navArgument("userId") { type = NavType.StringType }, + ), + ) { navBackStackEntry -> + val userId = navBackStackEntry.arguments?.getString("userId") ?: "" + ProfileSettingRoute( + userId = userId, + navigateToSignIn = navigateToSignIn, + navigateToProfile = navigateToProfile, + ) } } diff --git a/feature/profile/src/main/java/com/wap/wapp/feature/profile/screen/UserProfile.kt b/feature/profile/src/main/java/com/wap/wapp/feature/profile/screen/UserProfile.kt deleted file mode 100644 index 8f7f15096..000000000 --- a/feature/profile/src/main/java/com/wap/wapp/feature/profile/screen/UserProfile.kt +++ /dev/null @@ -1,190 +0,0 @@ -package com.wap.wapp.feature.profile.screen - -import androidx.compose.foundation.Image -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.layout.wrapContentHeight -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextDecoration -import androidx.compose.ui.text.withStyle -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.wap.designsystem.WappTheme -import com.wap.designsystem.component.CircleLoader -import com.wap.designsystem.component.WappCard -import com.wap.wapp.core.commmon.util.DateUtil -import com.wap.wapp.core.designresource.R.drawable -import com.wap.wapp.core.model.event.Event -import com.wap.wapp.feature.profile.ProfileViewModel -import com.wap.wapp.feature.profile.R -import com.wap.wapp.feature.profile.component.WappAttendacneRow -import com.wap.wapp.feature.profile.component.WappSurveyHistoryRow - -@Composable -internal fun UserProfile(eventsState: ProfileViewModel.EventsState) { - Column(modifier = Modifier.padding(horizontal = 10.dp)) { - handleMonthEventsState(eventsState = eventsState) - - MyAttendanceStatus(modifier = Modifier.padding(top = 20.dp)) - - MySurveyHistory(modifier = Modifier.padding(vertical = 20.dp)) - } -} - -@Composable -private fun handleMonthEventsState( - eventsState: ProfileViewModel.EventsState, -) = when (eventsState) { - is ProfileViewModel.EventsState.Loading -> CircleLoader(modifier = Modifier.fillMaxSize()) - is ProfileViewModel.EventsState.Success -> { - ProfileAttendanceCard( - events = eventsState.events, - modifier = Modifier.padding(top = 20.dp), - ) - } - - is ProfileViewModel.EventsState.Failure -> {} -} - -@Composable -private fun ProfileAttendanceCard( - events: List, - modifier: Modifier, -) { - WappCard( - modifier = modifier - .fillMaxWidth() - .wrapContentHeight(), - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 15.dp, vertical = 10.dp), - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - text = stringResource(id = R.string.wap_attendance), - style = WappTheme.typography.captionBold.copy(fontSize = 20.sp), - color = WappTheme.colors.white, - ) - - Image( - painter = painterResource(id = drawable.ic_check), - contentDescription = "", - modifier = Modifier.padding(start = 10.dp), - ) - } - - Text( - text = DateUtil.generateNowDate().format(DateUtil.yyyyMMddFormatter), - style = WappTheme.typography.contentRegular, - color = WappTheme.colors.white, - modifier = Modifier.padding(top = 20.dp), - ) - - if (events.isEmpty()) { - Text( - text = stringResource(id = R.string.no_event_today), - style = WappTheme.typography.contentRegular.copy(fontSize = 20.sp), - color = WappTheme.colors.white, - modifier = Modifier.padding(top = 5.dp), - ) - } else { - Text( - text = generateTodayEventString(events = events), - style = WappTheme.typography.contentRegular.copy(fontSize = 20.sp), - color = WappTheme.colors.white, - modifier = Modifier.padding(top = 5.dp), - ) - } - } - } -} - -@Composable -private fun MyAttendanceStatus(modifier: Modifier = Modifier) { - Column(modifier = modifier) { - Text( - text = stringResource(id = R.string.my_attendance), - style = WappTheme.typography.titleBold.copy(fontSize = 20.sp), - color = WappTheme.colors.white, - modifier = Modifier.padding(start = 5.dp), - ) - - WappCard( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(top = 10.dp), - ) { - Column( - verticalArrangement = Arrangement.spacedBy(10.dp), - modifier = Modifier.padding(vertical = 10.dp), - ) { - WappAttendacneRow(isAttendance = false) - WappAttendacneRow(isAttendance = true) - WappAttendacneRow(isAttendance = false) - WappAttendacneRow(isAttendance = true) - } - } - } -} - -@Composable -private fun MySurveyHistory(modifier: Modifier = Modifier) { - Column(modifier = modifier) { - Text( - text = stringResource(id = R.string.survey_i_did), - style = WappTheme.typography.titleBold.copy(fontSize = 20.sp), - color = WappTheme.colors.white, - modifier = Modifier.padding(start = 5.dp), - ) - - WappCard( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(top = 10.dp), - ) { - Column( - verticalArrangement = Arrangement.spacedBy(10.dp), - modifier = Modifier.padding(vertical = 10.dp), - ) { - WappSurveyHistoryRow() - WappSurveyHistoryRow() - WappSurveyHistoryRow() - WappSurveyHistoryRow() - } - } - } -} - -@Composable -private fun generateTodayEventString(events: List) = buildAnnotatedString { - append("오늘은") - - withStyle( - style = SpanStyle( - fontWeight = FontWeight.Bold, - textDecoration = TextDecoration.Underline, - ), - ) { - events.forEach { event -> - append(event.title + ", ") - } - } - - append("날 이에요!") -} diff --git a/feature/profile/src/main/res/values/strings.xml b/feature/profile/src/main/res/values/strings.xml index 235150c4d..d56ef8a9e 100644 --- a/feature/profile/src/main/res/values/strings.xml +++ b/feature/profile/src/main/res/values/strings.xml @@ -11,9 +11,11 @@ 내가 한 설문 계정 설정 알람 설정 - 로그아웃 - 회원 탈퇴 + 로그아웃 + 회원탈퇴 문의하기 + 완료 + 취소 더보기 FAQ 약관 및 정책 @@ -21,4 +23,6 @@ 로그인 하러 가기 WAP 출석 오늘은 별 다른 행사가 없어요! + 가입한 이후로 참여한 설문이 없어요! + 가입한 이후로 진행된 일정이 없어요!