diff --git a/app/src/main/java/org/memento/data/datasource/UserInfoDataSource.kt b/app/src/main/java/org/memento/data/datasource/UserInfoDataSource.kt new file mode 100644 index 00000000..a008cb90 --- /dev/null +++ b/app/src/main/java/org/memento/data/datasource/UserInfoDataSource.kt @@ -0,0 +1,8 @@ +package org.memento.data.datasource + +import org.memento.data.dto.BaseResponse +import org.memento.data.dto.request.RequestUserInfoUpdateDto + +interface UserInfoDataSource { + suspend fun fetchUserInfo(requestUserInfo: RequestUserInfoUpdateDto): BaseResponse +} diff --git a/app/src/main/java/org/memento/data/datasourceimpl/UserDataSourceImpl.kt b/app/src/main/java/org/memento/data/datasourceimpl/UserDataSourceImpl.kt new file mode 100644 index 00000000..4ff38083 --- /dev/null +++ b/app/src/main/java/org/memento/data/datasourceimpl/UserDataSourceImpl.kt @@ -0,0 +1,15 @@ +package org.memento.data.datasourceimpl + +import org.memento.data.datasource.UserInfoDataSource +import org.memento.data.dto.BaseResponse +import org.memento.data.dto.request.RequestUserInfoUpdateDto +import org.memento.data.service.UserInfoUpdateService +import javax.inject.Inject + +class UserDataSourceImpl + @Inject + constructor( + private val userService: UserInfoUpdateService, + ) : UserInfoDataSource { + override suspend fun fetchUserInfo(requestUserInfo: RequestUserInfoUpdateDto): BaseResponse = userService.fetchUserInfo(requestUserInfo) + } diff --git a/app/src/main/java/org/memento/data/dto/request/RequestUserInfoUpdateDto.kt b/app/src/main/java/org/memento/data/dto/request/RequestUserInfoUpdateDto.kt new file mode 100644 index 00000000..013938c2 --- /dev/null +++ b/app/src/main/java/org/memento/data/dto/request/RequestUserInfoUpdateDto.kt @@ -0,0 +1,24 @@ +package org.memento.data.dto.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RequestUserInfoUpdateDto( + @SerialName("wakeUpTime") + val wakeUpTime: String, + @SerialName("windDownTime") + val windDownTime: String, + @SerialName("job") + val job: String, + @SerialName("jobOtherDetail") + val jobOtherDetail: String? = null, + @SerialName("isStressedUnorganizedSchedule") + val isStressedUnorganizedSchedule: Boolean, + @SerialName("isForgetImportantThings") + val isForgetImportantThings: Boolean, + @SerialName("isPreferReminder") + val isPreferReminder: Boolean, + @SerialName("isImportantBreaks") + val isImportantBreaks: Boolean, +) diff --git a/app/src/main/java/org/memento/data/mapper/toData/RequestUserInfoMapper.kt b/app/src/main/java/org/memento/data/mapper/toData/RequestUserInfoMapper.kt new file mode 100644 index 00000000..2942c4c8 --- /dev/null +++ b/app/src/main/java/org/memento/data/mapper/toData/RequestUserInfoMapper.kt @@ -0,0 +1,16 @@ +package org.memento.data.mapper.toData + +import org.memento.data.dto.request.RequestUserInfoUpdateDto +import org.memento.domain.entity.UserInfo + +fun UserInfo.toData(): RequestUserInfoUpdateDto = + RequestUserInfoUpdateDto( + wakeUpTime = wakeUpTime, + windDownTime = windDownTime, + job = job, + jobOtherDetail = jobOtherDetail, + isStressedUnorganizedSchedule = isStressedUnorganizedSchedule, + isForgetImportantThings = isForgetImportantThings, + isPreferReminder = isPreferReminder, + isImportantBreaks = isImportantBreaks, + ) diff --git a/app/src/main/java/org/memento/data/mapper/toDomain/ResponseLoginMapper.kt b/app/src/main/java/org/memento/data/mapper/toDomain/ResponseLoginMapper.kt index 764acabd..209c1263 100644 --- a/app/src/main/java/org/memento/data/mapper/toDomain/ResponseLoginMapper.kt +++ b/app/src/main/java/org/memento/data/mapper/toDomain/ResponseLoginMapper.kt @@ -1,10 +1,10 @@ package org.memento.data.mapper.toDomain import org.memento.data.dto.response.ResponseLoginDto -import org.memento.domain.entity.UserInfo +import org.memento.domain.entity.LoginInfo -fun ResponseLoginDto.toUserInfo(): UserInfo = - UserInfo( +fun ResponseLoginDto.toUserInfo(): LoginInfo = + LoginInfo( accessToken = accessToken, refreshToken = refreshToken, isNewUser = isNewUser, diff --git a/app/src/main/java/org/memento/data/repositoryimpl/LoginRepositoryImpl.kt b/app/src/main/java/org/memento/data/repositoryimpl/LoginRepositoryImpl.kt index b09c16bb..3263f2bc 100644 --- a/app/src/main/java/org/memento/data/repositoryimpl/LoginRepositoryImpl.kt +++ b/app/src/main/java/org/memento/data/repositoryimpl/LoginRepositoryImpl.kt @@ -5,7 +5,7 @@ import org.memento.data.mapper.toData.toData import org.memento.data.mapper.toDomain.toUserInfo import org.memento.data.util.handleBaseResponse import org.memento.domain.entity.Login -import org.memento.domain.entity.UserInfo +import org.memento.domain.entity.LoginInfo import org.memento.domain.repository.LoginRepository import javax.inject.Inject @@ -14,7 +14,7 @@ class LoginRepositoryImpl constructor( private val loginDataSource: LoginDataSource, ) : LoginRepository { - override suspend fun postLogin(login: Login): Result { + override suspend fun postLogin(login: Login): Result { return runCatching { loginDataSource.postLogin( requestLoginDto = login.toData(), diff --git a/app/src/main/java/org/memento/data/repositoryimpl/UserInfoUpdateRepositoryImpl.kt b/app/src/main/java/org/memento/data/repositoryimpl/UserInfoUpdateRepositoryImpl.kt new file mode 100644 index 00000000..520af302 --- /dev/null +++ b/app/src/main/java/org/memento/data/repositoryimpl/UserInfoUpdateRepositoryImpl.kt @@ -0,0 +1,21 @@ +package org.memento.data.repositoryimpl + +import org.memento.data.datasource.UserInfoDataSource +import org.memento.data.mapper.toData.toData +import org.memento.domain.entity.UserInfo +import org.memento.domain.repository.UserInfoUpdateRepository +import javax.inject.Inject + +class UserInfoUpdateRepositoryImpl + @Inject + constructor( + private val userInfoDataSource: UserInfoDataSource, + ) : UserInfoUpdateRepository { + override suspend fun fetchUserInfo(userInfo: UserInfo): Result { + return runCatching { + userInfoDataSource.fetchUserInfo( + requestUserInfo = userInfo.toData(), + ) + } + } + } diff --git a/app/src/main/java/org/memento/data/service/UserInfoUpdateService.kt b/app/src/main/java/org/memento/data/service/UserInfoUpdateService.kt new file mode 100644 index 00000000..2dfb64a7 --- /dev/null +++ b/app/src/main/java/org/memento/data/service/UserInfoUpdateService.kt @@ -0,0 +1,13 @@ +package org.memento.data.service + +import org.memento.data.dto.BaseResponse +import org.memento.data.dto.request.RequestUserInfoUpdateDto +import retrofit2.http.Body +import retrofit2.http.PATCH + +interface UserInfoUpdateService { + @PATCH("/api/v1/members/personal-info") + suspend fun fetchUserInfo( + @Body requestUserInfo: RequestUserInfoUpdateDto, + ): BaseResponse +} diff --git a/app/src/main/java/org/memento/di/DataSourceModule.kt b/app/src/main/java/org/memento/di/DataSourceModule.kt index 24b32f27..7ff48bfd 100644 --- a/app/src/main/java/org/memento/di/DataSourceModule.kt +++ b/app/src/main/java/org/memento/di/DataSourceModule.kt @@ -9,11 +9,13 @@ import org.memento.data.datasource.LoginDataSource import org.memento.data.datasource.ReqresDataSource import org.memento.data.datasource.ScheduleDataSource import org.memento.data.datasource.TodoDataSource +import org.memento.data.datasource.UserInfoDataSource import org.memento.data.datasourceimpl.AddPlanDataSourceImpl import org.memento.data.datasourceimpl.LoginDataSourceImpl import org.memento.data.datasourceimpl.ReqresDataSourceImpl import org.memento.data.datasourceimpl.ScheduleDataSourceImpl import org.memento.data.datasourceimpl.TodoDataSourceImpl +import org.memento.data.datasourceimpl.UserDataSourceImpl import javax.inject.Singleton @Module @@ -31,6 +33,10 @@ internal abstract class DataSourceModule { @Singleton abstract fun bindsAddPlanDataSource(addScheduleDataSourceImpl: AddPlanDataSourceImpl): AddPlanDataSource + @Binds + @Singleton + abstract fun bindsUserDataSource(userDataSourceImpl: UserDataSourceImpl): UserInfoDataSource + @Binds @Singleton abstract fun bindsScheduleDataSource(scheduleDataSourceImpl: ScheduleDataSourceImpl): ScheduleDataSource diff --git a/app/src/main/java/org/memento/di/RepositoryModule.kt b/app/src/main/java/org/memento/di/RepositoryModule.kt index b89a3f45..1414fa30 100644 --- a/app/src/main/java/org/memento/di/RepositoryModule.kt +++ b/app/src/main/java/org/memento/di/RepositoryModule.kt @@ -9,11 +9,13 @@ import org.memento.data.repositoryimpl.LoginRepositoryImpl import org.memento.data.repositoryimpl.ReqresRepositoryImpl import org.memento.data.repositoryimpl.ScheduleRepositoryImpl import org.memento.data.repositoryimpl.TodoRepositoryImpl +import org.memento.data.repositoryimpl.UserInfoUpdateRepositoryImpl import org.memento.domain.repository.AddPlanRepository import org.memento.domain.repository.LoginRepository import org.memento.domain.repository.ReqresRepository import org.memento.domain.repository.ScheduleRepository import org.memento.domain.repository.TodoRepository +import org.memento.domain.repository.UserInfoUpdateRepository import javax.inject.Singleton @Module @@ -35,6 +37,10 @@ internal abstract class RepositoryModule { @Singleton abstract fun bindsScheduleRepository(scheduleRepositoryImpl: ScheduleRepositoryImpl): ScheduleRepository + @Binds + @Singleton + abstract fun bindsUserRepository(userInfoUpdateRepositoryImpl: UserInfoUpdateRepositoryImpl): UserInfoUpdateRepository + @Binds @Singleton abstract fun bindsTodoRepository(todoRepositoryImpl: TodoRepositoryImpl): TodoRepository diff --git a/app/src/main/java/org/memento/di/ServiceModule.kt b/app/src/main/java/org/memento/di/ServiceModule.kt index 71a778d8..ab78f482 100644 --- a/app/src/main/java/org/memento/di/ServiceModule.kt +++ b/app/src/main/java/org/memento/di/ServiceModule.kt @@ -9,6 +9,7 @@ import org.memento.data.service.LoginService import org.memento.data.service.ReqresService import org.memento.data.service.ScheduleService import org.memento.data.service.TodoService +import org.memento.data.service.UserInfoUpdateService import retrofit2.Retrofit import javax.inject.Singleton @@ -31,6 +32,10 @@ internal object ServiceModule { @Singleton fun provideScheduleService(retrofit: Retrofit): ScheduleService = retrofit.create(ScheduleService::class.java) + @Provides + @Singleton + fun provideUserInfoUpdateService(retrofit: Retrofit): UserInfoUpdateService = retrofit.create(UserInfoUpdateService::class.java) + @Provides @Singleton fun provideTodoService(retrofit: Retrofit): TodoService = retrofit.create(TodoService::class.java) diff --git a/app/src/main/java/org/memento/domain/entity/Login.kt b/app/src/main/java/org/memento/domain/entity/Login.kt index f965d884..3fe9dcb9 100644 --- a/app/src/main/java/org/memento/domain/entity/Login.kt +++ b/app/src/main/java/org/memento/domain/entity/Login.kt @@ -5,7 +5,7 @@ data class Login( val idToken: String, ) -data class UserInfo( +data class LoginInfo( val accessToken: String, val refreshToken: String, val isNewUser: Boolean, diff --git a/app/src/main/java/org/memento/domain/entity/UserInfo.kt b/app/src/main/java/org/memento/domain/entity/UserInfo.kt new file mode 100644 index 00000000..6b522b60 --- /dev/null +++ b/app/src/main/java/org/memento/domain/entity/UserInfo.kt @@ -0,0 +1,12 @@ +package org.memento.domain.entity + +data class UserInfo( + val wakeUpTime: String, + val windDownTime: String, + val job: String, + val jobOtherDetail: String? = null, + val isStressedUnorganizedSchedule: Boolean, + val isForgetImportantThings: Boolean, + val isPreferReminder: Boolean, + val isImportantBreaks: Boolean, +) diff --git a/app/src/main/java/org/memento/domain/repository/LoginRepository.kt b/app/src/main/java/org/memento/domain/repository/LoginRepository.kt index e1bec634..93c2b812 100644 --- a/app/src/main/java/org/memento/domain/repository/LoginRepository.kt +++ b/app/src/main/java/org/memento/domain/repository/LoginRepository.kt @@ -1,8 +1,8 @@ package org.memento.domain.repository import org.memento.domain.entity.Login -import org.memento.domain.entity.UserInfo +import org.memento.domain.entity.LoginInfo interface LoginRepository { - suspend fun postLogin(login: Login): Result + suspend fun postLogin(login: Login): Result } diff --git a/app/src/main/java/org/memento/domain/repository/UserInfoUpdateRepository.kt b/app/src/main/java/org/memento/domain/repository/UserInfoUpdateRepository.kt new file mode 100644 index 00000000..d248bc7e --- /dev/null +++ b/app/src/main/java/org/memento/domain/repository/UserInfoUpdateRepository.kt @@ -0,0 +1,9 @@ +package org.memento.domain.repository + +import org.memento.domain.entity.UserInfo + +interface UserInfoUpdateRepository { + suspend fun fetchUserInfo( + userInfo: UserInfo, + ): Result +} diff --git a/app/src/main/java/org/memento/presentation/onboarding/LoginScreen.kt b/app/src/main/java/org/memento/presentation/onboarding/LoginScreen.kt index 4201b2dc..57ca2517 100644 --- a/app/src/main/java/org/memento/presentation/onboarding/LoginScreen.kt +++ b/app/src/main/java/org/memento/presentation/onboarding/LoginScreen.kt @@ -38,7 +38,7 @@ import com.google.android.gms.common.api.ApiException import org.memento.BuildConfig import org.memento.R import org.memento.core.util.UiState -import org.memento.domain.entity.UserInfo +import org.memento.domain.entity.LoginInfo import org.memento.presentation.onboarding.component.SocialLoginButton import org.memento.presentation.onboarding.viewmodel.LoginViewModel import org.memento.presentation.util.noRippleClickable @@ -69,7 +69,7 @@ fun LoginScreen( when (uiState) { is UiState.Loading -> {} is UiState.Success -> { - val data = (uiState as UiState.Success).data + val data = (uiState as UiState.Success).data viewModel.saveToken( accessToken = data.accessToken, refreshToken = data.refreshToken, diff --git a/app/src/main/java/org/memento/presentation/onboarding/OnboardingScreen1.kt b/app/src/main/java/org/memento/presentation/onboarding/OnboardingScreen1.kt index 58daafdf..79603b53 100644 --- a/app/src/main/java/org/memento/presentation/onboarding/OnboardingScreen1.kt +++ b/app/src/main/java/org/memento/presentation/onboarding/OnboardingScreen1.kt @@ -21,42 +21,43 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import org.memento.R import org.memento.presentation.component.MementoBottomSheet import org.memento.presentation.component.MementoChipSelector import org.memento.presentation.component.MementoWakeTimePicker import org.memento.presentation.onboarding.component.OnboardingBottomButton import org.memento.presentation.onboarding.component.OnboardingTopAppBar +import org.memento.presentation.onboarding.viewmodel.OnboardingViewModel import org.memento.presentation.type.OnboardingTopType import org.memento.presentation.type.SelectorType +import org.memento.presentation.type.SetTimeType import org.memento.ui.theme.darkModeColors import org.memento.ui.theme.defaultMementoTypography -enum class SETTIME { - WAKEUP, - WINDDOWN, -} - @OptIn(ExperimentalMaterial3Api::class) @Composable fun OnboardingScreen1( + viewModel: OnboardingViewModel = hiltViewModel(), navigateToOnboardingScreen2: () -> Unit, navigateToOnboardingScreen4: () -> Unit, ) { val initialTimeText = stringResource(id = R.string.time_example) + + val selectedTimeTextWakeUp by viewModel.wakeUpTime.collectAsStateWithLifecycle(initialTimeText) + val selectedTimeTextWindDown by viewModel.windDownTime.collectAsStateWithLifecycle(initialTimeText) + val sheetTimePickerState = rememberModalBottomSheetState() var showTimePickerBottomSheet by remember { mutableStateOf(false) } - var selectedTimeTextWakeUp by remember { mutableStateOf(initialTimeText) } - var selectedTimeTextWindDown by remember { mutableStateOf(initialTimeText) } - var isClickedWakeUp by remember { mutableStateOf(false) } var isClickedWindDown by remember { mutableStateOf(false) } var isSelectedWakeUp by remember { mutableStateOf(false) } var isSelectedWindDown by remember { mutableStateOf(false) } - var currentActiveSelector by remember { mutableStateOf(null) } + var currentActiveSelector by remember { mutableStateOf(null) } Column( modifier = @@ -66,7 +67,10 @@ fun OnboardingScreen1( ) { OnboardingTopAppBar( type = OnboardingTopType.PAGE1, - onSkipClick = navigateToOnboardingScreen4, + onSkipClick = { + viewModel.fetchUserInfoUpdate() + navigateToOnboardingScreen4() + }, ) Spacer(Modifier.height(40.dp)) Column { @@ -89,10 +93,10 @@ fun OnboardingScreen1( ) MementoChipSelector( selectorType = SelectorType.TIMESELECTOR, - content = selectedTimeTextWakeUp, + content = selectedTimeTextWakeUp ?: initialTimeText, isClicked = isClickedWakeUp, onClickedChange = { - currentActiveSelector = SETTIME.WAKEUP + currentActiveSelector = SetTimeType.WAKEUP showTimePickerBottomSheet = true }, ) @@ -117,10 +121,10 @@ fun OnboardingScreen1( ) MementoChipSelector( selectorType = SelectorType.TIMESELECTOR, - content = selectedTimeTextWindDown, + content = selectedTimeTextWindDown ?: initialTimeText, isClicked = isClickedWindDown, onClickedChange = { - currentActiveSelector = SETTIME.WINDDOWN + currentActiveSelector = SetTimeType.WINDDOWN showTimePickerBottomSheet = true }, ) @@ -131,15 +135,15 @@ fun OnboardingScreen1( MementoWakeTimePicker( onTimeSelected = { selectedTime -> when (currentActiveSelector) { - SETTIME.WAKEUP -> { - selectedTimeTextWakeUp = selectedTime + SetTimeType.WAKEUP -> { + viewModel.setWakeUpTime(selectedTime) isClickedWakeUp = true isClickedWindDown = false isSelectedWakeUp = true } - SETTIME.WINDDOWN -> { - selectedTimeTextWindDown = selectedTime + SetTimeType.WINDDOWN -> { + viewModel.setWindDownTime(selectedTime) isClickedWindDown = true isClickedWakeUp = false } @@ -155,7 +159,7 @@ fun OnboardingScreen1( isClickedWakeUp = false isClickedWindDown = false - if (currentActiveSelector == SETTIME.WINDDOWN) { + if (currentActiveSelector == SetTimeType.WINDDOWN) { isSelectedWindDown = true } }, @@ -165,7 +169,12 @@ fun OnboardingScreen1( OnboardingBottomButton( content = R.string.onboarding_next, isSelected = isSelectedWakeUp && isSelectedWindDown, - onSelected = { if (isSelectedWakeUp && isSelectedWindDown) navigateToOnboardingScreen2() }, + onSelected = { + if (isSelectedWakeUp && isSelectedWindDown) { + viewModel.fetchUserInfoUpdate() + navigateToOnboardingScreen2() + } + }, ) } } diff --git a/app/src/main/java/org/memento/presentation/onboarding/OnboardingScreen2.kt b/app/src/main/java/org/memento/presentation/onboarding/OnboardingScreen2.kt index d8735b70..83c43fd9 100644 --- a/app/src/main/java/org/memento/presentation/onboarding/OnboardingScreen2.kt +++ b/app/src/main/java/org/memento/presentation/onboarding/OnboardingScreen2.kt @@ -1,5 +1,6 @@ package org.memento.presentation.onboarding +import android.util.Log import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -9,7 +10,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -17,42 +17,47 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel import okhttp3.internal.immutableListOf import org.memento.R import org.memento.presentation.onboarding.component.CheckboxWithTextField import org.memento.presentation.onboarding.component.OnboardingBottomButton import org.memento.presentation.onboarding.component.OnboardingTopAppBar import org.memento.presentation.onboarding.component.RoundCheckboxWithText +import org.memento.presentation.onboarding.viewmodel.OnboardingViewModel import org.memento.presentation.type.OnboardingTopType +data class JobItem(val jobCode: String, val jobName: String) + @Composable fun OnboardingScreen2( + viewModel: OnboardingViewModel = hiltViewModel(), navigateToOnboardingScreen3: () -> Unit, navigateToOnboardingScreen4: () -> Unit, popBackStack: () -> Unit, ) { val jobItems = immutableListOf( - R.string.onboarding2_tech, - R.string.onboarding2_data, - R.string.onboarding2_design, - R.string.onboarding2_business, - R.string.onboarding2_edu, - R.string.onboarding2_health, - R.string.onboarding2_free, - R.string.onboarding2_service, - R.string.onboarding2_engineer, + JobItem("TECHNOLOGY", stringResource(R.string.onboarding2_tech)), + JobItem("DATA_ANALYTICS", stringResource(R.string.onboarding2_data)), + JobItem("DESIGN_CREATIVITY", stringResource(R.string.onboarding2_design)), + JobItem("BUSINESS_MANAGEMENT", stringResource(R.string.onboarding2_business)), + JobItem("EDUCATION_TRAINING", stringResource(R.string.onboarding2_edu)), + JobItem("HEALTHCARE_WELLNESS", stringResource(R.string.onboarding2_health)), + JobItem("FREELANCE_SELF_EMPLOYMENT", stringResource(R.string.onboarding2_free)), + JobItem("SERVICE_HOSPITALITY", stringResource(R.string.onboarding2_service)), + JobItem("ENGINEERING_MANUFACTURING", stringResource(R.string.onboarding2_engineer)), ) - var selectedIndex by remember { mutableStateOf(-1) } - var textFieldValue by remember { mutableStateOf("") } - var isCheckedTextField by remember { mutableStateOf(false) } + var selectedIndex by remember { mutableStateOf(null) } + var textFieldValue by remember { mutableStateOf("") } - LaunchedEffect(selectedIndex, isCheckedTextField) { - if (selectedIndex != -1) { - isCheckedTextField = false - } - } + val job by viewModel.job.collectAsStateWithLifecycle() + val jobOtherDetail by viewModel.jobOtherDetail.collectAsStateWithLifecycle() + + var isCheckedTextField by remember { mutableStateOf(false) } Box( modifier = @@ -63,7 +68,10 @@ fun OnboardingScreen2( Column { OnboardingTopAppBar( type = OnboardingTopType.PAGE2, - onSkipClick = { navigateToOnboardingScreen4() }, + onSkipClick = { + viewModel.fetchUserInfoUpdate() + navigateToOnboardingScreen4() + }, onBackClick = { popBackStack() }, ) Spacer(Modifier.height(20.dp)) @@ -74,21 +82,27 @@ fun OnboardingScreen2( ) { itemsIndexed(jobItems, key = { index, _ -> index }) { index, item -> RoundCheckboxWithText( - content = item, + content = item.jobName, isChecked = selectedIndex == index, onCheckedChange = { isChecked -> + viewModel.setJob(newjob = item.jobCode) + viewModel.setJobOtherDetail("") selectedIndex = if (isChecked) index else null isCheckedTextField = false textFieldValue = "" + Log.e("text", item.jobName) }, ) } item(key = R.string.onboarding2_other) { CheckboxWithTextField( - isChecked = isCheckedTextField, + isChecked = (isCheckedTextField == true), onCheckedChange = { isChecked -> + viewModel.setJob("OTHER") + viewModel.setJobOtherDetail(textFieldValue) isCheckedTextField = isChecked - if (isChecked) selectedIndex = -1 + if (isChecked) selectedIndex = null + Log.e("textfield", isCheckedTextField.toString()) }, text = textFieldValue, onTextChange = { textFieldValue = it }, diff --git a/app/src/main/java/org/memento/presentation/onboarding/OnboardingScreen3.kt b/app/src/main/java/org/memento/presentation/onboarding/OnboardingScreen3.kt index 7e6f9443..7d7754fa 100644 --- a/app/src/main/java/org/memento/presentation/onboarding/OnboardingScreen3.kt +++ b/app/src/main/java/org/memento/presentation/onboarding/OnboardingScreen3.kt @@ -13,22 +13,25 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed 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.graphics.Color import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel import okhttp3.internal.immutableListOf import org.memento.R import org.memento.presentation.onboarding.component.OnboardingBottomButton import org.memento.presentation.onboarding.component.OnboardingQuestionBox import org.memento.presentation.onboarding.component.OnboardingTopAppBar +import org.memento.presentation.onboarding.viewmodel.OnboardingViewModel import org.memento.presentation.type.OnboardingTopType -import org.memento.presentation.type.YesNoButtonType +import org.memento.presentation.util.toYesNoType @Composable fun OnboardingScreen3( + viewModel: OnboardingViewModel = hiltViewModel(), navigateToOnboardingScreen4: () -> Unit, popBackStack: () -> Unit, ) { @@ -39,7 +42,20 @@ fun OnboardingScreen3( R.string.onboarding3_q3, R.string.onboarding3_q4, ) - var selectedOptions by remember { mutableStateOf(List(questionList.size) { null }) } + + val isStressedUnorganizedSchedule by viewModel.isStressedUnorganizedSchedule.collectAsStateWithLifecycle() + val isForgetImportantThings by viewModel.isForgetImportantThings.collectAsStateWithLifecycle() + val isPreferReminder by viewModel.isPreferReminder.collectAsStateWithLifecycle() + val isImportantBreaks by viewModel.isImportantBreaks.collectAsStateWithLifecycle() + + val selectedOptions = + listOf( + isStressedUnorganizedSchedule, + isForgetImportantThings, + isPreferReminder, + isImportantBreaks, + ) + val isAllSelected = selectedOptions.all { it != null } Box( @@ -52,7 +68,10 @@ fun OnboardingScreen3( OnboardingTopAppBar( type = OnboardingTopType.PAGE3, onBackClick = popBackStack, - onSkipClick = navigateToOnboardingScreen4, + onSkipClick = { + viewModel.fetchUserInfoUpdate() + navigateToOnboardingScreen4() + }, ) Spacer(Modifier.height(20.dp)) LazyColumn( @@ -63,12 +82,9 @@ fun OnboardingScreen3( itemsIndexed(questionList, key = { index, _ -> index }) { index, item -> OnboardingQuestionBox( question = item, - selectedOption = selectedOptions[index], + selectedOption = selectedOptions[index].toYesNoType(), onOptionSelected = { newSelection -> - selectedOptions = - selectedOptions.toMutableList().apply { - set(index, newSelection) - } + viewModel.updateQuestionAnswer(index, newSelection) }, ) Spacer(Modifier.height(18.dp)) diff --git a/app/src/main/java/org/memento/presentation/onboarding/OnboardingScreen4.kt b/app/src/main/java/org/memento/presentation/onboarding/OnboardingScreen4.kt index 7413ebd0..702d2f77 100644 --- a/app/src/main/java/org/memento/presentation/onboarding/OnboardingScreen4.kt +++ b/app/src/main/java/org/memento/presentation/onboarding/OnboardingScreen4.kt @@ -8,14 +8,17 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import org.memento.R import org.memento.presentation.onboarding.component.OnboardingBottomButton import org.memento.presentation.onboarding.component.OnboardingTopAppBar import org.memento.presentation.onboarding.component.SocialLoginButton +import org.memento.presentation.onboarding.viewmodel.OnboardingViewModel import org.memento.presentation.type.OnboardingTopType @Composable fun OnboardingScreen4( + viewModel: OnboardingViewModel = hiltViewModel(), navigateToMainScreen: () -> Unit, popBackStack: () -> Unit, ) { @@ -39,7 +42,9 @@ fun OnboardingScreen4( OnboardingBottomButton( content = R.string.onboarding_start, isSelected = true, - onSelected = navigateToMainScreen, + onSelected = { + navigateToMainScreen() + }, modifier = Modifier .padding(bottom = 10.dp), diff --git a/app/src/main/java/org/memento/presentation/onboarding/component/RoundCheckboxWithText.kt b/app/src/main/java/org/memento/presentation/onboarding/component/RoundCheckboxWithText.kt index 1b53a6d4..55a268e7 100644 --- a/app/src/main/java/org/memento/presentation/onboarding/component/RoundCheckboxWithText.kt +++ b/app/src/main/java/org/memento/presentation/onboarding/component/RoundCheckboxWithText.kt @@ -1,6 +1,5 @@ package org.memento.presentation.onboarding.component -import androidx.annotation.StringRes import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -24,7 +23,7 @@ import org.memento.ui.theme.defaultMementoTypography @Composable fun RoundCheckboxWithText( - @StringRes content: Int, + content: String, isChecked: Boolean, onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier, @@ -46,7 +45,7 @@ fun RoundCheckboxWithText( ) Spacer(modifier = Modifier.width(14.dp)) Text( - text = stringResource(content), + text = content, style = defaultMementoTypography.body_b_14, color = if (isChecked) darkModeColors.white else darkModeColors.gray06, modifier = @@ -64,7 +63,7 @@ fun RoundCheckboxWithTextExample() { Row { RoundCheckboxWithText( - content = R.string.onboarding2_free, + content = stringResource(R.string.onboarding2_free), isChecked = isChecked, onCheckedChange = { isChecked = it }, ) diff --git a/app/src/main/java/org/memento/presentation/onboarding/viewmodel/LoginViewModel.kt b/app/src/main/java/org/memento/presentation/onboarding/viewmodel/LoginViewModel.kt index 66b17912..d095b832 100644 --- a/app/src/main/java/org/memento/presentation/onboarding/viewmodel/LoginViewModel.kt +++ b/app/src/main/java/org/memento/presentation/onboarding/viewmodel/LoginViewModel.kt @@ -12,7 +12,7 @@ import kotlinx.coroutines.launch import org.memento.core.util.UiState import org.memento.data.local.TokenDataStore import org.memento.domain.entity.Login -import org.memento.domain.entity.UserInfo +import org.memento.domain.entity.LoginInfo import org.memento.domain.repository.AuthRepository import org.memento.domain.repository.LoginRepository import timber.log.Timber @@ -29,8 +29,8 @@ class LoginViewModel private val _user = MutableStateFlow(null) val user = _user.asStateFlow() - private val _uiState = MutableStateFlow>(UiState.Loading) - val uiState: StateFlow> = _uiState + private val _uiState = MutableStateFlow>(UiState.Loading) + val uiState: StateFlow> = _uiState private val _token = MutableStateFlow(null) val token: StateFlow = _token.asStateFlow() diff --git a/app/src/main/java/org/memento/presentation/onboarding/viewmodel/OnboardingViewModel.kt b/app/src/main/java/org/memento/presentation/onboarding/viewmodel/OnboardingViewModel.kt new file mode 100644 index 00000000..a37ed400 --- /dev/null +++ b/app/src/main/java/org/memento/presentation/onboarding/viewmodel/OnboardingViewModel.kt @@ -0,0 +1,109 @@ +package org.memento.presentation.onboarding.viewmodel + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import org.memento.core.util.UiState +import org.memento.domain.entity.UserInfo +import org.memento.domain.repository.UserInfoUpdateRepository +import org.memento.presentation.type.YesNoButtonType +import org.memento.presentation.util.to24HourFormat +import javax.inject.Inject + +@HiltViewModel +class OnboardingViewModel + @Inject + constructor( + private val userInfoUpdateRepository: UserInfoUpdateRepository, + ) : ViewModel() { + private val _uiState = MutableStateFlow>(UiState.Loading) + val uiState: StateFlow> = _uiState + + private val _wakeUpTime = MutableStateFlow(null) + val wakeUpTime: StateFlow = _wakeUpTime + + private val _windDownTime = MutableStateFlow(null) + val windDownTime: StateFlow = _windDownTime + + private val _job = MutableStateFlow(null) + val job: StateFlow = _job + + private val _jobOtherDetail = MutableStateFlow(null) + val jobOtherDetail: StateFlow = _jobOtherDetail + + private val _isStressedUnorganizedSchedule = MutableStateFlow(null) + val isStressedUnorganizedSchedule: StateFlow = _isStressedUnorganizedSchedule + + private val _isForgetImportantThings = MutableStateFlow(null) + val isForgetImportantThings: StateFlow = _isForgetImportantThings + + private val _isPreferReminder = MutableStateFlow(null) + val isPreferReminder: StateFlow = _isPreferReminder + + private val _isImportantBreaks = MutableStateFlow(null) + val isImportantBreaks: StateFlow = _isImportantBreaks + + fun fetchUserInfoUpdate() { + viewModelScope.launch { + Log.d("onboarding viewmodel", "enter to fetch user info") + _uiState.value = UiState.Loading + val result = + userInfoUpdateRepository.fetchUserInfo( + UserInfo( + wakeUpTime = _wakeUpTime.value?.to24HourFormat() ?: "08:00", + windDownTime = _windDownTime.value?.to24HourFormat() ?: "22:00", + job = _job.value ?: "TECHNOLOGY", + jobOtherDetail = _jobOtherDetail.value ?: "", + isStressedUnorganizedSchedule = _isStressedUnorganizedSchedule.value ?: true, + isForgetImportantThings = _isForgetImportantThings.value ?: true, + isPreferReminder = _isPreferReminder.value ?: false, + isImportantBreaks = _isImportantBreaks.value ?: true, + ), + ) + _uiState.value = + result.fold( + onSuccess = { + Log.e("onboarding success", "success to fetch user info") + UiState.Success(Unit) + }, + onFailure = { throwable -> + Log.e("onboarding", "Failed to fetch user info") + UiState.Failure + }, + ) + } + } + + fun setWakeUpTime(time: String) { + _wakeUpTime.value = time + } + + fun setWindDownTime(time: String) { + _windDownTime.value = time + } + + fun setJob(newjob: String) { + _job.value = newjob + } + + fun setJobOtherDetail(detail: String) { + _jobOtherDetail.value = detail + } + + fun updateQuestionAnswer( + index: Int, + answer: YesNoButtonType, + ) { + val value = answer == YesNoButtonType.YES + when (index) { + 0 -> _isStressedUnorganizedSchedule.value = value + 1 -> _isForgetImportantThings.value = value + 2 -> _isPreferReminder.value = value + 3 -> _isImportantBreaks.value = value + } + } + } diff --git a/app/src/main/java/org/memento/presentation/type/SetTimeType.kt b/app/src/main/java/org/memento/presentation/type/SetTimeType.kt new file mode 100644 index 00000000..16b15f75 --- /dev/null +++ b/app/src/main/java/org/memento/presentation/type/SetTimeType.kt @@ -0,0 +1,6 @@ +package org.memento.presentation.type + +enum class SetTimeType { + WAKEUP, + WINDDOWN, +} diff --git a/app/src/main/java/org/memento/presentation/util/ChangeBooleanToYesNoType.kt b/app/src/main/java/org/memento/presentation/util/ChangeBooleanToYesNoType.kt new file mode 100644 index 00000000..699360fd --- /dev/null +++ b/app/src/main/java/org/memento/presentation/util/ChangeBooleanToYesNoType.kt @@ -0,0 +1,11 @@ +package org.memento.presentation.util + +import org.memento.presentation.type.YesNoButtonType + +fun Boolean?.toYesNoType(): YesNoButtonType? { + return when (this) { + true -> YesNoButtonType.YES + false -> YesNoButtonType.NO + null -> null + } +} diff --git a/app/src/main/java/org/memento/presentation/util/FormatDate.kt b/app/src/main/java/org/memento/presentation/util/FormatDate.kt index 02636565..cd2115db 100644 --- a/app/src/main/java/org/memento/presentation/util/FormatDate.kt +++ b/app/src/main/java/org/memento/presentation/util/FormatDate.kt @@ -22,6 +22,24 @@ fun formatTime( return formatter // 03:30 PM } +fun String.to24HourFormat(): String { + val regex = """(\d{2}):(\d{2})\s?(AM|PM)""".toRegex() + val matchResult = regex.find(this) ?: return this + + val (hourStr, minuteStr, period) = matchResult.destructured + var hour = hourStr.toInt() + val minute = minuteStr.toInt() + + hour = + when { + period == "PM" && hour != 12 -> hour + 12 + period == "AM" && hour == 12 -> 0 + else -> hour + } + + return "%02d:%02d".format(hour, minute) +} + fun parseDateTime( date: String, time: String,