diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 42d29820a..267920f8a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -16,6 +16,7 @@
android:theme="@style/Theme.Cherrish" >
@@ -27,4 +28,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/java/com/cherrish/android/core/common/extension/ModifierExt.kt b/app/src/main/java/com/cherrish/android/core/common/extension/ModifierExt.kt
index 3ba4c8a87..353ea5bdb 100644
--- a/app/src/main/java/com/cherrish/android/core/common/extension/ModifierExt.kt
+++ b/app/src/main/java/com/cherrish/android/core/common/extension/ModifierExt.kt
@@ -4,8 +4,14 @@ import android.graphics.BlurMaskFilter
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.consumeWindowInsets
+import androidx.compose.foundation.layout.imePadding
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.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.drawBehind
@@ -17,6 +23,9 @@ import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.findRootCoordinates
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -86,3 +95,19 @@ fun Modifier.dropShadow(
}
}
}
+
+fun Modifier.advancedImePadding() = composed {
+ var consumePadding by remember { mutableStateOf(0) }
+ onGloballyPositioned { coordinates ->
+ consumePadding = (
+ coordinates.findRootCoordinates().size.height -
+ (coordinates.positionInWindow().y + coordinates.size.height).toInt()
+ ).coerceAtLeast(
+ 0
+ )
+ }
+ .consumeWindowInsets(
+ PaddingValues(bottom = with(LocalDensity.current) { consumePadding.toDp() })
+ )
+ .imePadding()
+}
diff --git a/app/src/main/java/com/cherrish/android/data/di/DataSourceModule.kt b/app/src/main/java/com/cherrish/android/data/di/DataSourceModule.kt
index 7b7d851d5..e94fd08a5 100644
--- a/app/src/main/java/com/cherrish/android/data/di/DataSourceModule.kt
+++ b/app/src/main/java/com/cherrish/android/data/di/DataSourceModule.kt
@@ -1,7 +1,9 @@
package com.cherrish.android.data.di
import com.cherrish.android.data.remote.datasource.CalendarDataSource
+import com.cherrish.android.data.remote.datasource.OnboardingProfileDataSource
import com.cherrish.android.data.remote.datasourceimpl.CalendarDataSourceImpl
+import com.cherrish.android.data.remote.datasourceimpl.OnboardingProfileDataSourceImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@@ -16,4 +18,10 @@ abstract class DataSourceModule {
abstract fun bindCalendarDataSource(
calendarDataSourceImpl: CalendarDataSourceImpl
): CalendarDataSource
+
+ @Binds
+ @Singleton
+ abstract fun bindOnboardingProfileDataSource(
+ onboardingProfileDataSourceImpl: OnboardingProfileDataSourceImpl
+ ): OnboardingProfileDataSource
}
diff --git a/app/src/main/java/com/cherrish/android/data/di/RepositoryModule.kt b/app/src/main/java/com/cherrish/android/data/di/RepositoryModule.kt
index ce7225f16..e249f72dd 100644
--- a/app/src/main/java/com/cherrish/android/data/di/RepositoryModule.kt
+++ b/app/src/main/java/com/cherrish/android/data/di/RepositoryModule.kt
@@ -1,7 +1,9 @@
package com.cherrish.android.data.di
import com.cherrish.android.data.repository.CalendarRepository
+import com.cherrish.android.data.repository.OnboardingProfileRepository
import com.cherrish.android.data.repositoryimpl.CalendarRepositoryImpl
+import com.cherrish.android.data.repositoryimpl.OnboardingProfileRepositoryImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@@ -16,4 +18,10 @@ abstract class RepositoryModule {
abstract fun bindCalendarRepository(
calendarRepositoryImpl: CalendarRepositoryImpl
): CalendarRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindOnboardingProfileRepository(
+ onboardingProfileRepositoryImpl: OnboardingProfileRepositoryImpl
+ ): OnboardingProfileRepository
}
diff --git a/app/src/main/java/com/cherrish/android/data/di/ServiceModule.kt b/app/src/main/java/com/cherrish/android/data/di/ServiceModule.kt
index 9bc6bbde6..014d47866 100644
--- a/app/src/main/java/com/cherrish/android/data/di/ServiceModule.kt
+++ b/app/src/main/java/com/cherrish/android/data/di/ServiceModule.kt
@@ -1,6 +1,7 @@
package com.cherrish.android.data.di
import com.cherrish.android.data.remote.service.CalendarService
+import com.cherrish.android.data.remote.service.OnboardingProfileService
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -16,4 +17,10 @@ object ServiceModule {
fun provideCalendarService(
retrofit: Retrofit
): CalendarService = retrofit.create(CalendarService::class.java)
+
+ @Provides
+ @Singleton
+ fun provideOnboardingProfileService(
+ retrofit: Retrofit
+ ): OnboardingProfileService = retrofit.create(OnboardingProfileService::class.java)
}
diff --git a/app/src/main/java/com/cherrish/android/data/model/OnboardingProfileRequestModel.kt b/app/src/main/java/com/cherrish/android/data/model/OnboardingProfileRequestModel.kt
new file mode 100644
index 000000000..decba6294
--- /dev/null
+++ b/app/src/main/java/com/cherrish/android/data/model/OnboardingProfileRequestModel.kt
@@ -0,0 +1,13 @@
+package com.cherrish.android.data.model
+
+import com.cherrish.android.data.remote.dto.request.OnboardingProfileRequestDto
+
+data class OnboardingProfileRequestModel(
+ val name: String,
+ val age: Int
+)
+
+fun OnboardingProfileRequestModel.toDto() = OnboardingProfileRequestDto(
+ name = this.name,
+ age = this.age
+)
diff --git a/app/src/main/java/com/cherrish/android/data/model/OnboardingProfileResponseModel.kt b/app/src/main/java/com/cherrish/android/data/model/OnboardingProfileResponseModel.kt
new file mode 100644
index 000000000..2c011a755
--- /dev/null
+++ b/app/src/main/java/com/cherrish/android/data/model/OnboardingProfileResponseModel.kt
@@ -0,0 +1,15 @@
+package com.cherrish.android.data.model
+
+import com.cherrish.android.data.remote.dto.response.OnboardingProfileResponseDto
+
+data class OnboardingProfileResponseModel(
+ val id: Long,
+ val name: String,
+ val date: String
+)
+
+fun OnboardingProfileResponseDto.toModel() = OnboardingProfileResponseModel(
+ id = this.id,
+ name = this.name,
+ date = this.date
+)
diff --git a/app/src/main/java/com/cherrish/android/data/remote/datasource/OnboardingProfileDataSource.kt b/app/src/main/java/com/cherrish/android/data/remote/datasource/OnboardingProfileDataSource.kt
new file mode 100644
index 000000000..89fceef05
--- /dev/null
+++ b/app/src/main/java/com/cherrish/android/data/remote/datasource/OnboardingProfileDataSource.kt
@@ -0,0 +1,11 @@
+package com.cherrish.android.data.remote.datasource
+
+import com.cherrish.android.core.network.BaseResponse
+import com.cherrish.android.data.remote.dto.request.OnboardingProfileRequestDto
+import com.cherrish.android.data.remote.dto.response.OnboardingProfileResponseDto
+
+interface OnboardingProfileDataSource {
+ suspend fun postOnboardingProfile(
+ request: OnboardingProfileRequestDto
+ ): BaseResponse
+}
diff --git a/app/src/main/java/com/cherrish/android/data/remote/datasourceimpl/OnboardingProfileDataSourceImpl.kt b/app/src/main/java/com/cherrish/android/data/remote/datasourceimpl/OnboardingProfileDataSourceImpl.kt
new file mode 100644
index 000000000..6ec9b140f
--- /dev/null
+++ b/app/src/main/java/com/cherrish/android/data/remote/datasourceimpl/OnboardingProfileDataSourceImpl.kt
@@ -0,0 +1,18 @@
+package com.cherrish.android.data.remote.datasourceimpl
+
+import com.cherrish.android.core.network.BaseResponse
+import com.cherrish.android.data.remote.datasource.OnboardingProfileDataSource
+import com.cherrish.android.data.remote.dto.request.OnboardingProfileRequestDto
+import com.cherrish.android.data.remote.dto.response.OnboardingProfileResponseDto
+import com.cherrish.android.data.remote.service.OnboardingProfileService
+import javax.inject.Inject
+
+class OnboardingProfileDataSourceImpl @Inject constructor(
+ private val onboardingProfileService: OnboardingProfileService
+) : OnboardingProfileDataSource {
+ override suspend fun postOnboardingProfile(
+ request: OnboardingProfileRequestDto
+ ): BaseResponse {
+ return onboardingProfileService.postOnboardingProfile(request)
+ }
+}
diff --git a/app/src/main/java/com/cherrish/android/data/remote/dto/request/OnboardingProfileRequestDto.kt b/app/src/main/java/com/cherrish/android/data/remote/dto/request/OnboardingProfileRequestDto.kt
new file mode 100644
index 000000000..133825005
--- /dev/null
+++ b/app/src/main/java/com/cherrish/android/data/remote/dto/request/OnboardingProfileRequestDto.kt
@@ -0,0 +1,12 @@
+package com.cherrish.android.data.remote.dto.request
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class OnboardingProfileRequestDto(
+ @SerialName("name")
+ val name: String,
+ @SerialName("age")
+ val age: Int
+)
diff --git a/app/src/main/java/com/cherrish/android/data/remote/dto/response/OnboardingProfileResponseDto.kt b/app/src/main/java/com/cherrish/android/data/remote/dto/response/OnboardingProfileResponseDto.kt
new file mode 100644
index 000000000..d7a18f2f2
--- /dev/null
+++ b/app/src/main/java/com/cherrish/android/data/remote/dto/response/OnboardingProfileResponseDto.kt
@@ -0,0 +1,14 @@
+package com.cherrish.android.data.remote.dto.response
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class OnboardingProfileResponseDto(
+ @SerialName("id")
+ val id: Long,
+ @SerialName("name")
+ val name: String,
+ @SerialName("date")
+ val date: String
+)
diff --git a/app/src/main/java/com/cherrish/android/data/remote/service/OnboardingProfileService.kt b/app/src/main/java/com/cherrish/android/data/remote/service/OnboardingProfileService.kt
new file mode 100644
index 000000000..ec32a1451
--- /dev/null
+++ b/app/src/main/java/com/cherrish/android/data/remote/service/OnboardingProfileService.kt
@@ -0,0 +1,14 @@
+package com.cherrish.android.data.remote.service
+
+import com.cherrish.android.core.network.BaseResponse
+import com.cherrish.android.data.remote.dto.request.OnboardingProfileRequestDto
+import com.cherrish.android.data.remote.dto.response.OnboardingProfileResponseDto
+import retrofit2.http.Body
+import retrofit2.http.POST
+
+interface OnboardingProfileService {
+ @POST("api/onboarding/profiles")
+ suspend fun postOnboardingProfile(
+ @Body request: OnboardingProfileRequestDto
+ ): BaseResponse
+}
diff --git a/app/src/main/java/com/cherrish/android/data/repository/OnboardingProfileRepository.kt b/app/src/main/java/com/cherrish/android/data/repository/OnboardingProfileRepository.kt
new file mode 100644
index 000000000..dbb14e609
--- /dev/null
+++ b/app/src/main/java/com/cherrish/android/data/repository/OnboardingProfileRepository.kt
@@ -0,0 +1,10 @@
+package com.cherrish.android.data.repository
+
+import com.cherrish.android.data.model.OnboardingProfileRequestModel
+import com.cherrish.android.data.model.OnboardingProfileResponseModel
+
+interface OnboardingProfileRepository {
+ suspend fun postOnboardingProfile(
+ request: OnboardingProfileRequestModel
+ ): Result
+}
diff --git a/app/src/main/java/com/cherrish/android/data/repositoryimpl/OnboardingProfileRepositoryImpl.kt b/app/src/main/java/com/cherrish/android/data/repositoryimpl/OnboardingProfileRepositoryImpl.kt
new file mode 100644
index 000000000..64c3e08c8
--- /dev/null
+++ b/app/src/main/java/com/cherrish/android/data/repositoryimpl/OnboardingProfileRepositoryImpl.kt
@@ -0,0 +1,23 @@
+package com.cherrish.android.data.repositoryimpl
+
+import com.cherrish.android.core.util.suspendRunCatching
+import com.cherrish.android.data.model.OnboardingProfileRequestModel
+import com.cherrish.android.data.model.OnboardingProfileResponseModel
+import com.cherrish.android.data.model.toDto
+import com.cherrish.android.data.model.toModel
+import com.cherrish.android.data.remote.datasource.OnboardingProfileDataSource
+import com.cherrish.android.data.repository.OnboardingProfileRepository
+import javax.inject.Inject
+
+class OnboardingProfileRepositoryImpl @Inject constructor(
+ private val onboardingProfileDataSource: OnboardingProfileDataSource
+) : OnboardingProfileRepository {
+ override suspend fun postOnboardingProfile(
+ request: OnboardingProfileRequestModel
+ ): Result =
+ suspendRunCatching {
+ onboardingProfileDataSource.postOnboardingProfile(
+ request = request.toDto()
+ ).data!!.toModel()
+ }
+}
diff --git a/app/src/main/java/com/cherrish/android/presentation/main/MainActivity.kt b/app/src/main/java/com/cherrish/android/presentation/main/MainActivity.kt
index ecbf6c227..5b0594de7 100644
--- a/app/src/main/java/com/cherrish/android/presentation/main/MainActivity.kt
+++ b/app/src/main/java/com/cherrish/android/presentation/main/MainActivity.kt
@@ -1,5 +1,6 @@
package com.cherrish.android.presentation.main
+import android.content.pm.ActivityInfo
import android.graphics.Color
import android.os.Bundle
import androidx.activity.ComponentActivity
@@ -13,11 +14,11 @@ import dagger.hilt.android.AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
-
enableEdgeToEdge(
statusBarStyle = SystemBarStyle.dark(Color.TRANSPARENT),
navigationBarStyle = SystemBarStyle.dark(Color.TRANSPARENT)
)
+ requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
setContent {
CherrishTheme {
val appState = rememberMainAppState()
diff --git a/app/src/main/java/com/cherrish/android/presentation/main/MainAppState.kt b/app/src/main/java/com/cherrish/android/presentation/main/MainAppState.kt
index 549462b95..63d611b5b 100644
--- a/app/src/main/java/com/cherrish/android/presentation/main/MainAppState.kt
+++ b/app/src/main/java/com/cherrish/android/presentation/main/MainAppState.kt
@@ -6,6 +6,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.NavHostController
+import androidx.navigation.NavOptions
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navOptions
import com.cherrish.android.presentation.calendar.navigation.navigateToCalendar
@@ -13,6 +14,8 @@ import com.cherrish.android.presentation.calendar.navigation.navigateToProcedure
import com.cherrish.android.presentation.challenge.navigation.navigateToChallenge
import com.cherrish.android.presentation.home.navigation.navigateToHome
import com.cherrish.android.presentation.mypage.navigation.navigateToMyPage
+import com.cherrish.android.presentation.onboarding.navigation.navigateToOnboarding
+import com.cherrish.android.presentation.onboarding.navigation.navigateToOnboardingInformation
import com.cherrish.android.presentation.splash.navigation.Splash
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -27,6 +30,17 @@ class MainAppState(
) {
val startDestination = Splash
+ private val clearStackNavOptions = navOptions {
+ popUpTo(0) { inclusive = true }
+ launchSingleTop = true
+ restoreState = false
+ }
+
+ private val keepStackNavOptions = navOptions {
+ launchSingleTop = true
+ restoreState = true
+ }
+
private val currentDestination = navController.currentBackStackEntryFlow
.map { it.destination }
.stateIn(
@@ -79,6 +93,18 @@ class MainAppState(
}
}
+ fun navigateToOnboarding(navOptions: NavOptions? = clearStackNavOptions) {
+ navController.navigateToOnboarding(navOptions)
+ }
+
+ fun navigateToOnboardingInformation(navOptions: NavOptions? = clearStackNavOptions) {
+ navController.navigateToOnboardingInformation(navOptions)
+ }
+
+ fun navigateToHome(navOptions: NavOptions? = clearStackNavOptions) {
+ navController.navigateToHome(navOptions)
+ }
+
fun navigateUp() {
navController.navigateUp()
}
diff --git a/app/src/main/java/com/cherrish/android/presentation/main/MainScreen.kt b/app/src/main/java/com/cherrish/android/presentation/main/MainScreen.kt
index d0db756f2..5ad6df56e 100644
--- a/app/src/main/java/com/cherrish/android/presentation/main/MainScreen.kt
+++ b/app/src/main/java/com/cherrish/android/presentation/main/MainScreen.kt
@@ -12,6 +12,8 @@ import com.cherrish.android.presentation.challenge.navigation.challengeNavGraph
import com.cherrish.android.presentation.home.navigation.homeNavGraph
import com.cherrish.android.presentation.main.component.MainBottomBar
import com.cherrish.android.presentation.mypage.navigation.myPageNavGraph
+import com.cherrish.android.presentation.onboarding.navigation.onboardingInformationNavGraph
+import com.cherrish.android.presentation.onboarding.navigation.onboardingNavGraph
import com.cherrish.android.presentation.splash.navigation.splashNavGraph
import kotlinx.collections.immutable.toPersistentList
@@ -41,10 +43,20 @@ fun MainScreen(
startDestination = appState.startDestination
) {
splashNavGraph(
- navigateToOnboarding = {}, // TODO: 로직 넣어서 바꿀 예정
+ navigateToOnboarding = appState::navigateToOnboarding,
paddingValues = innerPadding
)
+ onboardingNavGraph(
+ paddingValues = innerPadding,
+ navigateToOnboardingInformation = appState::navigateToOnboardingInformation
+ )
+
+ onboardingInformationNavGraph(
+ paddingValues = innerPadding,
+ navigateToHome = appState::navigateToHome
+ )
+
homeNavGraph(paddingValues = innerPadding)
calendarNavGraph(
diff --git a/app/src/main/java/com/cherrish/android/presentation/onboarding/OnboardingScreen.kt b/app/src/main/java/com/cherrish/android/presentation/onboarding/OnboardingScreen.kt
index 64b9e8d00..cab2dc74a 100644
--- a/app/src/main/java/com/cherrish/android/presentation/onboarding/OnboardingScreen.kt
+++ b/app/src/main/java/com/cherrish/android/presentation/onboarding/OnboardingScreen.kt
@@ -5,11 +5,13 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentWidth
@@ -50,6 +52,7 @@ import androidx.constraintlayout.compose.ConstraintLayout
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import com.cherrish.android.R
+import com.cherrish.android.core.common.extension.collectLatestSideEffect
import com.cherrish.android.core.common.extension.noRippleClickable
import com.cherrish.android.core.designsystem.component.button.CherrishButton
import com.cherrish.android.core.designsystem.theme.CherrishTheme
@@ -61,16 +64,27 @@ import kotlinx.coroutines.android.awaitFrame
@Composable
fun OnboardingRoute(
+ paddingValues: PaddingValues,
+ navigateToOnboardingInformation: () -> Unit,
viewModel: OnboardingViewModel = hiltViewModel()
) {
+ viewModel.sideEffect.collectLatestSideEffect { sideEffect ->
+ when (sideEffect) {
+ OnboardingSideEffect.NavigateToOnboardingInformation -> {
+ navigateToOnboardingInformation()
+ }
+ }
+ }
OnboardingScreen(
- onCancelClick = viewModel::onCancelClick,
- onNextClick = viewModel::onNextClick
+ paddingValues = paddingValues,
+ onCancelClick = viewModel::onClick,
+ onNextClick = viewModel::onClick
)
}
@Composable
private fun OnboardingScreen(
+ paddingValues: PaddingValues,
onCancelClick: () -> Unit,
onNextClick: () -> Unit,
modifier: Modifier = Modifier
@@ -93,7 +107,9 @@ private fun OnboardingScreen(
)
) {
Column(
- modifier = Modifier.fillMaxSize()
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(paddingValues)
) {
Spacer(modifier = Modifier.height(54.dp))
@@ -140,6 +156,7 @@ private fun OnboardingScreen(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp)
+ .navigationBarsPadding()
.graphicsLayer {
alpha = if (showButton) 1f else 0f
}
@@ -470,6 +487,7 @@ private fun Preview() {
modifier = Modifier.fillMaxSize()
) {
OnboardingScreen(
+ paddingValues = PaddingValues(),
onCancelClick = {},
onNextClick = {}
)
diff --git a/app/src/main/java/com/cherrish/android/presentation/onboarding/OnboardingSideEffect.kt b/app/src/main/java/com/cherrish/android/presentation/onboarding/OnboardingSideEffect.kt
new file mode 100644
index 000000000..3f42f4ffc
--- /dev/null
+++ b/app/src/main/java/com/cherrish/android/presentation/onboarding/OnboardingSideEffect.kt
@@ -0,0 +1,5 @@
+package com.cherrish.android.presentation.onboarding
+
+sealed interface OnboardingSideEffect {
+ data object NavigateToOnboardingInformation : OnboardingSideEffect
+}
diff --git a/app/src/main/java/com/cherrish/android/presentation/onboarding/OnboardingViewModel.kt b/app/src/main/java/com/cherrish/android/presentation/onboarding/OnboardingViewModel.kt
index d758d826e..36921aa5e 100644
--- a/app/src/main/java/com/cherrish/android/presentation/onboarding/OnboardingViewModel.kt
+++ b/app/src/main/java/com/cherrish/android/presentation/onboarding/OnboardingViewModel.kt
@@ -1,15 +1,25 @@
package com.cherrish.android.presentation.onboarding
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.launch
@HiltViewModel
class OnboardingViewModel @Inject constructor() : ViewModel() {
- fun onCancelClick() {
- }
+ private val _sideEffect = MutableSharedFlow()
+ val sideEffect: SharedFlow = _sideEffect.asSharedFlow()
- fun onNextClick() {
+ fun onClick() {
+ viewModelScope.launch {
+ _sideEffect.emit(
+ OnboardingSideEffect.NavigateToOnboardingInformation
+ )
+ }
}
}
diff --git a/app/src/main/java/com/cherrish/android/presentation/onboarding/information/InformationUiState.kt b/app/src/main/java/com/cherrish/android/presentation/onboarding/information/InformationUiState.kt
deleted file mode 100644
index e8db292a3..000000000
--- a/app/src/main/java/com/cherrish/android/presentation/onboarding/information/InformationUiState.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.cherrish.android.presentation.onboarding.information
-
-import androidx.compose.runtime.Immutable
-
-@Immutable
-data class InformationUiState(
- val username: String = "",
- val age: String = ""
-) {
- val buttonEnabled: Boolean
- get() = username.isNotBlank() && age.toIntOrNull()?.let { it > 0 } == true
-}
diff --git a/app/src/main/java/com/cherrish/android/presentation/onboarding/information/InformationViewModel.kt b/app/src/main/java/com/cherrish/android/presentation/onboarding/information/InformationViewModel.kt
deleted file mode 100644
index be7b2dd7d..000000000
--- a/app/src/main/java/com/cherrish/android/presentation/onboarding/information/InformationViewModel.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.cherrish.android.presentation.onboarding.information
-
-import androidx.lifecycle.ViewModel
-import dagger.hilt.android.lifecycle.HiltViewModel
-import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.update
-
-@HiltViewModel
-class InformationViewModel @Inject constructor() : ViewModel() {
- private val _uiState = MutableStateFlow(InformationUiState())
- val uiState: StateFlow = _uiState.asStateFlow()
-
- fun onNameChanged(name: String) {
- _uiState.update {
- it.copy(
- username = name
- )
- }
- }
-
- fun onAgeChanged(input: String) {
- val filtered = input.filter { it.isDigit() }
- _uiState.update { it.copy(age = filtered) }
- }
-
- fun onNextClicked() {
- // TODO: 다음 화면으로 이동
- }
-}
diff --git a/app/src/main/java/com/cherrish/android/presentation/onboarding/information/InformationScreen.kt b/app/src/main/java/com/cherrish/android/presentation/onboarding/information/OnboardingInformationScreen.kt
similarity index 80%
rename from app/src/main/java/com/cherrish/android/presentation/onboarding/information/InformationScreen.kt
rename to app/src/main/java/com/cherrish/android/presentation/onboarding/information/OnboardingInformationScreen.kt
index 8139d0ea4..acd8a7a7c 100644
--- a/app/src/main/java/com/cherrish/android/presentation/onboarding/information/InformationScreen.kt
+++ b/app/src/main/java/com/cherrish/android/presentation/onboarding/information/OnboardingInformationScreen.kt
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
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.shape.RoundedCornerShape
import androidx.compose.material3.Text
@@ -19,6 +20,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.input.ImeAction
@@ -29,31 +31,42 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.cherrish.android.core.common.extension.addFocusCleaner
+import com.cherrish.android.core.common.extension.advancedImePadding
+import com.cherrish.android.core.common.extension.collectLatestSideEffect
import com.cherrish.android.core.designsystem.component.button.CherrishButton
import com.cherrish.android.core.designsystem.component.textfield.CherrishTextField
import com.cherrish.android.core.designsystem.theme.CherrishTheme
import com.cherrish.android.presentation.onboarding.information.extension.AgeSuffixTransformation
@Composable
-fun InformationRoute(
+fun OnboardingInformationRoute(
paddingValues: PaddingValues,
- viewModel: InformationViewModel = hiltViewModel()
+ navigateToHome: () -> Unit,
+ viewModel: OnboardingInformationViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
- InformationScreen(
+ viewModel.sideEffect.collectLatestSideEffect { sideEffect ->
+ when (sideEffect) {
+ InformationSideEffect.NavigateToHome -> navigateToHome()
+ }
+ }
+
+ OnboardingInformationScreen(
paddingValues = paddingValues,
username = uiState.username,
onNameChange = viewModel::onNameChanged,
age = uiState.age,
onAgeChange = viewModel::onAgeChanged,
onNextClick = viewModel::onNextClicked,
- enabled = uiState.buttonEnabled
+ enabled = uiState.buttonEnabled,
+ nameErrorCase = viewModel.onNameErrorCase(uiState.username),
+ ageErrorCase = viewModel.onAgeErrorCase(uiState.age)
)
}
@Composable
-private fun InformationScreen(
+private fun OnboardingInformationScreen(
paddingValues: PaddingValues,
username: String,
onNameChange: (String) -> Unit,
@@ -61,6 +74,8 @@ private fun InformationScreen(
onAgeChange: (String) -> Unit,
onNextClick: () -> Unit,
enabled: Boolean,
+ nameErrorCase: Boolean,
+ ageErrorCase: Boolean,
modifier: Modifier = Modifier
) {
val focusManager = LocalFocusManager.current
@@ -75,6 +90,7 @@ private fun InformationScreen(
.background(color = CherrishTheme.colors.gray0)
.addFocusCleaner(focusManager)
.padding(paddingValues = paddingValues)
+ .advancedImePadding()
) {
Spacer(modifier = Modifier.weight(135f))
@@ -91,7 +107,9 @@ private fun InformationScreen(
onNextAction = {
ageFocusRequester.requestFocus()
},
- keyboardType = KeyboardType.Text
+ keyboardType = KeyboardType.Text,
+ errorText = "이름은 최대 7자까지 입력 가능합니다.",
+ errorCase = nameErrorCase
)
Spacer(modifier = Modifier.weight(30f))
@@ -114,6 +132,8 @@ private fun InformationScreen(
" 세"
)
},
+ errorText = "입력 가능한 최대 나이 100세를 초과했습니다.",
+ errorCase = ageErrorCase,
modifier = Modifier
.focusRequester(ageFocusRequester)
.onFocusChanged { state ->
@@ -164,16 +184,17 @@ private fun UserInfoTextField(
placeholder: String,
keyboardImeAction: ImeAction,
keyboardType: KeyboardType,
+ errorText: String,
modifier: Modifier = Modifier,
onNextAction: () -> Unit = {},
onDoneAction: () -> Unit = {},
- visualTransformation: VisualTransformation = VisualTransformation.None
+ visualTransformation: VisualTransformation = VisualTransformation.None,
+ errorCase: Boolean = false
) {
Column(
modifier = modifier
.fillMaxWidth()
- .padding(horizontal = 26.dp),
- verticalArrangement = Arrangement.spacedBy(8.dp)
+ .padding(horizontal = 26.dp)
) {
Text(
text = textFieldName,
@@ -181,6 +202,8 @@ private fun UserInfoTextField(
color = CherrishTheme.colors.gray1000
)
+ Spacer(modifier = Modifier.height(8.dp))
+
CherrishTextField(
value = value,
onValueChange = onValueChange,
@@ -197,6 +220,14 @@ private fun UserInfoTextField(
visualTransformation = visualTransformation,
modifier = Modifier.fillMaxWidth()
)
+
+ Spacer(modifier = Modifier.height(4.dp))
+
+ Text(
+ text = errorText,
+ style = CherrishTheme.typography.body1R14,
+ color = if (errorCase) CherrishTheme.colors.red700 else Color.Transparent
+ )
}
}
@@ -204,14 +235,16 @@ private fun UserInfoTextField(
@Composable
private fun Preview() {
CherrishTheme {
- InformationScreen(
+ OnboardingInformationScreen(
paddingValues = PaddingValues(),
username = "",
onNameChange = {},
age = "",
onAgeChange = {},
onNextClick = {},
- enabled = true
+ enabled = true,
+ nameErrorCase = false,
+ ageErrorCase = false
)
}
}
diff --git a/app/src/main/java/com/cherrish/android/presentation/onboarding/information/OnboardingInformationUiState.kt b/app/src/main/java/com/cherrish/android/presentation/onboarding/information/OnboardingInformationUiState.kt
new file mode 100644
index 000000000..53fc9dbb5
--- /dev/null
+++ b/app/src/main/java/com/cherrish/android/presentation/onboarding/information/OnboardingInformationUiState.kt
@@ -0,0 +1,20 @@
+package com.cherrish.android.presentation.onboarding.information
+
+import androidx.compose.runtime.Immutable
+
+@Immutable
+data class InformationUiState(
+ val username: String = "",
+ val age: String = ""
+) {
+ val buttonEnabled: Boolean
+ get() {
+ val ageInt = age.toIntOrNull() ?: return false
+ return username.isNotBlank() && age.toIntOrNull()?.let { it > 0 } == true &&
+ ageInt in 1..100 && username.length <= 7
+ }
+}
+
+sealed interface InformationSideEffect {
+ data object NavigateToHome : InformationSideEffect
+}
diff --git a/app/src/main/java/com/cherrish/android/presentation/onboarding/information/OnboardingInformationViewModel.kt b/app/src/main/java/com/cherrish/android/presentation/onboarding/information/OnboardingInformationViewModel.kt
new file mode 100644
index 000000000..a5d517364
--- /dev/null
+++ b/app/src/main/java/com/cherrish/android/presentation/onboarding/information/OnboardingInformationViewModel.kt
@@ -0,0 +1,63 @@
+package com.cherrish.android.presentation.onboarding.information
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.cherrish.android.core.common.extension.onLogFailure
+import com.cherrish.android.core.local.TokenManager
+import com.cherrish.android.data.model.OnboardingProfileRequestModel
+import com.cherrish.android.data.repository.OnboardingProfileRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+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.flow.update
+import kotlinx.coroutines.launch
+
+@HiltViewModel
+class OnboardingInformationViewModel @Inject constructor(
+ private val onboardingProfileRepository: OnboardingProfileRepository,
+ private val tokenManager: TokenManager
+) : ViewModel() {
+ private val _uiState = MutableStateFlow(InformationUiState())
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ private val _sideEffect = MutableSharedFlow()
+ val sideEffect: SharedFlow = _sideEffect.asSharedFlow()
+
+ fun onNameChanged(name: String) {
+ _uiState.update {
+ it.copy(
+ username = name
+ )
+ }
+ }
+
+ fun onNameErrorCase(name: String): Boolean = name.length > 7
+
+ fun onAgeChanged(input: String) {
+ val filtered = input.filter { it.isDigit() }
+ _uiState.update { it.copy(age = filtered) }
+ }
+
+ fun onAgeErrorCase(age: String): Boolean = age.toIntOrNull() ?.let { it > 100 } ?: false
+
+ fun onNextClicked() {
+ val age = uiState.value.age.toIntOrNull() ?: return
+
+ viewModelScope.launch {
+ onboardingProfileRepository.postOnboardingProfile(
+ request = OnboardingProfileRequestModel(
+ name = uiState.value.username,
+ age = age
+ )
+ ).onSuccess { response ->
+ tokenManager.saveId(response.id)
+ _sideEffect.emit(InformationSideEffect.NavigateToHome)
+ }.onLogFailure {}
+ }
+ }
+}
diff --git a/app/src/main/java/com/cherrish/android/presentation/onboarding/navigation/OnboardingInformationNavigator.kt b/app/src/main/java/com/cherrish/android/presentation/onboarding/navigation/OnboardingInformationNavigator.kt
new file mode 100644
index 000000000..1a697414b
--- /dev/null
+++ b/app/src/main/java/com/cherrish/android/presentation/onboarding/navigation/OnboardingInformationNavigator.kt
@@ -0,0 +1,29 @@
+package com.cherrish.android.presentation.onboarding.navigation
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptions
+import androidx.navigation.compose.composable
+import com.cherrish.android.core.common.navigation.Route
+import com.cherrish.android.presentation.onboarding.information.OnboardingInformationRoute
+import kotlinx.serialization.Serializable
+
+@Serializable
+data object OnboardingInformation : Route
+
+fun NavController.navigateToOnboardingInformation(navOptions: NavOptions? = null) {
+ navigate(route = OnboardingInformation, navOptions = navOptions)
+}
+
+fun NavGraphBuilder.onboardingInformationNavGraph(
+ paddingValues: PaddingValues,
+ navigateToHome: () -> Unit
+) {
+ composable {
+ OnboardingInformationRoute(
+ paddingValues = paddingValues,
+ navigateToHome = navigateToHome
+ )
+ }
+}
diff --git a/app/src/main/java/com/cherrish/android/presentation/onboarding/navigation/OnboardingNavigator.kt b/app/src/main/java/com/cherrish/android/presentation/onboarding/navigation/OnboardingNavigator.kt
new file mode 100644
index 000000000..7633f8d1b
--- /dev/null
+++ b/app/src/main/java/com/cherrish/android/presentation/onboarding/navigation/OnboardingNavigator.kt
@@ -0,0 +1,29 @@
+package com.cherrish.android.presentation.onboarding.navigation
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptions
+import androidx.navigation.compose.composable
+import com.cherrish.android.core.common.navigation.Route
+import com.cherrish.android.presentation.onboarding.OnboardingRoute
+import kotlinx.serialization.Serializable
+
+@Serializable
+data object Onboarding : Route
+
+fun NavController.navigateToOnboarding(navOptions: NavOptions? = null) {
+ navigate(route = Onboarding, navOptions = navOptions)
+}
+
+fun NavGraphBuilder.onboardingNavGraph(
+ paddingValues: PaddingValues,
+ navigateToOnboardingInformation: () -> Unit
+) {
+ composable {
+ OnboardingRoute(
+ paddingValues = paddingValues,
+ navigateToOnboardingInformation = navigateToOnboardingInformation
+ )
+ }
+}