diff --git a/core/model/src/main/java/com/susu/core/model/OnboardVote.kt b/core/model/src/main/java/com/susu/core/model/OnboardVote.kt new file mode 100644 index 00000000..529cc1fb --- /dev/null +++ b/core/model/src/main/java/com/susu/core/model/OnboardVote.kt @@ -0,0 +1,9 @@ +package com.susu.core.model + +import androidx.compose.runtime.Stable + +@Stable +data class OnboardVote( + val mostContent: String, + val mostPercentage: Int, +) diff --git a/data/src/main/java/com/susu/data/data/di/RepositoryModule.kt b/data/src/main/java/com/susu/data/data/di/RepositoryModule.kt index 20b0792d..4effc251 100644 --- a/data/src/main/java/com/susu/data/data/di/RepositoryModule.kt +++ b/data/src/main/java/com/susu/data/data/di/RepositoryModule.kt @@ -9,6 +9,7 @@ import com.susu.data.data.repository.FriendRepositoryImpl import com.susu.data.data.repository.LedgerRecentSearchRepositoryImpl import com.susu.data.data.repository.LedgerRepositoryImpl import com.susu.data.data.repository.LoginRepositoryImpl +import com.susu.data.data.repository.OnboardRepositoryImpl import com.susu.data.data.repository.ReportRepositoryImpl import com.susu.data.data.repository.SignUpRepositoryImpl import com.susu.data.data.repository.StatisticsRepositoryImpl @@ -26,6 +27,7 @@ import com.susu.domain.repository.FriendRepository import com.susu.domain.repository.LedgerRecentSearchRepository import com.susu.domain.repository.LedgerRepository import com.susu.domain.repository.LoginRepository +import com.susu.domain.repository.OnboardRepository import com.susu.domain.repository.ReportRepository import com.susu.domain.repository.SignUpRepository import com.susu.domain.repository.StatisticsRepository @@ -127,4 +129,9 @@ abstract class RepositoryModule { abstract fun bindReportRepository( reportRepositoryImpl: ReportRepositoryImpl, ): ReportRepository + + @Binds + abstract fun bindOnboardRepository( + onboardRepositoryImpl: OnboardRepositoryImpl, + ): OnboardRepository } diff --git a/data/src/main/java/com/susu/data/data/repository/OnboardRepositoryImpl.kt b/data/src/main/java/com/susu/data/data/repository/OnboardRepositoryImpl.kt new file mode 100644 index 00000000..cfbc8e78 --- /dev/null +++ b/data/src/main/java/com/susu/data/data/repository/OnboardRepositoryImpl.kt @@ -0,0 +1,20 @@ +package com.susu.data.data.repository + +import com.susu.core.model.OnboardVote +import com.susu.data.remote.api.OnboardService +import com.susu.domain.repository.OnboardRepository +import javax.inject.Inject + +class OnboardRepositoryImpl @Inject constructor( + private val onboardService: OnboardService, +) : OnboardRepository { + override suspend fun getOnboardVote(): OnboardVote { + val result = onboardService.getOnboardVote().getOrThrow().options + val mostOption = result.maxBy { it.count } + + return OnboardVote( + mostContent = mostOption.content, + mostPercentage = (mostOption.count.toFloat() / result.sumOf { it.count } * 100).toInt(), + ) + } +} diff --git a/data/src/main/java/com/susu/data/remote/api/OnboardService.kt b/data/src/main/java/com/susu/data/remote/api/OnboardService.kt new file mode 100644 index 00000000..676a092b --- /dev/null +++ b/data/src/main/java/com/susu/data/remote/api/OnboardService.kt @@ -0,0 +1,10 @@ +package com.susu.data.remote.api + +import com.susu.data.remote.model.response.OnboardVoteResponse +import com.susu.data.remote.retrofit.ApiResult +import retrofit2.http.GET + +interface OnboardService { + @GET("votes/onboarding") + suspend fun getOnboardVote(): ApiResult +} diff --git a/data/src/main/java/com/susu/data/remote/di/ApiServiceModule.kt b/data/src/main/java/com/susu/data/remote/di/ApiServiceModule.kt index 6b5041c4..38edfbf2 100644 --- a/data/src/main/java/com/susu/data/remote/di/ApiServiceModule.kt +++ b/data/src/main/java/com/susu/data/remote/di/ApiServiceModule.kt @@ -6,6 +6,7 @@ import com.susu.data.remote.api.CategoryService import com.susu.data.remote.api.EnvelopesService import com.susu.data.remote.api.FriendService import com.susu.data.remote.api.LedgerService +import com.susu.data.remote.api.OnboardService import com.susu.data.remote.api.ReportService import com.susu.data.remote.api.SignUpService import com.susu.data.remote.api.StatisticsService @@ -101,4 +102,10 @@ object ApiServiceModule { fun providesBlockService(retrofit: Retrofit): BlockService { return retrofit.create(BlockService::class.java) } + + @Singleton + @Provides + fun providesOnboardService(retrofit: Retrofit): OnboardService { + return retrofit.create(OnboardService::class.java) + } } diff --git a/data/src/main/java/com/susu/data/remote/model/response/OnboardVoteResponse.kt b/data/src/main/java/com/susu/data/remote/model/response/OnboardVoteResponse.kt new file mode 100644 index 00000000..db9aeb30 --- /dev/null +++ b/data/src/main/java/com/susu/data/remote/model/response/OnboardVoteResponse.kt @@ -0,0 +1,14 @@ +package com.susu.data.remote.model.response + +import kotlinx.serialization.Serializable + +@Serializable +data class OnboardVoteResponse( + val options: List, +) + +@Serializable +data class OnboardVoteOption( + val content: String = "", + val count: Int = 0, +) diff --git a/domain/src/main/java/com/susu/domain/repository/OnboardRepository.kt b/domain/src/main/java/com/susu/domain/repository/OnboardRepository.kt new file mode 100644 index 00000000..f1686555 --- /dev/null +++ b/domain/src/main/java/com/susu/domain/repository/OnboardRepository.kt @@ -0,0 +1,7 @@ +package com.susu.domain.repository + +import com.susu.core.model.OnboardVote + +interface OnboardRepository { + suspend fun getOnboardVote(): OnboardVote +} diff --git a/domain/src/main/java/com/susu/domain/usecase/loginsignup/GetOnboardVoteUseCase.kt b/domain/src/main/java/com/susu/domain/usecase/loginsignup/GetOnboardVoteUseCase.kt new file mode 100644 index 00000000..22ff2c31 --- /dev/null +++ b/domain/src/main/java/com/susu/domain/usecase/loginsignup/GetOnboardVoteUseCase.kt @@ -0,0 +1,14 @@ +package com.susu.domain.usecase.loginsignup + +import com.susu.core.common.runCatchingIgnoreCancelled +import com.susu.domain.repository.OnboardRepository +import javax.inject.Inject + +class GetOnboardVoteUseCase @Inject constructor( + private val onboardRepository: OnboardRepository, +) { + + suspend operator fun invoke() = runCatchingIgnoreCancelled { + onboardRepository.getOnboardVote() + } +} diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginArchGraph.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginArchGraph.kt index 75b0f322..91449474 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginArchGraph.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginArchGraph.kt @@ -27,7 +27,7 @@ fun LoginArchGraph( ) { val fillAngle = remember { Animatable(fillFrom) } - LaunchedEffect(key1 = Unit) { + LaunchedEffect(key1 = fillUntil) { fillAngle.animateTo( targetValue = fillUntil, animationSpec = tween( diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt index c22a5b99..16665e78 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginContract.kt @@ -1,5 +1,6 @@ package com.susu.feature.loginsignup.login +import com.susu.core.model.OnboardVote import com.susu.core.ui.base.SideEffect import com.susu.core.ui.base.UiState @@ -11,4 +12,5 @@ sealed interface LoginEffect : SideEffect { data class LoginState( val isLoading: Boolean = false, + val onboardVote: OnboardVote? = null, ) : UiState diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt index c9ab7d72..65ba5a38 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginScreen.kt @@ -46,6 +46,7 @@ import com.susu.core.designsystem.theme.Orange60 import com.susu.core.designsystem.theme.SusuTheme import com.susu.core.ui.SnackbarToken import com.susu.core.ui.SnsProviders +import com.susu.core.ui.extension.collectWithLifecycle import com.susu.core.ui.extension.susuClickable import com.susu.feature.loginsignup.R import com.susu.feature.loginsignup.social.KakaoLoginHelper @@ -65,16 +66,18 @@ fun LoginRoute( } } - LaunchedEffect(key1 = viewModel.sideEffect) { - viewModel.sideEffect.collect { sideEffect -> - when (sideEffect) { - is LoginEffect.ShowSnackBar -> onShowSnackBar(SnackbarToken(message = sideEffect.message)) - LoginEffect.NavigateToReceived -> navigateToReceived() - LoginEffect.NavigateToSignUp -> navigateToSignUp() - } + viewModel.sideEffect.collectWithLifecycle { sideEffect -> + when (sideEffect) { + is LoginEffect.ShowSnackBar -> onShowSnackBar(SnackbarToken(message = sideEffect.message)) + LoginEffect.NavigateToReceived -> navigateToReceived() + LoginEffect.NavigateToSignUp -> navigateToSignUp() } } + LaunchedEffect(key1 = Unit) { + viewModel.initData() + } + LoginScreen( uiState = uiState, transitionState = transitionState, @@ -131,25 +134,48 @@ fun LoginScreen( Spacer(modifier = Modifier.height(SusuTheme.spacing.spacing_xxxxs)) Text(text = stringResource(R.string.login_sub_header), style = SusuTheme.typography.title_m, color = Gray50) Spacer(modifier = Modifier.height(64.dp)) - LoginArchGraph(modifier = Modifier.size(200.dp)) + + LoginArchGraph( + modifier = Modifier.size(200.dp), + fillUntil = uiState.onboardVote?.let { it.mostPercentage.toFloat() / 100f } ?: 0.85f, + ) + Spacer(modifier = Modifier.height(SusuTheme.spacing.spacing_xxxxl)) + Row( horizontalArrangement = Arrangement.spacedBy(SusuTheme.spacing.spacing_xxxxs), ) { Text(text = stringResource(R.string.login_statistics_1), style = SusuTheme.typography.title_s) - LoginBlankText(text = "87%", state = transitionState) // TODO: 임시 하드코딩 + LoginBlankText( + text = stringResource( + id = R.string.login_percentage_format, + uiState.onboardVote?.mostPercentage?.toString() + ?: stringResource(id = R.string.login_statistics_unknown_percentage), + ), + state = transitionState, + ) Text(text = stringResource(R.string.login_statistics_2), style = SusuTheme.typography.title_s) } + Spacer(modifier = Modifier.height(SusuTheme.spacing.spacing_xxs)) + Row( horizontalArrangement = Arrangement.spacedBy(SusuTheme.spacing.spacing_xxxxs), ) { - LoginBlankText(text = "10만원", state = transitionState) // TODO: 임시 하드코딩 + LoginBlankText( + text = uiState.onboardVote?.mostContent + ?: stringResource(id = R.string.login_statistics_unknown_content), + state = transitionState, + ) Text(text = stringResource(R.string.login_statistics_3), style = SusuTheme.typography.title_s) } + Spacer(modifier = Modifier.height(64.dp)) + KakaoLoginButton(onClick = onLoginClick) - Spacer(modifier = Modifier.height(24.dp)) + + Spacer(modifier = Modifier.weight(1f)) + Image( modifier = Modifier.align(Alignment.CenterHorizontally), painter = painterResource(id = com.susu.core.designsystem.R.drawable.ic_susu_logo_weak), diff --git a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt index c1e7f6e6..5e2708b1 100644 --- a/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt +++ b/feature/loginsignup/src/main/java/com/susu/feature/loginsignup/login/LoginViewModel.kt @@ -5,6 +5,7 @@ import com.susu.core.model.exception.UnknownException import com.susu.core.ui.SnsProviders import com.susu.core.ui.base.BaseViewModel import com.susu.domain.usecase.loginsignup.CheckCanRegisterUseCase +import com.susu.domain.usecase.loginsignup.GetOnboardVoteUseCase import com.susu.domain.usecase.loginsignup.LoginUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch @@ -14,8 +15,17 @@ import javax.inject.Inject class LoginViewModel @Inject constructor( private val checkCanRegisterUseCase: CheckCanRegisterUseCase, private val loginUseCase: LoginUseCase, + private val getOnboardVoteUseCase: GetOnboardVoteUseCase, ) : BaseViewModel(LoginState()) { + fun initData() = viewModelScope.launch { + getOnboardVoteUseCase().onSuccess { + intent { copy(onboardVote = it) } + }.onFailure { + intent { copy(onboardVote = null) } + } + } + fun login(provider: SnsProviders, oauthAccessToken: String) { viewModelScope.launch { intent { copy(isLoading = true) } diff --git a/feature/loginsignup/src/main/res/values/strings.xml b/feature/loginsignup/src/main/res/values/strings.xml index 7931166a..0404248a 100644 --- a/feature/loginsignup/src/main/res/values/strings.xml +++ b/feature/loginsignup/src/main/res/values/strings.xml @@ -10,6 +10,9 @@ 수수 가입자 이 적당하다고 답했어요 + %s%% + \?만원 + \? 친구의 결혼식, 축의금 "은 "