diff --git a/app/build.gradle.kts b/app/build.gradle.kts index aa170be0..bc85d4e6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -15,7 +15,10 @@ android { } dependencies { + implementation(project(":core:common")) implementation(project(":core:design-system")) + implementation(project(":core:data")) + implementation(project(":core:model")) implementation(project(":feature:login")) implementation(project(":feature:sign-up")) implementation(project(":feature:main")) diff --git a/app/src/main/java/com/goms/goms_android_v2/MainActivity.kt b/app/src/main/java/com/goms/goms_android_v2/MainActivity.kt index c4b071a3..8faf31b2 100644 --- a/app/src/main/java/com/goms/goms_android_v2/MainActivity.kt +++ b/app/src/main/java/com/goms/goms_android_v2/MainActivity.kt @@ -5,13 +5,22 @@ import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.OnBackPressedCallback import androidx.activity.compose.setContent +import androidx.activity.viewModels import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.goms.design_system.theme.GomsTheme import com.goms.goms_android_v2.ui.GomsApp import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch @AndroidEntryPoint class MainActivity : ComponentActivity() { @@ -23,15 +32,37 @@ class MainActivity : ComponentActivity() { } } + private val viewModel: MainActivityViewModel by viewModels() + @OptIn(ExperimentalMaterial3WindowSizeClassApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) this.onBackPressedDispatcher.addCallback(this, onBackPressedCallback) + + var uiState: MainActivityUiState by mutableStateOf(MainActivityUiState.Loading) + + lifecycleScope.launch { + lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.uiState + .onEach { uiState = it } + .collect {} + } + } + setContent { - installSplashScreen() - CompositionLocalProvider { - GomsTheme { _, _ -> - GomsApp(windowSizeClass = calculateWindowSizeClass(this)) + installSplashScreen().apply { + setKeepOnScreenCondition { + uiState is MainActivityUiState.Loading + } + } + if (uiState !is MainActivityUiState.Loading) { + CompositionLocalProvider { + GomsTheme { _, _ -> + GomsApp( + windowSizeClass = calculateWindowSizeClass(this), + uiState = uiState + ) + } } } } diff --git a/app/src/main/java/com/goms/goms_android_v2/MainActivityViewModel.kt b/app/src/main/java/com/goms/goms_android_v2/MainActivityViewModel.kt new file mode 100644 index 00000000..58e20139 --- /dev/null +++ b/app/src/main/java/com/goms/goms_android_v2/MainActivityViewModel.kt @@ -0,0 +1,45 @@ +package com.goms.goms_android_v2 + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.goms.common.result.Result +import com.goms.common.result.asResult +import com.goms.data.repository.account.AccountRepository +import com.goms.model.response.account.ProfileResponse +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject + +@HiltViewModel +class MainActivityViewModel @Inject constructor( + private val accountRepository: AccountRepository +) : ViewModel() { + val uiState: StateFlow = flow { + accountRepository.getProfile().collect { profileResponse -> + emit(profileResponse) + } + } + .asResult() + .map { result -> + when (result) { + Result.Loading -> MainActivityUiState.Loading + is Result.Success -> MainActivityUiState.Success(result.data) + is Result.Error -> MainActivityUiState.Error(result.exception) + } + } + .stateIn( + scope = viewModelScope, + initialValue = MainActivityUiState.Loading, + started = SharingStarted.WhileSubscribed(5_000), + ) +} + +sealed interface MainActivityUiState { + object Loading : MainActivityUiState + data class Success(val getProfileResponse: ProfileResponse) : MainActivityUiState + data class Error(val exception: Throwable) : MainActivityUiState +} \ No newline at end of file diff --git a/app/src/main/java/com/goms/goms_android_v2/ui/GomsApp.kt b/app/src/main/java/com/goms/goms_android_v2/ui/GomsApp.kt index 415b03dd..1369ec3f 100644 --- a/app/src/main/java/com/goms/goms_android_v2/ui/GomsApp.kt +++ b/app/src/main/java/com/goms/goms_android_v2/ui/GomsApp.kt @@ -3,16 +3,27 @@ package com.goms.goms_android_v2.ui import androidx.compose.material3.windowsizeclass.WindowSizeClass import androidx.compose.runtime.Composable import com.goms.design_system.theme.GomsTheme +import com.goms.goms_android_v2.MainActivityUiState import com.goms.goms_android_v2.navigation.GomsNavHost +import com.goms.login.navigation.loginRoute +import com.goms.main.navigation.mainRoute @Composable fun GomsApp( windowSizeClass: WindowSizeClass, appState: GomsAppState = rememberBitgoeulAppState( windowSizeClass = windowSizeClass - ) + ), + uiState: MainActivityUiState ) { GomsTheme { _,_ -> - GomsNavHost(appState = appState) + GomsNavHost( + appState = appState, + startDestination = when (uiState) { + is MainActivityUiState.Success -> mainRoute + is MainActivityUiState.Error -> loginRoute + else -> loginRoute + } + ) } } \ No newline at end of file diff --git a/core/data/src/main/java/com/goms/data/di/RepositoryModule.kt b/core/data/src/main/java/com/goms/data/di/RepositoryModule.kt index 661a6c85..0b280e8d 100644 --- a/core/data/src/main/java/com/goms/data/di/RepositoryModule.kt +++ b/core/data/src/main/java/com/goms/data/di/RepositoryModule.kt @@ -1,7 +1,13 @@ package com.goms.data.di +import com.goms.data.repository.account.AccountRepository +import com.goms.data.repository.account.AccountRepositoryImpl import com.goms.data.repository.auth.AuthRepository import com.goms.data.repository.auth.AuthRepositoryImpl +import com.goms.data.repository.late.LateRepository +import com.goms.data.repository.late.LateRepositoryImpl +import com.goms.data.repository.outing.OutingRepository +import com.goms.data.repository.outing.OutingRepositoryImpl import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -14,4 +20,19 @@ abstract class RepositoryModule { abstract fun bindAuthRepository( authRepositoryImpl: AuthRepositoryImpl ): AuthRepository + + @Binds + abstract fun bindAccountRepository( + accountRepositoryImpl: AccountRepositoryImpl + ): AccountRepository + + @Binds + abstract fun bindLateRepository( + lateRepositoryImpl: LateRepositoryImpl + ): LateRepository + + @Binds + abstract fun bindOutingRepository( + outingRepositoryImpl: OutingRepositoryImpl + ): OutingRepository } \ No newline at end of file diff --git a/core/data/src/main/java/com/goms/data/repository/account/AccountRepository.kt b/core/data/src/main/java/com/goms/data/repository/account/AccountRepository.kt new file mode 100644 index 00000000..836ac85b --- /dev/null +++ b/core/data/src/main/java/com/goms/data/repository/account/AccountRepository.kt @@ -0,0 +1,8 @@ +package com.goms.data.repository.account + +import com.goms.model.response.account.ProfileResponse +import kotlinx.coroutines.flow.Flow + +interface AccountRepository { + suspend fun getProfile(): Flow +} \ No newline at end of file diff --git a/core/data/src/main/java/com/goms/data/repository/account/AccountRepositoryImpl.kt b/core/data/src/main/java/com/goms/data/repository/account/AccountRepositoryImpl.kt new file mode 100644 index 00000000..b17268a6 --- /dev/null +++ b/core/data/src/main/java/com/goms/data/repository/account/AccountRepositoryImpl.kt @@ -0,0 +1,14 @@ +package com.goms.data.repository.account + +import com.goms.model.response.account.ProfileResponse +import com.goms.network.datasource.account.AccountDataSource +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class AccountRepositoryImpl @Inject constructor( + private val remoteAccountDataSource: AccountDataSource +) : AccountRepository { + override suspend fun getProfile(): Flow { + return remoteAccountDataSource.getProfile() + } +} \ No newline at end of file diff --git a/core/data/src/main/java/com/goms/data/repository/late/LateRepository.kt b/core/data/src/main/java/com/goms/data/repository/late/LateRepository.kt new file mode 100644 index 00000000..96e72e1e --- /dev/null +++ b/core/data/src/main/java/com/goms/data/repository/late/LateRepository.kt @@ -0,0 +1,8 @@ +package com.goms.data.repository.late + +import com.goms.model.response.late.RankResponse +import kotlinx.coroutines.flow.Flow + +interface LateRepository { + suspend fun getLateRankList(): Flow> +} \ No newline at end of file diff --git a/core/data/src/main/java/com/goms/data/repository/late/LateRepositoryImpl.kt b/core/data/src/main/java/com/goms/data/repository/late/LateRepositoryImpl.kt new file mode 100644 index 00000000..99199c12 --- /dev/null +++ b/core/data/src/main/java/com/goms/data/repository/late/LateRepositoryImpl.kt @@ -0,0 +1,14 @@ +package com.goms.data.repository.late + +import com.goms.model.response.late.RankResponse +import com.goms.network.datasource.late.LateDataSource +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class LateRepositoryImpl @Inject constructor( + private val remoteLateDataSource: LateDataSource +) : LateRepository { + override suspend fun getLateRankList(): Flow> { + return remoteLateDataSource.getLateRankList() + } +} \ No newline at end of file diff --git a/core/data/src/main/java/com/goms/data/repository/outing/OutingRepository.kt b/core/data/src/main/java/com/goms/data/repository/outing/OutingRepository.kt new file mode 100644 index 00000000..65ff8923 --- /dev/null +++ b/core/data/src/main/java/com/goms/data/repository/outing/OutingRepository.kt @@ -0,0 +1,11 @@ +package com.goms.data.repository.outing + +import com.goms.model.response.outing.CountResponse +import com.goms.model.response.outing.OutingResponse +import kotlinx.coroutines.flow.Flow + +interface OutingRepository { + suspend fun getOutingList(): Flow> + + suspend fun getOutingCount(): Flow +} \ No newline at end of file diff --git a/core/data/src/main/java/com/goms/data/repository/outing/OutingRepositoryImpl.kt b/core/data/src/main/java/com/goms/data/repository/outing/OutingRepositoryImpl.kt new file mode 100644 index 00000000..c7d6bf12 --- /dev/null +++ b/core/data/src/main/java/com/goms/data/repository/outing/OutingRepositoryImpl.kt @@ -0,0 +1,19 @@ +package com.goms.data.repository.outing + +import com.goms.model.response.outing.CountResponse +import com.goms.model.response.outing.OutingResponse +import com.goms.network.datasource.outing.OutingDataSource +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class OutingRepositoryImpl @Inject constructor( + private val remoteOutingDataSource: OutingDataSource +) : OutingRepository { + override suspend fun getOutingList(): Flow> { + return remoteOutingDataSource.getOutingList() + } + + override suspend fun getOutingCount(): Flow { + return remoteOutingDataSource.getOutingCount() + } +} \ No newline at end of file diff --git a/core/datastore/src/main/java/com/goms/datastore/AuthTokenDataSource.kt b/core/datastore/src/main/java/com/goms/datastore/AuthTokenDataSource.kt index d9b99721..472e60ef 100644 --- a/core/datastore/src/main/java/com/goms/datastore/AuthTokenDataSource.kt +++ b/core/datastore/src/main/java/com/goms/datastore/AuthTokenDataSource.kt @@ -51,7 +51,7 @@ class AuthTokenDataSource @Inject constructor( suspend fun setRefreshTokenExp(refreshTokenExp: String) { authToken.updateData { it.toBuilder() - .setRefreshToken(refreshTokenExp) + .setRefreshExp(refreshTokenExp) .build() } } diff --git a/core/design-system/src/main/res/drawable/ic_profile.xml b/core/design-system/src/main/res/drawable/ic_profile.xml new file mode 100644 index 00000000..7264e429 --- /dev/null +++ b/core/design-system/src/main/res/drawable/ic_profile.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/core/domain/src/main/java/com/goms/domain/account/GetProfileUseCase.kt b/core/domain/src/main/java/com/goms/domain/account/GetProfileUseCase.kt new file mode 100644 index 00000000..b447621c --- /dev/null +++ b/core/domain/src/main/java/com/goms/domain/account/GetProfileUseCase.kt @@ -0,0 +1,13 @@ +package com.goms.domain.account + +import com.goms.data.repository.account.AccountRepository +import com.goms.model.response.account.ProfileResponse +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GetProfileUseCase @Inject constructor( + private val accountRepository: AccountRepository +) { + suspend operator fun invoke(): Flow = + accountRepository.getProfile() +} \ No newline at end of file diff --git a/core/domain/src/main/java/com/goms/domain/late/GetLateRankListUseCase.kt b/core/domain/src/main/java/com/goms/domain/late/GetLateRankListUseCase.kt new file mode 100644 index 00000000..fa60d265 --- /dev/null +++ b/core/domain/src/main/java/com/goms/domain/late/GetLateRankListUseCase.kt @@ -0,0 +1,13 @@ +package com.goms.domain.late + +import com.goms.data.repository.late.LateRepository +import com.goms.model.response.late.RankResponse +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GetLateRankListUseCase @Inject constructor( + private val lateRepository: LateRepository +) { + suspend operator fun invoke(): Flow> = + lateRepository.getLateRankList() +} \ No newline at end of file diff --git a/core/domain/src/main/java/com/goms/domain/outing/GetOutingCountUseCase.kt b/core/domain/src/main/java/com/goms/domain/outing/GetOutingCountUseCase.kt new file mode 100644 index 00000000..cc22e189 --- /dev/null +++ b/core/domain/src/main/java/com/goms/domain/outing/GetOutingCountUseCase.kt @@ -0,0 +1,13 @@ +package com.goms.domain.outing + +import com.goms.data.repository.outing.OutingRepository +import com.goms.model.response.outing.CountResponse +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GetOutingCountUseCase @Inject constructor( + private val outingRepository: OutingRepository +) { + suspend operator fun invoke(): Flow = + outingRepository.getOutingCount() +} \ No newline at end of file diff --git a/core/domain/src/main/java/com/goms/domain/outing/GetOutingListUseCase.kt b/core/domain/src/main/java/com/goms/domain/outing/GetOutingListUseCase.kt new file mode 100644 index 00000000..c4bce7a1 --- /dev/null +++ b/core/domain/src/main/java/com/goms/domain/outing/GetOutingListUseCase.kt @@ -0,0 +1,13 @@ +package com.goms.domain.outing + +import com.goms.data.repository.outing.OutingRepository +import com.goms.model.response.outing.OutingResponse +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GetOutingListUseCase @Inject constructor( + private val outingRepository: OutingRepository +) { + suspend operator fun invoke(): Flow> = + outingRepository.getOutingList() +} \ No newline at end of file diff --git a/core/model/src/main/java/com/goms/model/enum/Major.kt b/core/model/src/main/java/com/goms/model/enum/Major.kt index ce1aa6b3..3bfdc6f7 100644 --- a/core/model/src/main/java/com/goms/model/enum/Major.kt +++ b/core/model/src/main/java/com/goms/model/enum/Major.kt @@ -4,4 +4,12 @@ enum class Major(val value: String) { SW_DEVELOP("SW"), SMART_IOT("IoT"), AI("AI") +} + +fun Major.toText(): String { + return when (this) { + Major.SW_DEVELOP -> "SW개발" + Major.SMART_IOT -> "IoT" + Major.AI -> "AI" + } } \ No newline at end of file diff --git a/core/model/src/main/java/com/goms/model/response/account/ProfileResponse.kt b/core/model/src/main/java/com/goms/model/response/account/ProfileResponse.kt new file mode 100644 index 00000000..54f3e10f --- /dev/null +++ b/core/model/src/main/java/com/goms/model/response/account/ProfileResponse.kt @@ -0,0 +1,20 @@ +package com.goms.model.response.account + +import com.goms.model.enum.Authority +import com.goms.model.enum.Gender +import com.goms.model.enum.Major +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class ProfileResponse( + @Json(name = "name") val name: String, + @Json(name = "grade") val grade: Int, + @Json(name = "major") val major: Major, + @Json(name = "gender") val gender: Gender, + @Json(name = "authority") val authority: Authority, + @Json(name = "profileUrl") val profileUrl: String?, + @Json(name = "lateCount") val lateCount: Int, + @Json(name = "isOuting") val isOuting: Boolean, + @Json(name = "isBlackList") val isBlackList: Boolean +) diff --git a/core/model/src/main/java/com/goms/model/response/late/RankResponse.kt b/core/model/src/main/java/com/goms/model/response/late/RankResponse.kt new file mode 100644 index 00000000..290477c0 --- /dev/null +++ b/core/model/src/main/java/com/goms/model/response/late/RankResponse.kt @@ -0,0 +1,16 @@ +package com.goms.model.response.late + +import com.goms.model.enum.Gender +import com.goms.model.enum.Major +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class RankResponse( + @Json(name = "accountIdx") val accountIdx: String, + @Json(name = "name") val name: String, + @Json(name = "grade") val grade: Int, + @Json(name = "major") val major: Major, + @Json(name = "gender") val gender: Gender, + @Json(name = "profileUrl") val profileUrl: String? +) \ No newline at end of file diff --git a/core/model/src/main/java/com/goms/model/response/outing/CountResponse.kt b/core/model/src/main/java/com/goms/model/response/outing/CountResponse.kt new file mode 100644 index 00000000..2c272f4f --- /dev/null +++ b/core/model/src/main/java/com/goms/model/response/outing/CountResponse.kt @@ -0,0 +1,9 @@ +package com.goms.model.response.outing + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class CountResponse( + @Json(name = "outingCount") val outingCount: Int +) \ No newline at end of file diff --git a/core/model/src/main/java/com/goms/model/response/outing/OutingResponse.kt b/core/model/src/main/java/com/goms/model/response/outing/OutingResponse.kt new file mode 100644 index 00000000..af093340 --- /dev/null +++ b/core/model/src/main/java/com/goms/model/response/outing/OutingResponse.kt @@ -0,0 +1,17 @@ +package com.goms.model.response.outing + +import com.goms.model.enum.Gender +import com.goms.model.enum.Major +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class OutingResponse( + @Json(name = "accountIdx") val accountIdx: String, + @Json(name = "name") val name: String, + @Json(name = "major") val major: Major, + @Json(name = "grade") val grade: Int, + @Json(name = "gender") val gender: Gender, + @Json(name = "profileUrl") val profileUrl: String?, + @Json(name = "createdTime") val createdTime: String +) \ No newline at end of file diff --git a/core/network/src/main/java/com/goms/network/api/AccountAPI.kt b/core/network/src/main/java/com/goms/network/api/AccountAPI.kt new file mode 100644 index 00000000..c7a7d236 --- /dev/null +++ b/core/network/src/main/java/com/goms/network/api/AccountAPI.kt @@ -0,0 +1,9 @@ +package com.goms.network.api + +import com.goms.model.response.account.ProfileResponse +import retrofit2.http.GET + +interface AccountAPI { + @GET("/api/v2/account/profile") + suspend fun getProfile(): ProfileResponse +} \ No newline at end of file diff --git a/core/network/src/main/java/com/goms/network/api/LateAPI.kt b/core/network/src/main/java/com/goms/network/api/LateAPI.kt new file mode 100644 index 00000000..8521a605 --- /dev/null +++ b/core/network/src/main/java/com/goms/network/api/LateAPI.kt @@ -0,0 +1,9 @@ +package com.goms.network.api + +import com.goms.model.response.late.RankResponse +import retrofit2.http.GET + +interface LateAPI { + @GET("/api/v2/late/rank") + suspend fun getLateRankList(): List +} \ No newline at end of file diff --git a/core/network/src/main/java/com/goms/network/api/OutingAPI.kt b/core/network/src/main/java/com/goms/network/api/OutingAPI.kt new file mode 100644 index 00000000..b40fa925 --- /dev/null +++ b/core/network/src/main/java/com/goms/network/api/OutingAPI.kt @@ -0,0 +1,13 @@ +package com.goms.network.api + +import com.goms.model.response.outing.CountResponse +import com.goms.model.response.outing.OutingResponse +import retrofit2.http.GET + +interface OutingAPI { + @GET("/api/v2/outing/") + suspend fun getOutingList(): List + + @GET("/api/v2/outing/count") + suspend fun getOutingCount(): CountResponse +} \ No newline at end of file diff --git a/core/network/src/main/java/com/goms/network/datasource/account/AccountDataSource.kt b/core/network/src/main/java/com/goms/network/datasource/account/AccountDataSource.kt new file mode 100644 index 00000000..54e2744f --- /dev/null +++ b/core/network/src/main/java/com/goms/network/datasource/account/AccountDataSource.kt @@ -0,0 +1,8 @@ +package com.goms.network.datasource.account + +import com.goms.model.response.account.ProfileResponse +import kotlinx.coroutines.flow.Flow + +interface AccountDataSource { + suspend fun getProfile(): Flow +} \ No newline at end of file diff --git a/core/network/src/main/java/com/goms/network/datasource/account/AccountDataSourceImpl.kt b/core/network/src/main/java/com/goms/network/datasource/account/AccountDataSourceImpl.kt new file mode 100644 index 00000000..672f944d --- /dev/null +++ b/core/network/src/main/java/com/goms/network/datasource/account/AccountDataSourceImpl.kt @@ -0,0 +1,22 @@ +package com.goms.network.datasource.account + +import com.goms.model.response.account.ProfileResponse +import com.goms.network.api.AccountAPI +import com.goms.network.util.GomsApiHandler +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import javax.inject.Inject + +class AccountDataSourceImpl @Inject constructor( + private val accountAPI: AccountAPI +) : AccountDataSource { + override suspend fun getProfile(): Flow = flow { + emit( + GomsApiHandler() + .httpRequest { accountAPI.getProfile() } + .sendRequest() + ) + }.flowOn(Dispatchers.IO) +} \ No newline at end of file diff --git a/core/network/src/main/java/com/goms/network/datasource/late/LateDataSource.kt b/core/network/src/main/java/com/goms/network/datasource/late/LateDataSource.kt new file mode 100644 index 00000000..c990a403 --- /dev/null +++ b/core/network/src/main/java/com/goms/network/datasource/late/LateDataSource.kt @@ -0,0 +1,8 @@ +package com.goms.network.datasource.late + +import com.goms.model.response.late.RankResponse +import kotlinx.coroutines.flow.Flow + +interface LateDataSource { + suspend fun getLateRankList(): Flow> +} \ No newline at end of file diff --git a/core/network/src/main/java/com/goms/network/datasource/late/LateDataSourceImpl.kt b/core/network/src/main/java/com/goms/network/datasource/late/LateDataSourceImpl.kt new file mode 100644 index 00000000..d131b1d0 --- /dev/null +++ b/core/network/src/main/java/com/goms/network/datasource/late/LateDataSourceImpl.kt @@ -0,0 +1,22 @@ +package com.goms.network.datasource.late + +import com.goms.model.response.late.RankResponse +import com.goms.network.api.LateAPI +import com.goms.network.util.GomsApiHandler +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import javax.inject.Inject + +class LateDataSourceImpl @Inject constructor( + private val lateAPI: LateAPI +) : LateDataSource { + override suspend fun getLateRankList(): Flow> = flow { + emit( + GomsApiHandler>() + .httpRequest { lateAPI.getLateRankList() } + .sendRequest() + ) + }.flowOn(Dispatchers.IO) +} \ No newline at end of file diff --git a/core/network/src/main/java/com/goms/network/datasource/outing/OutingDataSource.kt b/core/network/src/main/java/com/goms/network/datasource/outing/OutingDataSource.kt new file mode 100644 index 00000000..a1de612a --- /dev/null +++ b/core/network/src/main/java/com/goms/network/datasource/outing/OutingDataSource.kt @@ -0,0 +1,11 @@ +package com.goms.network.datasource.outing + +import com.goms.model.response.outing.CountResponse +import com.goms.model.response.outing.OutingResponse +import kotlinx.coroutines.flow.Flow + +interface OutingDataSource { + suspend fun getOutingList(): Flow> + + suspend fun getOutingCount(): Flow +} \ No newline at end of file diff --git a/core/network/src/main/java/com/goms/network/datasource/outing/OutingDataSourceImpl.kt b/core/network/src/main/java/com/goms/network/datasource/outing/OutingDataSourceImpl.kt new file mode 100644 index 00000000..286000f3 --- /dev/null +++ b/core/network/src/main/java/com/goms/network/datasource/outing/OutingDataSourceImpl.kt @@ -0,0 +1,31 @@ +package com.goms.network.datasource.outing + +import com.goms.model.response.outing.CountResponse +import com.goms.model.response.outing.OutingResponse +import com.goms.network.api.OutingAPI +import com.goms.network.util.GomsApiHandler +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import javax.inject.Inject + +class OutingDataSourceImpl @Inject constructor( + private val outingAPI: OutingAPI +) : OutingDataSource { + override suspend fun getOutingList(): Flow> = flow { + emit( + GomsApiHandler>() + .httpRequest { outingAPI.getOutingList() } + .sendRequest() + ) + }.flowOn(Dispatchers.IO) + + override suspend fun getOutingCount(): Flow = flow { + emit( + GomsApiHandler() + .httpRequest { outingAPI.getOutingCount() } + .sendRequest() + ) + }.flowOn(Dispatchers.IO) +} \ No newline at end of file diff --git a/core/network/src/main/java/com/goms/network/di/NetworkModule.kt b/core/network/src/main/java/com/goms/network/di/NetworkModule.kt index 7e6c46f8..abc5a9f5 100644 --- a/core/network/src/main/java/com/goms/network/di/NetworkModule.kt +++ b/core/network/src/main/java/com/goms/network/di/NetworkModule.kt @@ -2,7 +2,10 @@ package com.goms.network.di import android.util.Log import com.goms.network.BuildConfig +import com.goms.network.api.AccountAPI import com.goms.network.api.AuthAPI +import com.goms.network.api.LateAPI +import com.goms.network.api.OutingAPI import com.goms.network.util.AuthInterceptor import com.squareup.moshi.Moshi import dagger.Module @@ -71,4 +74,22 @@ object NetworkModule { fun provideAuthAPI(retrofit: Retrofit): AuthAPI { return retrofit.create(AuthAPI::class.java) } + + @Provides + @Singleton + fun provideAccountAPI(retrofit: Retrofit): AccountAPI { + return retrofit.create(AccountAPI::class.java) + } + + @Provides + @Singleton + fun provideLateAPI(retrofit: Retrofit): LateAPI { + return retrofit.create(LateAPI::class.java) + } + + @Provides + @Singleton + fun provideOutingAPI(retrofit: Retrofit): OutingAPI { + return retrofit.create(OutingAPI::class.java) + } } \ No newline at end of file diff --git a/core/network/src/main/java/com/goms/network/di/RemoteDataSourceModule.kt b/core/network/src/main/java/com/goms/network/di/RemoteDataSourceModule.kt index b6b2bd64..500fa189 100644 --- a/core/network/src/main/java/com/goms/network/di/RemoteDataSourceModule.kt +++ b/core/network/src/main/java/com/goms/network/di/RemoteDataSourceModule.kt @@ -1,7 +1,13 @@ package com.goms.network.di +import com.goms.network.datasource.account.AccountDataSource +import com.goms.network.datasource.account.AccountDataSourceImpl import com.goms.network.datasource.auth.AuthDataSource import com.goms.network.datasource.auth.AuthDataSourceImpl +import com.goms.network.datasource.late.LateDataSource +import com.goms.network.datasource.late.LateDataSourceImpl +import com.goms.network.datasource.outing.OutingDataSource +import com.goms.network.datasource.outing.OutingDataSourceImpl import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -14,4 +20,19 @@ abstract class RemoteDataSourceModule { abstract fun bindAuthDataSource( authDataSourceImpl: AuthDataSourceImpl ): AuthDataSource + + @Binds + abstract fun bindAccountDataSource( + accountDataSourceImpl: AccountDataSourceImpl + ): AccountDataSource + + @Binds + abstract fun bindLateDataSource( + lateDataSourceImpl: LateDataSourceImpl + ): LateDataSource + + @Binds + abstract fun bindOutingDataSource( + outingDataSourceImpl: OutingDataSourceImpl + ): OutingDataSource } \ No newline at end of file diff --git a/core/network/src/main/java/com/goms/network/util/AuthInterceptor.kt b/core/network/src/main/java/com/goms/network/util/AuthInterceptor.kt index 05daaac5..afe3faae 100644 --- a/core/network/src/main/java/com/goms/network/util/AuthInterceptor.kt +++ b/core/network/src/main/java/com/goms/network/util/AuthInterceptor.kt @@ -1,5 +1,6 @@ package com.goms.network.util +import android.util.Log import com.goms.common.exception.TokenExpirationException import com.goms.datastore.AuthTokenDataSource import com.goms.model.response.auth.LoginResponse @@ -8,7 +9,6 @@ import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking -import kotlinx.serialization.json.JsonObject import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Request @@ -21,18 +21,16 @@ class AuthInterceptor @Inject constructor( ): Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() - val response = chain.proceed(request) val builder = request.newBuilder() val currentTime = System.currentTimeMillis().toGomsTimeDate() - val ignorePath = listOf("/auth") - val ignoreMethod = listOf("POST") + val ignorePath = "/auth" + val ignoreMethodPost = "POST" + val ignoreMethodGet = "GET" val path = request.url.encodedPath val method = request.method - ignorePath.forEachIndexed { index, s -> - if (s == path && ignoreMethod[index] == method) { - return response - } + if (path.contains(ignorePath) && (method == ignoreMethodPost || method == ignoreMethodGet)) { + return chain.proceed(request) } runBlocking { @@ -61,7 +59,7 @@ class AuthInterceptor @Inject constructor( val refreshRequest = Request.Builder() .url(BuildConfig.BASE_URL + "/api/v2/auth") .patch(chain.request().body ?: RequestBody.create(null, byteArrayOf())) - .addHeader("Refresh-Token", refreshTokenWithBearer) + .addHeader("refreshToken", refreshTokenWithBearer) .build() val response = client.newCall(refreshRequest).execute() @@ -78,6 +76,8 @@ class AuthInterceptor @Inject constructor( val accessToken = dataSource.getAccessToken().first().replace("\"", "") builder.addHeader("Authorization", "Bearer $accessToken") } + val response = chain.proceed(builder.build()) + return if (response.code == 204) { response.newBuilder().code(200).build() } else { diff --git a/core/ui/src/main/java/com/goms/ui/MainLateCard.kt b/core/ui/src/main/java/com/goms/ui/MainLateCard.kt deleted file mode 100644 index 010da7ff..00000000 --- a/core/ui/src/main/java/com/goms/ui/MainLateCard.kt +++ /dev/null @@ -1,158 +0,0 @@ -package com.goms.ui - -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import coil.compose.AsyncImage -import com.goms.design_system.component.modifier.gomsClickable -import com.goms.design_system.theme.GomsTheme -import com.goms.model.enum.Authority - -@Composable -fun MainLateCard( - modifier: Modifier = Modifier, - list: List = listOf("1", "2", "3", "4"), - role: Authority, - onClick: () -> Unit -) { - var componentWidth by remember { mutableStateOf(0.dp) } - val density = LocalDensity.current - - GomsTheme { colors, typography -> - Surface( - modifier = modifier - .onGloballyPositioned { - componentWidth = with(density) { - it.size.width.toDp() - } - }, - color = colors.G1, - shape = RoundedCornerShape(12.dp), - border = BorderStroke(width = 1.dp, color = colors.WHITE.copy(0.15f)) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "지각자 TOP 3", - style = typography.titleMedium, - fontWeight = FontWeight.Bold, - color = colors.WHITE - ) - if (role == Authority.ROLE_STUDENT_COUNCIL) { - Text( - modifier = Modifier.gomsClickable { onClick() }, - text = "더보기", - style = typography.buttonSmall, - fontWeight = FontWeight.Normal, - color = colors.G7 - ) - } - } - Spacer(modifier = Modifier.height(16.dp)) - if (list.isEmpty()) { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Text( - text = "\uD83D\uDC4D", - fontSize = 80.sp, - fontWeight = FontWeight.Bold, - color = colors.WHITE - ) - Text( - text = "지각자가 없어요! 놀랍게도...", - style = typography.textMedium, - fontWeight = FontWeight.SemiBold, - color = colors.G4 - ) - } - } else { - LazyRow(modifier = Modifier.fillMaxWidth()) { - items(3) { - MainLateItem( - modifier = Modifier.widthIn((componentWidth - 32.dp) / 3), - ) - } - } - } - } - } - } -} - -@Composable -fun MainLateItem( - modifier: Modifier = Modifier -) { - GomsTheme { colors, typography -> - Column( - modifier = modifier.padding(vertical = 16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - AsyncImage( - model = "", - modifier = Modifier.size(56.dp), - contentScale = ContentScale.Crop, - contentDescription = "Profile Image", - ) - Text( - text = "김경수", - style = typography.textMedium, - fontWeight = FontWeight.SemiBold, - color = colors.G7 - ) - Text( - text = "7기 | IoT", - style = typography.caption, - fontWeight = FontWeight.Normal, - color = colors.G4 - ) - } - } -} - -@Composable -@Preview(showBackground = true) -fun MainLateCardPreview() { - Column { - MainLateCard(role = Authority.ROLE_STUDENT) {} - MainLateCard(role = Authority.ROLE_STUDENT_COUNCIL) {} - MainLateCard(role = Authority.ROLE_STUDENT_COUNCIL, list = emptyList()) {} - } -} \ No newline at end of file diff --git a/core/ui/src/main/java/com/goms/ui/MainOutingCard.kt b/core/ui/src/main/java/com/goms/ui/MainOutingCard.kt deleted file mode 100644 index d225a0c3..00000000 --- a/core/ui/src/main/java/com/goms/ui/MainOutingCard.kt +++ /dev/null @@ -1,152 +0,0 @@ -package com.goms.ui - -import androidx.compose.foundation.BorderStroke -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.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Divider -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage -import com.goms.design_system.component.modifier.gomsClickable -import com.goms.design_system.theme.GomsTheme -import com.goms.model.enum.Authority - -@Composable -fun MainOutingCard( - modifier: Modifier = Modifier, - list: List = listOf("1", "2", "3", "4", "5", "6", "7"), - role: Authority, - onClick: () -> Unit -) { - GomsTheme { colors, typography -> - Surface( - modifier = modifier, - color = colors.G1, - shape = RoundedCornerShape(12.dp), - border = BorderStroke(width = 1.dp, color = colors.WHITE.copy(0.15f)) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(6.dp) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "외출 현황", - style = typography.titleMedium, - fontWeight = FontWeight.Bold, - color = colors.WHITE - ) - if (role == Authority.ROLE_STUDENT) { - Text( - modifier = Modifier.gomsClickable { onClick() }, - text = "더보기", - style = typography.buttonSmall, - fontWeight = FontWeight.Normal, - color = colors.G7 - ) - } else { - Text( - modifier = Modifier.gomsClickable { onClick() }, - text = "인원 관리하기", - style = typography.buttonSmall, - fontWeight = FontWeight.Normal, - color = colors.G7 - ) - } - } - Divider(modifier = Modifier.height(1.dp), color = colors.WHITE.copy(0.15f)) - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "66", - style = typography.textMedium, - fontWeight = FontWeight.SemiBold, - color = colors.G7 - ) - Text( - text = "명이 외출중", - style = typography.textMedium, - fontWeight = FontWeight.Normal, - color = colors.G4 - ) - } - LazyColumn( - modifier = Modifier - .fillMaxWidth() - .heightIn(max = 10000.dp) - ) { - items(list.size) { - MainOutingItem( - modifier = Modifier.fillMaxWidth() - ) - } - } - } - } - } -} - -@Composable -fun MainOutingItem( - modifier: Modifier = Modifier -) { - GomsTheme { colors, typography -> - Row( - modifier = modifier.padding(16.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - AsyncImage( - model = "", - modifier = Modifier.size(28.dp), - contentScale = ContentScale.Crop, - contentDescription = "Profile Image", - ) - Text( - text = "김경수", - style = typography.textMedium, - fontWeight = FontWeight.SemiBold, - color = colors.G7 - ) - Text( - text = "7기 | IoT", - style = typography.caption, - fontWeight = FontWeight.Normal, - color = colors.G4 - ) - } - } -} - -@Composable -@Preview(showBackground = true) -fun MainOutingCardPreview() { - Column { - MainOutingCard(role = Authority.ROLE_STUDENT, list = emptyList()) {} - MainOutingCard(role = Authority.ROLE_STUDENT) {} - MainOutingCard(role = Authority.ROLE_STUDENT_COUNCIL) {} - } -} \ No newline at end of file diff --git a/core/ui/src/main/java/com/goms/ui/MainProfileCard.kt b/core/ui/src/main/java/com/goms/ui/MainProfileCard.kt deleted file mode 100644 index b08d18dc..00000000 --- a/core/ui/src/main/java/com/goms/ui/MainProfileCard.kt +++ /dev/null @@ -1,94 +0,0 @@ -package com.goms.ui - -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage -import com.goms.design_system.theme.GomsTheme -import com.goms.model.enum.Authority - -@Composable -fun MainProfileCard( - modifier: Modifier = Modifier, - role: Authority -) { - GomsTheme { colors, typography -> - val stateColor = when (role) { - Authority.ROLE_STUDENT -> colors.G4 - Authority.ROLE_STUDENT_COUNCIL -> colors.A7 - } - - val stateText = when (role) { - Authority.ROLE_STUDENT -> "외출 대기중" - Authority.ROLE_STUDENT_COUNCIL -> "학생회" - } - - Surface( - modifier = modifier, - color = colors.G1, - shape = RoundedCornerShape(12.dp), - border = BorderStroke(width = 1.dp, color = colors.WHITE.copy(0.15f)) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - AsyncImage( - model = "", - modifier = Modifier.size(64.dp), - contentScale = ContentScale.Crop, - contentDescription = "Profile Image", - ) - Spacer(modifier = Modifier.width(16.dp)) - Column(verticalArrangement = Arrangement.SpaceBetween) { - Text( - text = "홍길동", - style = typography.titleSmall, - fontWeight = FontWeight.SemiBold, - color = colors.WHITE - ) - Text( - text = "7기 | SW개발", - style = typography.textMedium, - fontWeight = FontWeight.Normal, - color = colors.G4 - ) - } - Spacer(modifier = Modifier.weight(1f)) - Text( - text = stateText, - style = typography.titleSmall, - fontWeight = FontWeight.SemiBold, - color = stateColor - ) - } - } - } -} - -@Composable -@Preview(showBackground = true) -fun MainProfileCardPreview() { - Column { - MainProfileCard(role = Authority.ROLE_STUDENT) - MainProfileCard(role = Authority.ROLE_STUDENT_COUNCIL) - } -} \ No newline at end of file diff --git a/feature/login/src/main/java/com/goms/login/InputLoginScreen.kt b/feature/login/src/main/java/com/goms/login/InputLoginScreen.kt index b76cb3ae..ef157352 100644 --- a/feature/login/src/main/java/com/goms/login/InputLoginScreen.kt +++ b/feature/login/src/main/java/com/goms/login/InputLoginScreen.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.goms.common.result.Result import com.goms.design_system.component.button.ButtonState import com.goms.design_system.component.button.GomsBackButton import com.goms.design_system.component.button.GomsButton @@ -47,17 +48,23 @@ fun InputLoginRoute( onMainClick: () -> Unit, viewModel: LoginViewModel = hiltViewModel(), ) { - val loginResponse = viewModel.loginResponse.collectAsStateWithLifecycle() + val loginUiState by viewModel.loginUiState.collectAsStateWithLifecycle() + val saveTokenUiState by viewModel.saveTokenUiState.collectAsStateWithLifecycle() val email by viewModel.email.collectAsStateWithLifecycle() val password by viewModel.password.collectAsStateWithLifecycle() var isError by remember { mutableStateOf(false) } var errorText by remember { mutableStateOf("") } - when (val state = loginResponse.value) { + when (loginUiState) { is LoginUiState.Loading -> Unit is LoginUiState.Success -> { - viewModel.saveToken(token = state.loginResponse) - onMainClick() + when (saveTokenUiState) { + is Result.Loading -> Unit + is Result.Success -> { + onMainClick() + } + is Result.Error -> {} + } } is LoginUiState.Error -> { isError = true diff --git a/feature/login/src/main/java/com/goms/login/viewmodel/LoginViewModel.kt b/feature/login/src/main/java/com/goms/login/viewmodel/LoginViewModel.kt index c3d9345f..24c13c63 100644 --- a/feature/login/src/main/java/com/goms/login/viewmodel/LoginViewModel.kt +++ b/feature/login/src/main/java/com/goms/login/viewmodel/LoginViewModel.kt @@ -22,11 +22,11 @@ class LoginViewModel @Inject constructor( private val loginUseCase: LoginUseCase, private val saveTokenUseCase: SaveTokenUseCase ) : ViewModel() { - private val _loginResponse = MutableStateFlow(LoginUiState.Loading) - val loginResponse = _loginResponse.asStateFlow() + private val _loginUiState = MutableStateFlow(LoginUiState.Loading) + val loginUiState = _loginUiState.asStateFlow() - private val _saveTokenResponse = MutableStateFlow>(Result.Loading) - val saveTokenResponse = _saveTokenResponse.asStateFlow() + private val _saveTokenUiState = MutableStateFlow>(Result.Loading) + val saveTokenUiState = _saveTokenUiState.asStateFlow() var email = savedStateHandle.getStateFlow(key = EMAIL, initialValue = "") var password = savedStateHandle.getStateFlow(key = PASSWORD, initialValue = "") @@ -34,11 +34,14 @@ class LoginViewModel @Inject constructor( fun login(body: LoginRequest) = viewModelScope.launch { loginUseCase(body = body) .asResult() - .collectLatest{ result -> + .collectLatest { result -> when (result) { - is Result.Loading -> _loginResponse.value = LoginUiState.Loading - is Result.Success -> _loginResponse.value = LoginUiState.Success(result.data!!) - is Result.Error -> _loginResponse.value = LoginUiState.Error(result.exception) + is Result.Loading -> _loginUiState.value = LoginUiState.Loading + is Result.Success -> { + _loginUiState.value = LoginUiState.Success(result.data) + saveToken(result.data) + } + is Result.Error -> _loginUiState.value = LoginUiState.Error(result.exception) } } } @@ -46,9 +49,9 @@ class LoginViewModel @Inject constructor( fun saveToken(token: LoginResponse) = viewModelScope.launch { saveTokenUseCase(token = token) .onSuccess { - _saveTokenResponse.value = Result.Success(it) + _saveTokenUiState.value = Result.Success(it) }.onFailure { - _saveTokenResponse.value = Result.Error(it) + _saveTokenUiState.value = Result.Error(it) } } diff --git a/feature/main/src/main/java/com/goms/main/MainScreen.kt b/feature/main/src/main/java/com/goms/main/MainScreen.kt index f15c8496..3f72c15e 100644 --- a/feature/main/src/main/java/com/goms/main/MainScreen.kt +++ b/feature/main/src/main/java/com/goms/main/MainScreen.kt @@ -1,5 +1,6 @@ package com.goms.main +import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -11,25 +12,65 @@ import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.goms.design_system.icon.SettingIcon import com.goms.design_system.theme.GomsTheme +import com.goms.main.viewmodel.GetLateRankListUiState +import com.goms.main.viewmodel.GetOutingCountUiState +import com.goms.main.viewmodel.GetOutingListUiState +import com.goms.main.viewmodel.GetProfileUiState +import com.goms.main.viewmodel.MainViewModel import com.goms.model.enum.Authority import com.goms.ui.GomsTopBar import com.goms.ui.GomsFloatingButton -import com.goms.ui.MainLateCard -import com.goms.ui.MainOutingCard -import com.goms.ui.MainProfileCard +import com.goms.main.component.MainLateCard +import com.goms.main.component.MainOutingCard +import com.goms.main.component.MainProfileCard @Composable -fun MainRoute() { - MainScreen() +fun MainRoute( + viewModel: MainViewModel = hiltViewModel() +) { + val role by viewModel.role.collectAsStateWithLifecycle(initialValue = "") + val getProfileUiState by viewModel.getProfileUiState.collectAsStateWithLifecycle() + val getLateRankListUiState by viewModel.getLateRankListUiState.collectAsStateWithLifecycle() + val getOutingListUiState by viewModel.getOutingListUiState.collectAsStateWithLifecycle() + val getOutingCountUiState by viewModel.getOutingCountUiState.collectAsStateWithLifecycle() + + MainScreen( + role = if (role.isNotBlank()) Authority.valueOf(role) else Authority.ROLE_STUDENT, + getProfileUiState = getProfileUiState, + getLateRankListUiState = getLateRankListUiState, + getOutingListUiState = getOutingListUiState, + getOutingCountUiState = getOutingCountUiState, + getData = { + viewModel.getProfile() + viewModel.getLateRankList() + viewModel.getOutingCount() + } + ) } @Composable -fun MainScreen() { +fun MainScreen( + role: Authority, + getProfileUiState: GetProfileUiState, + getLateRankListUiState: GetLateRankListUiState, + getOutingListUiState: GetOutingListUiState, + getOutingCountUiState: GetOutingCountUiState, + getData: () -> Unit +) { + LaunchedEffect(true) { + getData() + } + val scrollState = rememberScrollState() GomsTheme { colors, typography -> @@ -42,7 +83,7 @@ fun MainScreen() { ) { Column { GomsTopBar( - role = Authority.ROLE_STUDENT, + role = role, icon = { SettingIcon(tint = colors.G7) }, onSettingClick = {}, onAdminClick = {} @@ -54,16 +95,26 @@ fun MainScreen() { .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(32.dp) ) { - MainProfileCard(role = Authority.ROLE_STUDENT) - MainLateCard(role = Authority.ROLE_STUDENT) {} - MainOutingCard(role = Authority.ROLE_STUDENT) {} + MainProfileCard( + role = role, + getProfileUiState = getProfileUiState + ) + MainLateCard( + role = role, + getLateRankListUiState = getLateRankListUiState + ) {} + MainOutingCard( + role = role, + getOutingListUiState = getOutingListUiState, + getOutingCountUiState = getOutingCountUiState + ) {} } } GomsFloatingButton( modifier = Modifier .align(Alignment.BottomEnd) .padding(end = 16.dp, bottom = 16.dp), - role = Authority.ROLE_STUDENT + role = role ) {} } } diff --git a/feature/main/src/main/java/com/goms/main/component/MainLateCard.kt b/feature/main/src/main/java/com/goms/main/component/MainLateCard.kt new file mode 100644 index 00000000..597dac42 --- /dev/null +++ b/feature/main/src/main/java/com/goms/main/component/MainLateCard.kt @@ -0,0 +1,171 @@ +package com.goms.main.component + +import androidx.compose.foundation.BorderStroke +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.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import coil.compose.AsyncImage +import com.goms.design_system.R +import com.goms.design_system.component.modifier.gomsClickable +import com.goms.design_system.theme.GomsTheme +import com.goms.main.viewmodel.GetLateRankListUiState +import com.goms.model.enum.Authority +import com.goms.model.enum.toText +import com.goms.model.response.late.RankResponse + +@Composable +fun MainLateCard( + modifier: Modifier = Modifier, + role: Authority, + getLateRankListUiState: GetLateRankListUiState, + onClick: () -> Unit +) { + when (getLateRankListUiState) { + GetLateRankListUiState.Loading -> Unit + is GetLateRankListUiState.Error -> Unit + is GetLateRankListUiState.Success -> { + val list = getLateRankListUiState.getLateRankListResponse + + var componentWidth by remember { mutableStateOf(0.dp) } + val density = LocalDensity.current + + GomsTheme { colors, typography -> + Surface( + modifier = modifier + .onGloballyPositioned { + componentWidth = with(density) { + it.size.width.toDp() + } + }, + color = colors.G1, + shape = RoundedCornerShape(12.dp), + border = BorderStroke(width = 1.dp, color = colors.WHITE.copy(0.15f)) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "지각자 TOP 3", + style = typography.titleMedium, + fontWeight = FontWeight.Bold, + color = colors.WHITE + ) + if (role == Authority.ROLE_STUDENT_COUNCIL) { + Text( + modifier = Modifier.gomsClickable { onClick() }, + text = "더보기", + style = typography.buttonSmall, + fontWeight = FontWeight.Normal, + color = colors.G7 + ) + } + } + Spacer(modifier = Modifier.height(16.dp)) + if (list.isEmpty()) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = "\uD83D\uDC4D", + fontSize = 80.sp, + fontWeight = FontWeight.Bold, + color = colors.WHITE + ) + Text( + text = "지각자가 없어요! 놀랍게도...", + style = typography.textMedium, + fontWeight = FontWeight.SemiBold, + color = colors.G4 + ) + } + } else { + LazyRow(modifier = Modifier.fillMaxWidth()) { + items(list.take(3).size) { + MainLateItem( + modifier = Modifier.widthIn((componentWidth - 32.dp) / 3), + list = list[it] + ) + } + } + } + } + } + } + } + } +} + +@Composable +fun MainLateItem( + modifier: Modifier = Modifier, + list: RankResponse +) { + GomsTheme { colors, typography -> + Column( + modifier = modifier.padding(vertical = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + if (list.profileUrl.isNullOrEmpty()) { + Image( + painter = painterResource(R.drawable.ic_profile), + contentDescription = "Default Profile Image", + modifier = Modifier.size(56.dp) + ) + } else { + AsyncImage( + model = list.profileUrl, + modifier = Modifier.size(56.dp), + contentScale = ContentScale.Crop, + contentDescription = "Profile Image", + ) + } + Text( + text = list.name, + style = typography.textMedium, + fontWeight = FontWeight.SemiBold, + color = colors.G7 + ) + Text( + text = "${list.grade}기 | ${list.major.toText()}", + style = typography.caption, + fontWeight = FontWeight.Normal, + color = colors.G4 + ) + } + } +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/goms/main/component/MainOutingCard.kt b/feature/main/src/main/java/com/goms/main/component/MainOutingCard.kt new file mode 100644 index 00000000..d3b07c05 --- /dev/null +++ b/feature/main/src/main/java/com/goms/main/component/MainOutingCard.kt @@ -0,0 +1,239 @@ +package com.goms.main.component + +import androidx.compose.foundation.BorderStroke +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.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Divider +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import com.goms.design_system.R +import com.goms.design_system.component.modifier.gomsClickable +import com.goms.design_system.theme.GomsTheme +import com.goms.main.viewmodel.GetOutingCountUiState +import com.goms.main.viewmodel.GetOutingListUiState +import com.goms.model.enum.Authority +import com.goms.model.enum.toText +import com.goms.model.response.outing.OutingResponse + +@Composable +fun MainOutingCard( + modifier: Modifier = Modifier, + role: Authority, + getOutingListUiState: GetOutingListUiState, + getOutingCountUiState: GetOutingCountUiState, + onClick: () -> Unit +) { + when (getOutingCountUiState) { + GetOutingCountUiState.Loading -> Unit + is GetOutingCountUiState.Error -> Unit + GetOutingCountUiState.Empty -> { + GomsTheme { colors, typography -> + Surface( + modifier = modifier, + color = colors.G1, + shape = RoundedCornerShape(12.dp), + border = BorderStroke(width = 1.dp, color = colors.WHITE.copy(0.15f)) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(6.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "외출 현황", + style = typography.titleMedium, + fontWeight = FontWeight.Bold, + color = colors.WHITE + ) + if (role == Authority.ROLE_STUDENT) { + Text( + modifier = Modifier.gomsClickable { onClick() }, + text = "더보기", + style = typography.buttonSmall, + fontWeight = FontWeight.Normal, + color = colors.G7 + ) + } else { + Text( + modifier = Modifier.gomsClickable { onClick() }, + text = "인원 관리하기", + style = typography.buttonSmall, + fontWeight = FontWeight.Normal, + color = colors.G7 + ) + } + } + Divider(modifier = Modifier.height(1.dp), color = colors.WHITE.copy(0.15f)) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "0", + style = typography.textMedium, + fontWeight = FontWeight.SemiBold, + color = colors.G7 + ) + Text( + text = "명이 외출중", + style = typography.textMedium, + fontWeight = FontWeight.Normal, + color = colors.G4 + ) + } + } + } + } + } + is GetOutingCountUiState.Success -> { + when (getOutingListUiState) { + GetOutingListUiState.Loading -> Unit + is GetOutingListUiState.Error -> Unit + is GetOutingListUiState.Success -> { + val count = getOutingCountUiState.getOutingCountResponse + val list = getOutingListUiState.getOutingListResponse + + GomsTheme { colors, typography -> + Surface( + modifier = modifier, + color = colors.G1, + shape = RoundedCornerShape(12.dp), + border = BorderStroke(width = 1.dp, color = colors.WHITE.copy(0.15f)) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(6.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "외출 현황", + style = typography.titleMedium, + fontWeight = FontWeight.Bold, + color = colors.WHITE + ) + if (role == Authority.ROLE_STUDENT) { + Text( + modifier = Modifier.gomsClickable { onClick() }, + text = "더보기", + style = typography.buttonSmall, + fontWeight = FontWeight.Normal, + color = colors.G7 + ) + } else { + Text( + modifier = Modifier.gomsClickable { onClick() }, + text = "인원 관리하기", + style = typography.buttonSmall, + fontWeight = FontWeight.Normal, + color = colors.G7 + ) + } + } + Divider(modifier = Modifier.height(1.dp), color = colors.WHITE.copy(0.15f)) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "${count.outingCount}", + style = typography.textMedium, + fontWeight = FontWeight.SemiBold, + color = colors.G7 + ) + Text( + text = "명이 외출중", + style = typography.textMedium, + fontWeight = FontWeight.Normal, + color = colors.G4 + ) + } + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .heightIn(max = 10000.dp) + ) { + items(list.size) { + MainOutingItem( + modifier = Modifier.fillMaxWidth(), + list = list[it] + ) + } + } + } + } + } + } + } + } + } +} + +@Composable +fun MainOutingItem( + modifier: Modifier = Modifier, + list: OutingResponse +) { + GomsTheme { colors, typography -> + Row( + modifier = modifier.padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + if (list.profileUrl.isNullOrEmpty()) { + Image( + painter = painterResource(R.drawable.ic_profile), + contentDescription = "Default Profile Image", + modifier = Modifier.size(28.dp) + ) + } else { + AsyncImage( + model = list.profileUrl, + modifier = Modifier.size(28.dp), + contentScale = ContentScale.Crop, + contentDescription = "Profile Image", + ) + } + Text( + text = list.name, + style = typography.textMedium, + fontWeight = FontWeight.SemiBold, + color = colors.G7 + ) + Text( + text = "${list.grade}기 | ${list.major.toText()}", + style = typography.caption, + fontWeight = FontWeight.Normal, + color = colors.G4 + ) + } + } +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/goms/main/component/MainProfileCard.kt b/feature/main/src/main/java/com/goms/main/component/MainProfileCard.kt new file mode 100644 index 00000000..a26ad163 --- /dev/null +++ b/feature/main/src/main/java/com/goms/main/component/MainProfileCard.kt @@ -0,0 +1,130 @@ +package com.goms.main.component + +import androidx.compose.foundation.BorderStroke +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.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import com.goms.design_system.theme.GomsTheme +import com.goms.design_system.R +import com.goms.main.viewmodel.GetProfileUiState +import com.goms.model.enum.Authority +import com.goms.model.enum.toText + +@Composable +fun MainProfileCard( + modifier: Modifier = Modifier, + role: Authority, + getProfileUiState: GetProfileUiState, +) { + when (getProfileUiState) { + GetProfileUiState.Loading -> Unit + is GetProfileUiState.Error -> Unit + is GetProfileUiState.Success -> { + val data = getProfileUiState.getProfileResponse + + GomsTheme { colors, typography -> + val stateColor = when (role) { + Authority.ROLE_STUDENT -> { + if (data.isBlackList) { + colors.N5 + } else { + if (data.isOuting) { + colors.P5 + } else { + colors.G4 + } + } + } + Authority.ROLE_STUDENT_COUNCIL -> colors.A7 + } + + val stateText = when (role) { + Authority.ROLE_STUDENT -> { + if (data.isBlackList) { + "외출 금지" + } else { + if (data.isOuting) { + "외출 중" + } else { + "외출 대기 중" + } + } + } + Authority.ROLE_STUDENT_COUNCIL -> "학생회" + } + + Surface( + modifier = modifier, + color = colors.G1, + shape = RoundedCornerShape(12.dp), + border = BorderStroke(width = 1.dp, color = colors.WHITE.copy(0.15f)) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (data.profileUrl.isNullOrEmpty()) { + Image( + painter = painterResource(R.drawable.ic_profile), + contentDescription = "Default Profile Image", + modifier = Modifier.size(64.dp) + ) + } else { + AsyncImage( + model = data.profileUrl, + modifier = Modifier.size(64.dp), + contentScale = ContentScale.Crop, + contentDescription = "Profile Image", + ) + } + Spacer(modifier = Modifier.width(16.dp)) + Column( + modifier = Modifier.height(56.dp), + verticalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = data.name, + style = typography.titleSmall, + fontWeight = FontWeight.SemiBold, + color = colors.WHITE + ) + Text( + text = "${data.grade}기 | ${data.major.toText()}", + style = typography.textMedium, + fontWeight = FontWeight.Normal, + color = colors.G4 + ) + } + Spacer(modifier = Modifier.weight(1f)) + Text( + text = stateText, + style = typography.titleSmall, + fontWeight = FontWeight.SemiBold, + color = stateColor + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/goms/main/viewmodel/GetLateRankListUiState.kt b/feature/main/src/main/java/com/goms/main/viewmodel/GetLateRankListUiState.kt new file mode 100644 index 00000000..df9440e1 --- /dev/null +++ b/feature/main/src/main/java/com/goms/main/viewmodel/GetLateRankListUiState.kt @@ -0,0 +1,9 @@ +package com.goms.main.viewmodel + +import com.goms.model.response.late.RankResponse + +sealed interface GetLateRankListUiState { + object Loading : GetLateRankListUiState + data class Success(val getLateRankListResponse: List) : GetLateRankListUiState + data class Error(val exception: Throwable) : GetLateRankListUiState +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/goms/main/viewmodel/GetOutingCountUiState.kt b/feature/main/src/main/java/com/goms/main/viewmodel/GetOutingCountUiState.kt new file mode 100644 index 00000000..85b742b5 --- /dev/null +++ b/feature/main/src/main/java/com/goms/main/viewmodel/GetOutingCountUiState.kt @@ -0,0 +1,10 @@ +package com.goms.main.viewmodel + +import com.goms.model.response.outing.CountResponse + +sealed interface GetOutingCountUiState { + object Loading : GetOutingCountUiState + object Empty : GetOutingCountUiState + data class Success(val getOutingCountResponse: CountResponse) : GetOutingCountUiState + data class Error(val exception: Throwable) : GetOutingCountUiState +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/goms/main/viewmodel/GetOutingListUiState.kt b/feature/main/src/main/java/com/goms/main/viewmodel/GetOutingListUiState.kt new file mode 100644 index 00000000..9ace05ef --- /dev/null +++ b/feature/main/src/main/java/com/goms/main/viewmodel/GetOutingListUiState.kt @@ -0,0 +1,9 @@ +package com.goms.main.viewmodel + +import com.goms.model.response.outing.OutingResponse + +sealed interface GetOutingListUiState { + object Loading : GetOutingListUiState + data class Success(val getOutingListResponse: List) : GetOutingListUiState + data class Error(val exception: Throwable) : GetOutingListUiState +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/goms/main/viewmodel/GetProfileUiState.kt b/feature/main/src/main/java/com/goms/main/viewmodel/GetProfileUiState.kt new file mode 100644 index 00000000..0ab01b2b --- /dev/null +++ b/feature/main/src/main/java/com/goms/main/viewmodel/GetProfileUiState.kt @@ -0,0 +1,9 @@ +package com.goms.main.viewmodel + +import com.goms.model.response.account.ProfileResponse + +sealed interface GetProfileUiState { + object Loading : GetProfileUiState + data class Success(val getProfileResponse: ProfileResponse) : GetProfileUiState + data class Error(val exception: Throwable) : GetProfileUiState +} \ No newline at end of file diff --git a/feature/main/src/main/java/com/goms/main/viewmodel/MainViewModel.kt b/feature/main/src/main/java/com/goms/main/viewmodel/MainViewModel.kt new file mode 100644 index 00000000..7f17cacb --- /dev/null +++ b/feature/main/src/main/java/com/goms/main/viewmodel/MainViewModel.kt @@ -0,0 +1,97 @@ +package com.goms.main.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.goms.common.result.Result +import com.goms.common.result.asResult +import com.goms.datastore.AuthTokenDataSource +import com.goms.domain.account.GetProfileUseCase +import com.goms.domain.late.GetLateRankListUseCase +import com.goms.domain.outing.GetOutingCountUseCase +import com.goms.domain.outing.GetOutingListUseCase +import com.goms.model.enum.Authority +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class MainViewModel @Inject constructor( + private val getProfileUseCase: GetProfileUseCase, + private val getLateRankListUseCase: GetLateRankListUseCase, + private val getOutingListUseCase: GetOutingListUseCase, + private val getOutingCountUseCase: GetOutingCountUseCase, + private val authTokenDataSource: AuthTokenDataSource +) : ViewModel() { + val role = authTokenDataSource.getAuthority() + + private val _getProfileUiState = MutableStateFlow(GetProfileUiState.Loading) + val getProfileUiState = _getProfileUiState.asStateFlow() + + private val _getLateRankListUiState = MutableStateFlow(GetLateRankListUiState.Loading) + val getLateRankListUiState = _getLateRankListUiState.asStateFlow() + + private val _getOutingListUiState = MutableStateFlow(GetOutingListUiState.Loading) + val getOutingListUiState = _getOutingListUiState.asStateFlow() + + private val _getOutingCountUiState = MutableStateFlow(GetOutingCountUiState.Loading) + val getOutingCountUiState = _getOutingCountUiState.asStateFlow() + + fun getProfile() = viewModelScope.launch { + getProfileUseCase() + .asResult() + .collectLatest { result -> + when (result) { + is Result.Loading -> _getProfileUiState.value = GetProfileUiState.Loading + is Result.Success -> _getProfileUiState.value = GetProfileUiState.Success(result.data) + is Result.Error -> _getProfileUiState.value = GetProfileUiState.Error(result.exception) + } + } + } + + fun getLateRankList() = viewModelScope.launch { + getLateRankListUseCase() + .asResult() + .collectLatest { result -> + when (result) { + is Result.Loading -> _getLateRankListUiState.value = GetLateRankListUiState.Loading + is Result.Success -> _getLateRankListUiState.value = GetLateRankListUiState.Success(result.data) + is Result.Error -> _getLateRankListUiState.value = GetLateRankListUiState.Error(result.exception) + } + } + } + + fun getOutingList() = viewModelScope.launch { + getOutingListUseCase() + .asResult() + .collectLatest { result -> + when (result) { + is Result.Loading -> _getOutingListUiState.value = GetOutingListUiState.Loading + is Result.Success -> _getOutingListUiState.value = GetOutingListUiState.Success(result.data) + is Result.Error -> _getOutingListUiState.value = GetOutingListUiState.Error(result.exception) + } + } + } + + fun getOutingCount() = viewModelScope.launch { + getOutingCountUseCase() + .asResult() + .collectLatest { result -> + when (result) { + is Result.Loading -> _getOutingCountUiState.value = GetOutingCountUiState.Loading + is Result.Success -> { + if (result.data.outingCount == 0) { + _getOutingCountUiState.value = GetOutingCountUiState.Empty + } else { + _getOutingCountUiState.value = GetOutingCountUiState.Success(result.data) + getOutingList() + } + } + is Result.Error -> _getOutingCountUiState.value = GetOutingCountUiState.Error(result.exception) + } + } + } +} \ No newline at end of file diff --git a/feature/sign-up/src/main/java/com/goms/sign_up/NumberScreen.kt b/feature/sign-up/src/main/java/com/goms/sign_up/NumberScreen.kt index 0a9eb0c1..f7c50a60 100644 --- a/feature/sign-up/src/main/java/com/goms/sign_up/NumberScreen.kt +++ b/feature/sign-up/src/main/java/com/goms/sign_up/NumberScreen.kt @@ -44,12 +44,12 @@ fun NumberRoute( onPasswordClick: () -> Unit, ) { SignUpViewModelProvider(viewModelStoreOwner = viewModelStoreOwner) { viewModel -> - val verifyNumberResponse = viewModel.verifyNumberResponse.collectAsStateWithLifecycle() + val verifyNumberUiState by viewModel.verifyNumberUiState.collectAsStateWithLifecycle() val number by viewModel.number.collectAsStateWithLifecycle() var isError by remember { mutableStateOf(false) } var errorText by remember { mutableStateOf("") } - when (verifyNumberResponse.value) { + when (verifyNumberUiState) { is Result.Loading -> Unit is Result.Success -> { onPasswordClick() diff --git a/feature/sign-up/src/main/java/com/goms/sign_up/PasswordScreen.kt b/feature/sign-up/src/main/java/com/goms/sign_up/PasswordScreen.kt index 4d57ea36..7eec19f1 100644 --- a/feature/sign-up/src/main/java/com/goms/sign_up/PasswordScreen.kt +++ b/feature/sign-up/src/main/java/com/goms/sign_up/PasswordScreen.kt @@ -50,12 +50,12 @@ fun PasswordRoute( onLoginClick: () -> Unit, ) { SignUpViewModelProvider(viewModelStoreOwner = viewModelStoreOwner) { viewModel -> - val signUpResponse = viewModel.signUpResponse.collectAsStateWithLifecycle() + val signUpUiState by viewModel.signUpUiState.collectAsStateWithLifecycle() val password by viewModel.password.collectAsStateWithLifecycle() var isError by remember { mutableStateOf(false) } var errorText by remember { mutableStateOf("") } - when (signUpResponse.value) { + when (signUpUiState) { is Result.Loading -> Unit is Result.Success -> { onLoginClick() diff --git a/feature/sign-up/src/main/java/com/goms/sign_up/SignUpScreen.kt b/feature/sign-up/src/main/java/com/goms/sign_up/SignUpScreen.kt index 71ca8680..d274b691 100644 --- a/feature/sign-up/src/main/java/com/goms/sign_up/SignUpScreen.kt +++ b/feature/sign-up/src/main/java/com/goms/sign_up/SignUpScreen.kt @@ -49,13 +49,13 @@ fun SignUpRoute( onNumberClick: () -> Unit, ) { SignUpViewModelProvider(viewModelStoreOwner = viewModelStoreOwner) { viewModel -> - val sendNumberResponse = viewModel.sendNumberResponse.collectAsStateWithLifecycle() + val sendNumberUiState by viewModel.sendNumberUiState.collectAsStateWithLifecycle() val name by viewModel.name.collectAsStateWithLifecycle() val email by viewModel.email.collectAsStateWithLifecycle() val gender by viewModel.gender.collectAsStateWithLifecycle() val major by viewModel.major.collectAsStateWithLifecycle() - when (sendNumberResponse.value) { + when (sendNumberUiState) { is Result.Loading -> Unit is Result.Success -> { onNumberClick() diff --git a/feature/sign-up/src/main/java/com/goms/sign_up/viewmodel/SignUpViewModel.kt b/feature/sign-up/src/main/java/com/goms/sign_up/viewmodel/SignUpViewModel.kt index 17998021..173afb07 100644 --- a/feature/sign-up/src/main/java/com/goms/sign_up/viewmodel/SignUpViewModel.kt +++ b/feature/sign-up/src/main/java/com/goms/sign_up/viewmodel/SignUpViewModel.kt @@ -23,14 +23,14 @@ class SignUpViewModel @Inject constructor( private val sendNumberUseCase: SendNumberUseCase, private val verifyNumberUseCase: VerifyNumberUseCase ) : ViewModel() { - private val _signUpResponse = MutableStateFlow>(Result.Loading) - val signUpResponse = _signUpResponse.asStateFlow() + private val _signUpUiState = MutableStateFlow>(Result.Loading) + val signUpUiState = _signUpUiState.asStateFlow() - private val _sendNumberResponse = MutableStateFlow>(Result.Loading) - val sendNumberResponse = _sendNumberResponse.asStateFlow() + private val _sendNumberUiState = MutableStateFlow>(Result.Loading) + val sendNumberUiState = _sendNumberUiState.asStateFlow() - private val _verifyNumberResponse = MutableStateFlow>(Result.Loading) - val verifyNumberResponse = _verifyNumberResponse.asStateFlow() + private val _verifyNumberUiState = MutableStateFlow>(Result.Loading) + val verifyNumberUiState = _verifyNumberUiState.asStateFlow() var name = savedStateHandle.getStateFlow(key = NAME, initialValue = "") var email = savedStateHandle.getStateFlow(key = EMAIL, initialValue = "") @@ -43,12 +43,12 @@ class SignUpViewModel @Inject constructor( signUpUseCase(body = body) .onSuccess { it.catch { remoteError -> - _signUpResponse.value = Result.Error(remoteError) + _signUpUiState.value = Result.Error(remoteError) }.collect { result -> - _signUpResponse.value = Result.Success(result) + _signUpUiState.value = Result.Success(result) } }.onFailure { - _signUpResponse.value = Result.Error(it) + _signUpUiState.value = Result.Error(it) } } @@ -56,17 +56,17 @@ class SignUpViewModel @Inject constructor( sendNumberUseCase(body = body) .onSuccess { it.catch { remoteError -> - _sendNumberResponse.value = Result.Error(remoteError) + _sendNumberUiState.value = Result.Error(remoteError) }.collect { result -> - _sendNumberResponse.value = Result.Success(result) + _sendNumberUiState.value = Result.Success(result) } }.onFailure { - _sendNumberResponse.value = Result.Error(it) + _sendNumberUiState.value = Result.Error(it) } } fun initSendNumber() { - _sendNumberResponse.value = Result.Loading + _sendNumberUiState.value = Result.Loading } @@ -74,17 +74,17 @@ class SignUpViewModel @Inject constructor( verifyNumberUseCase(email = email, authCode = authCode) .onSuccess { it.catch { remoteError -> - _verifyNumberResponse.value = Result.Error(remoteError) + _verifyNumberUiState.value = Result.Error(remoteError) }.collect { result -> - _verifyNumberResponse.value = Result.Success(result) + _verifyNumberUiState.value = Result.Success(result) } }.onFailure { - _verifyNumberResponse.value = Result.Error(it) + _verifyNumberUiState.value = Result.Error(it) } } fun initVerifyNumber() { - _verifyNumberResponse.value = Result.Loading + _verifyNumberUiState.value = Result.Loading } fun onNameChange(value: String) {