diff --git a/build.gradle.kts b/build.gradle.kts index b816fd0..0f72e95 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,6 +4,7 @@ buildscript { google() mavenCentral() gradlePluginPortal() + maven("https://naver.jfrog.io/artifactory/maven/") } dependencies { diff --git a/presentation/src/main/java/com/meongmoryteam/presentation/ui/bottom/BottomNavigation.kt b/presentation/src/main/java/com/meongmoryteam/presentation/ui/bottom/BottomNavigation.kt index 1f2cfd0..54fbc48 100644 --- a/presentation/src/main/java/com/meongmoryteam/presentation/ui/bottom/BottomNavigation.kt +++ b/presentation/src/main/java/com/meongmoryteam/presentation/ui/bottom/BottomNavigation.kt @@ -25,4 +25,6 @@ enum class MeongMoryRoute(val route: String) { MAP("map"), HOME("home"), MY_PAGE("my-page"), + EDIT_NICKNAME("edit-nickname"), + QUESTION("question"), } \ No newline at end of file diff --git a/presentation/src/main/java/com/meongmoryteam/presentation/ui/bottom/BottomNavigationScreen.kt b/presentation/src/main/java/com/meongmoryteam/presentation/ui/bottom/BottomNavigationScreen.kt index 19f39cf..f551b91 100644 --- a/presentation/src/main/java/com/meongmoryteam/presentation/ui/bottom/BottomNavigationScreen.kt +++ b/presentation/src/main/java/com/meongmoryteam/presentation/ui/bottom/BottomNavigationScreen.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.navigation.NavDestination +import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController import com.meongmoryteam.presentation.ui.theme.GRAY100 @@ -48,7 +49,7 @@ fun MeongMoryBottomNavigation( } ) }, - selected = currentDestination?.route == bottomItem.route, + selected = currentDestination?.hierarchy?.any { it.route == bottomItem.route } == true, onClick = { navigateToScreen(bottomItem) }, colors = NavigationBarItemDefaults.colors(indicatorColor = Color.White), ) diff --git a/presentation/src/main/java/com/meongmoryteam/presentation/ui/main/MainActivity.kt b/presentation/src/main/java/com/meongmoryteam/presentation/ui/main/MainActivity.kt index f0484c6..21e58f2 100644 --- a/presentation/src/main/java/com/meongmoryteam/presentation/ui/main/MainActivity.kt +++ b/presentation/src/main/java/com/meongmoryteam/presentation/ui/main/MainActivity.kt @@ -1,20 +1,12 @@ package com.meongmoryteam.presentation.ui.main import android.os.Bundle -import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.meongmoryteam.presentation.ui.bottom.MeongMoryRoute import com.meongmoryteam.presentation.ui.theme.MeongmoryTheme import dagger.hilt.android.AndroidEntryPoint @@ -29,8 +21,16 @@ class MainActivity : ComponentActivity() { private fun setMainScreen() { setContent { MeongmoryTheme { - MainScreen() + val navController = rememberNavController() + NavHost( + navController = navController, + startDestination = MeongMoryRoute.HOME.route, + ) { + composable(route = MeongMoryRoute.HOME.route) { + MainScreen() + } + } } } } -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/meongmoryteam/presentation/ui/main/MainContract.kt b/presentation/src/main/java/com/meongmoryteam/presentation/ui/main/MainContract.kt new file mode 100644 index 0000000..a33e491 --- /dev/null +++ b/presentation/src/main/java/com/meongmoryteam/presentation/ui/main/MainContract.kt @@ -0,0 +1,21 @@ +package com.meongmoryteam.presentation.ui.main + +import com.meongmoryteam.presentation.base.ViewEvent +import com.meongmoryteam.presentation.base.ViewSideEffect +import com.meongmoryteam.presentation.base.ViewState + +class MainContract { + data class MainViewState( + val loginState: LoginState = LoginState.NONE, + ) : ViewState + + sealed class MainSideEffect : ViewSideEffect { + } + + sealed class MainEvent : ViewEvent { + } + + enum class LoginState { + NONE + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/meongmoryteam/presentation/ui/main/MainScreen.kt b/presentation/src/main/java/com/meongmoryteam/presentation/ui/main/MainScreen.kt index 7270c1e..0b5c7bd 100644 --- a/presentation/src/main/java/com/meongmoryteam/presentation/ui/main/MainScreen.kt +++ b/presentation/src/main/java/com/meongmoryteam/presentation/ui/main/MainScreen.kt @@ -4,6 +4,9 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.navigation.NavHostController @@ -11,14 +14,14 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController -import com.meongmoryteam.presentation.ui.bottom.BottomNavigation import com.meongmoryteam.presentation.ui.bottom.MeongMoryBottomNavigation +import com.meongmoryteam.presentation.ui.bottom.MeongMoryRoute import com.meongmoryteam.presentation.ui.bottom.navigateBottomNavigationScreen import com.meongmoryteam.presentation.ui.home.HomeScreen import com.meongmoryteam.presentation.ui.map.MapScreen -import com.meongmoryteam.presentation.ui.myPage.MyPageProfileButton import com.meongmoryteam.presentation.ui.myPage.MyPageScreen -import com.meongmoryteam.presentation.ui.myPage.profile.MypageProfileScreen +import com.meongmoryteam.presentation.ui.myPage.profile.MyPageProfileScreen +import com.meongmoryteam.presentation.ui.myPage.question.MyPageQuestionScreen @Composable fun MainScreen( @@ -26,36 +29,60 @@ fun MainScreen( ) { val navBackStackEntry by navController.currentBackStackEntryAsState() val currentDestination = navBackStackEntry?.destination + var bottomBarState by rememberSaveable { mutableStateOf(true) } Scaffold( bottomBar = { - MeongMoryBottomNavigation( - currentDestination = currentDestination, - navigateToScreen = { navigationItem -> - navigateBottomNavigationScreen( - navController = navController, - navigationItem = navigationItem, - ) - } - ) + if (bottomBarState) { + MeongMoryBottomNavigation( + currentDestination = currentDestination, + navigateToScreen = { navigationItem -> + navigateBottomNavigationScreen( + navController = navController, + navigationItem = navigationItem, + ) + } + ) + } } ) { padding -> NavHost( modifier = Modifier.padding(padding), navController = navController, - startDestination = BottomNavigation.HOME.route, + startDestination = MeongMoryRoute.HOME.route, ) { - composable(route = BottomNavigation.HOME.route) { + composable(route = MeongMoryRoute.HOME.route) { HomeScreen() } - composable(route = BottomNavigation.MAP.route) { + composable(route = MeongMoryRoute.MAP.route) { MapScreen() } - composable(route = BottomNavigation.MY_PAGE.route) { - MyPageScreen() + composable(route = MeongMoryRoute.MY_PAGE.route) { + MyPageScreen( + navigateToEditNickNameScreen = { navController.navigate(MeongMoryRoute.EDIT_NICKNAME.route) }, + navigateToQuestionScreen = { navController.navigate(MeongMoryRoute.QUESTION.route) }, + ) + } + composable(route = MeongMoryRoute.EDIT_NICKNAME.route) { + MyPageProfileScreen( + navigateToPrevious = { navController.navigate(MeongMoryRoute.MY_PAGE.route) } + ) + } + composable(route = MeongMoryRoute.QUESTION.route) { + MyPageQuestionScreen( + navigateToPrevious = { navController.navigate(MeongMoryRoute.MY_PAGE.route)} + ) } } } + + // 바텀 네비게이션 보이기, 숨기기 + bottomBarState = when (currentDestination?.route) { + MeongMoryRoute.HOME.route -> true + MeongMoryRoute.MAP.route -> true + MeongMoryRoute.MY_PAGE.route -> true + else -> false + } } @Preview diff --git a/presentation/src/main/java/com/meongmoryteam/presentation/ui/main/MainViewModel.kt b/presentation/src/main/java/com/meongmoryteam/presentation/ui/main/MainViewModel.kt new file mode 100644 index 0000000..eadad17 --- /dev/null +++ b/presentation/src/main/java/com/meongmoryteam/presentation/ui/main/MainViewModel.kt @@ -0,0 +1,15 @@ +package com.meongmoryteam.presentation.ui.main + +import com.meongmoryteam.presentation.base.BaseViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import com.meongmoryteam.presentation.ui.main.MainContract.* + +@HiltViewModel +class MainViewModel @Inject constructor( +) : BaseViewModel( + MainViewState() +) { + override fun handleEvents(event: MainEvent) { + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/meongmoryteam/presentation/ui/map/MapScreen.kt b/presentation/src/main/java/com/meongmoryteam/presentation/ui/map/MapScreen.kt index 82dd220..51b68b0 100644 --- a/presentation/src/main/java/com/meongmoryteam/presentation/ui/map/MapScreen.kt +++ b/presentation/src/main/java/com/meongmoryteam/presentation/ui/map/MapScreen.kt @@ -3,18 +3,20 @@ package com.meongmoryteam.presentation.ui.map import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview +import com.meongmoryteam.presentation.ui.theme.MeongmoryTheme @Composable -fun MapScreen( - -) { +fun MapScreen() { Text( text = "MapScreen", ) } -@Preview +@Preview(showBackground = true) + @Composable fun MapScreenPreview() { - MapScreen() + MeongmoryTheme { + MapScreen() + } } \ No newline at end of file diff --git a/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/MyPageContract.kt b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/MyPageContract.kt new file mode 100644 index 0000000..133a348 --- /dev/null +++ b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/MyPageContract.kt @@ -0,0 +1,25 @@ +package com.meongmoryteam.presentation.ui.myPage + +import com.meongmoryteam.presentation.base.LoadState +import com.meongmoryteam.presentation.base.ViewEvent +import com.meongmoryteam.presentation.base.ViewSideEffect +import com.meongmoryteam.presentation.base.ViewState + +class MyPageContract { + data class MyPageViewState( + val loadState: LoadState = LoadState.SUCCESS, + val nickName: String = "", + val isDialogVisible: Boolean = false + ) : ViewState + + sealed class MyPageSideEffect : ViewSideEffect { + object NavigateToEditProfile : MyPageSideEffect() + object NavigateToQuestion : MyPageSideEffect() + } + + sealed class MyPageEvent : ViewEvent { + object InitMyPageScreen : MyPageEvent() + object OnClickProfileEditButtonClicked : MyPageEvent() + object OnQuestionClicked : MyPageEvent() + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/MyPageScreen.kt b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/MyPageScreen.kt index 2751a16..e57a801 100644 --- a/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/MyPageScreen.kt +++ b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/MyPageScreen.kt @@ -20,7 +20,7 @@ import androidx.compose.material3.Divider import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -34,8 +34,7 @@ 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 androidx.navigation.NavHostController -import androidx.navigation.compose.rememberNavController +import androidx.hilt.navigation.compose.hiltViewModel import com.meongmoryteam.presentation.R import com.meongmoryteam.presentation.base.LogoutAlertDialog import com.meongmoryteam.presentation.base.SecessionAlertDialog @@ -52,266 +51,356 @@ val PADDING_16 = 16.dp @Composable fun MyPageScreen( - navController: NavHostController = rememberNavController() + viewModel: MyPageViewModel = hiltViewModel(), + navigateToEditNickNameScreen: () -> Unit, + navigateToQuestionScreen: () -> Unit, ) { + LaunchedEffect(true) { + viewModel.setEvent(MyPageContract.MyPageEvent.InitMyPageScreen) + } + + LaunchedEffect(viewModel.effect) { + viewModel.effect.collect { effect -> + when (effect) { + is MyPageContract.MyPageSideEffect.NavigateToEditProfile -> { + navigateToEditNickNameScreen() + } + + is MyPageContract.MyPageSideEffect.NavigateToQuestion -> { + navigateToQuestionScreen() + } + } + } + } + Column { MyPageTitle() - MyPageProfile() - MyPageList() + MyPageProfile( + OnClickEditNickname = { + viewModel.setEvent(MyPageContract.MyPageEvent.OnClickProfileEditButtonClicked) + } + ) + MyPageList( + onQuestionClicked = { + viewModel.setEvent(MyPageContract.MyPageEvent.OnQuestionClicked) + } + ) } } -@Composable -fun MyPageTitle() { - Box( - modifier = Modifier - .padding(24.dp) - .fillMaxWidth(), - contentAlignment = Alignment.Center - ) { - Text( - text = stringResource(R.string.my_page_title), - fontSize = 15.sp - ) - + @Composable + fun MyPageTitle() { + Box( + modifier = Modifier + .padding(24.dp) + .fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + Text( + text = stringResource(R.string.my_page_title), + fontSize = 15.sp + ) + } } -} -@Composable -fun MyPageProfile() { + @Composable + fun MyPageProfile( + OnClickEditNickname: () -> Unit, + ) { // 상단 프로필 메뉴 - Column { - Row( - Modifier - .padding(PADDING_16) - .fillMaxWidth() - .clip(RoundedCornerShape(20)) - .border(width = 1.dp, color = MyPageYellowStroke, shape = RoundedCornerShape(20.dp)) - .background(color = Color(MyPageYellowFill.value)), - // 중앙 정렬 - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center - ) { - Column( - modifier = Modifier + Column { + Row( + Modifier .padding(PADDING_16) .fillMaxWidth() + .clip(RoundedCornerShape(20)) + .border( + width = 1.dp, + color = MyPageYellowStroke, + shape = RoundedCornerShape(20.dp) + ) + .background(color = Color(MyPageYellowFill.value)), + // 중앙 정렬 + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center ) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween + Column( + modifier = Modifier + .padding(PADDING_16) + .fillMaxWidth() ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = stringResource(R.string.my_page_profile), + fontWeight = FontWeight.Bold, + fontSize = 14.sp + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + MyPageProfileButton( + stringResource(R.string.my_page_profile_alarm), + onClick = { } + ) + MyPageProfileButton( + stringResource(R.string.my_page_profile_edit), + onClick = OnClickEditNickname + ) + } + } Text( - text = stringResource(R.string.my_page_profile), - fontWeight = FontWeight.Bold, - fontSize = 14.sp + modifier = Modifier.padding(bottom = 8.dp), + text = stringResource(R.string.my_page_phone_number), + fontSize = 10.sp ) - - Row(modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End) { - MyPageProfileButton(stringResource(R.string.my_page_profile_alarm)) - MyPageProfileButton(stringResource(R.string.my_page_profile_edit)) - } - - } - Text( - modifier = Modifier.padding(bottom = 8.dp), - text = stringResource(R.string.my_page_phone_number), - fontSize = 10.sp - ) } - } - - } -} - -@Composable -fun MyPageList() { - // 마이페이지 목록 부분 - Row( - modifier = Modifier - .padding(PADDING_16) - .clip(RoundedCornerShape(5)) - .background(color = MyPageYellowFill) - .border(width = 1.dp, color = MyPageYellowStroke, shape = RoundedCornerShape(19.dp)) - .fillMaxWidth() - .fillMaxHeight() + @Composable + fun MyPageList( + onQuestionClicked: () -> Unit, ) { - Column{ - Text( - text = stringResource(R.string.my_page_list_account), - fontWeight = FontWeight.Bold, - modifier = Modifier - .padding(start = 24.dp, top = 24.dp, bottom = 8.dp), - color = ListTitle, - fontSize = 11.sp - ) - - ListButton(R.drawable.ic_coin, stringResource(R.string.my_page_pro_ver)) - ListButton(R.drawable.ic_logout, stringResource(R.string.my_page_logout), stringResource(R.string.my_page_logout)) - ListButton(R.drawable.ic_user, stringResource(R.string.my_page_drop), stringResource(R.string.my_page_drop)) - - Spacer(modifier = Modifier.padding(top = PADDING_16)) - - // 구분선 - Divider( - color = ListDivider, - modifier = Modifier - .fillMaxWidth() - .height(1.dp) - .padding(start = 24.dp, end = 24.dp) - ) - - - Text( - text = stringResource(R.string.my_page_support), - fontWeight = FontWeight.Bold, - modifier = Modifier.padding(start = 24.dp, top = 24.dp, bottom = 8.dp), - color = ListTitle, - fontSize = 11.sp - ) - - ListButton(R.drawable.ic_mail, stringResource(R.string.my_page_notice)) - ListButton(R.drawable.ic_send, stringResource(R.string.my_page_question)) - - - Spacer(modifier = Modifier.padding(top = PADDING_16)) - - // 구분선 - Divider( - color = ListDivider, - modifier = Modifier - .fillMaxWidth() - .height(1.dp) - .padding(start = 24.dp, end = 24.dp) - ) - - Text( - text = stringResource(R.string.my_page_app_info), - fontWeight = FontWeight.Bold, - modifier = Modifier.padding(start = 24.dp, top = 24.dp, bottom = 8.dp), - color = ListTitle, - fontSize = 11.sp - ) - ListButton(R.drawable.ic_info, stringResource(R.string.my_page_clause)) - ListButton(R.drawable.ic_unlock, stringResource(R.string.my_page_personal)) + // 마이페이지 목록 부분 + Row( + modifier = Modifier + .padding(PADDING_16) + .clip(RoundedCornerShape(5)) + .background(color = MyPageYellowFill) + .border(width = 1.dp, color = MyPageYellowStroke, shape = RoundedCornerShape(19.dp)) + .fillMaxWidth() + .fillMaxHeight() + ) { + Column { + Text( + text = stringResource(R.string.my_page_list_account), + fontWeight = FontWeight.Bold, + modifier = Modifier + .padding(start = 24.dp, top = 24.dp, bottom = 8.dp), + color = ListTitle, + fontSize = 11.sp + ) + ListButton( + R.drawable.ic_coin, + stringResource(R.string.my_page_pro_ver), + onClick = { }, + ) + ListButton( + R.drawable.ic_logout, + stringResource(R.string.my_page_logout), + stringResource(R.string.my_page_logout), + onClick = { }, + ) + ListButton( + R.drawable.ic_user, + stringResource(R.string.my_page_drop), + stringResource(R.string.my_page_drop), + onClick = { }, + ) - } + Spacer(modifier = Modifier.padding(top = PADDING_16)) + // 구분선 + Divider( + color = ListDivider, + modifier = Modifier + .fillMaxWidth() + .height(1.dp) + .padding(start = 24.dp, end = 24.dp) + ) - } + Text( + text = stringResource(R.string.my_page_support), + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(start = 24.dp, top = 24.dp, bottom = 8.dp), + color = ListTitle, + fontSize = 11.sp + ) -} + ListButton( + R.drawable.ic_mail, + stringResource(R.string.my_page_notice), + onClick = { }, + ) + ListButton( + R.drawable.ic_send, + stringResource(R.string.my_page_question), + onClick = onQuestionClicked, + ) -@Composable -fun MyPageProfileButton( - buttonText: String? = null -) { - Column(modifier = Modifier) { + Spacer(modifier = Modifier.padding(top = PADDING_16)) - Button( - onClick = { /*TODO*/ }, - modifier = Modifier - .padding(end = PADDING_8) - .wrapContentSize(), - contentPadding = PaddingValues(4.dp), - colors = ButtonDefaults.buttonColors(MyPageProfileEditButton, Color.Black) - ) { + // 구분선 + Divider( + color = ListDivider, + modifier = Modifier + .fillMaxWidth() + .height(1.dp) + .padding(start = 24.dp, end = 24.dp) + ) - if (buttonText != null) { Text( - text = buttonText, - fontSize = 9.sp + text = stringResource(R.string.my_page_app_info), + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(start = 24.dp, top = 24.dp, bottom = 8.dp), + color = ListTitle, + fontSize = 11.sp + ) + ListButton( + R.drawable.ic_info, + stringResource(R.string.my_page_clause), + onClick = { }, + ) + ListButton( + R.drawable.ic_unlock, + stringResource(R.string.my_page_personal), + onClick = { }, ) } } } -} - -@Composable -fun ListButton( - buttonIcon: Int? = null, - buttonText: String, - onClickAction: String? = null -) { + @Composable + fun MyPageProfileButton( + buttonText: String? = null, + onClickAction: String? = null, + onClick: () -> Unit, + viewModel: MyPageViewModel = hiltViewModel() + ) { + val refreshButton = viewModel.refreshButton - val openDialogCustom = remember { - mutableStateOf(false) + Column(modifier = Modifier) { + Button( + onClick = { + onClick() + if (onClickAction != null) { + // ViewModel 내의 refreshButton 값을 직접 수정 + viewModel.refreshButton.value = true + } + }, + modifier = Modifier + .padding(end = PADDING_8) + .wrapContentSize(), + contentPadding = PaddingValues(4.dp), + colors = ButtonDefaults.buttonColors(MyPageProfileEditButton, Color.Black) + ) { + if (buttonText != null) { + Text( + text = buttonText, + fontSize = 9.sp + ) + } + } + } + // 버튼마다 다른 다이어로그 + if (refreshButton.value) { + if (onClickAction == stringResource(R.string.my_page_profile_edit)) { + LogoutAlertDialog(openDialogCustom = refreshButton) + } + if (onClickAction == stringResource(R.string.my_page_question)) { + SecessionAlertDialog(openDialogCustom = refreshButton) + } + } } - Row( - modifier = Modifier - .fillMaxWidth() + @Composable + fun ListButton( + buttonIcon: Int? = null, + buttonText: String, + onClickAction: String? = null, + onClick: () -> Unit, ) { - Button( - onClick = { openDialogCustom.value = true }, - colors = ButtonDefaults.buttonColors( - contentColor = Color.Unspecified, - containerColor = Color.Transparent - ) - ) { - - if (buttonIcon != null) { - Icon( - imageVector = ImageVector.vectorResource(buttonIcon), - contentDescription = stringResource(R.string.my_page_list_button_icon), - ) - } - Text( - text = buttonText, - Modifier - .padding(start = PADDING_16), - color = Color.Black, - fontSize = 12.sp - ) + val openDialogCustom = remember { + mutableStateOf(false) } - - Box(modifier = Modifier - .padding() - .fillMaxWidth(), - contentAlignment = Alignment.CenterEnd + val refreshButton = remember { + mutableStateOf(false) + } + Row( + modifier = Modifier + .fillMaxWidth() ) { Button( - onClick = { openDialogCustom.value = true }, - colors = ButtonDefaults.buttonColors(Color.Transparent, ListNextButton) + onClick = { + onClick() + openDialogCustom.value = true + if (onClickAction != null) { + refreshButton.value = true + } + }, + colors = ButtonDefaults.buttonColors( + contentColor = Color.Unspecified, + containerColor = Color.Transparent + ) ) { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.ic_right_btn), - contentDescription = stringResource(R.string.my_page_list_button_right), + + if (buttonIcon != null) { + Icon( + imageVector = ImageVector.vectorResource(buttonIcon), + contentDescription = stringResource(R.string.my_page_list_button_icon), + ) + } + Text( + text = buttonText, + Modifier + .padding(start = PADDING_16), + color = Color.Black, + fontSize = 12.sp ) } + Box( + modifier = Modifier + .padding() + .fillMaxWidth(), + contentAlignment = Alignment.CenterEnd + ) { + Button( + onClick = { + onClick() + openDialogCustom.value = true + if (onClickAction != null) { + refreshButton.value = true + } + }, + colors = ButtonDefaults.buttonColors(Color.Transparent, ListNextButton) + ) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_right_btn), + contentDescription = stringResource(R.string.my_page_list_button_right), + ) + } + } } - } - - // 버튼마다 다른 다이어로그 - if (openDialogCustom.value) { - if (onClickAction == stringResource(R.string.my_page_logout)) { - LogoutAlertDialog(openDialogCustom = openDialogCustom) - } - - if (onClickAction == stringResource(R.string.my_page_drop)) { - SecessionAlertDialog(openDialogCustom = openDialogCustom) + // 버튼마다 다른 다이어로그 + if (openDialogCustom.value) { + if (onClickAction == stringResource(R.string.my_page_logout)) { + LogoutAlertDialog(openDialogCustom = openDialogCustom) + } + if (onClickAction == stringResource(R.string.my_page_drop)) { + SecessionAlertDialog(openDialogCustom = openDialogCustom) + } } - } -} - -@Preview(showBackground = true) -@Composable -fun PreviewMyPageScreen() { - MeongmoryTheme { - MyPageScreen() + @Preview(showBackground = true) + @Composable + fun PreviewMyPageScreen() { + MeongmoryTheme { + MyPageScreen( + navigateToEditNickNameScreen = { }, + navigateToQuestionScreen = { }, + ) + } } -} diff --git a/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/MyPageViewModel.kt b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/MyPageViewModel.kt new file mode 100644 index 0000000..9f91d3e --- /dev/null +++ b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/MyPageViewModel.kt @@ -0,0 +1,35 @@ +package com.meongmoryteam.presentation.ui.myPage + +import androidx.compose.runtime.mutableStateOf +import com.meongmoryteam.presentation.base.BaseViewModel +import com.meongmoryteam.presentation.base.LoadState +import com.meongmoryteam.presentation.ui.myPage.MyPageContract.MyPageEvent +import com.meongmoryteam.presentation.ui.myPage.MyPageContract.MyPageSideEffect +import com.meongmoryteam.presentation.ui.myPage.MyPageContract.MyPageViewState +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + + +@HiltViewModel +class MyPageViewModel @Inject constructor( +) : BaseViewModel( + MyPageViewState() +) { + val refreshButton = mutableStateOf(false) + + override fun handleEvents(event: MyPageEvent) { + when (event) { + is MyPageEvent.InitMyPageScreen -> { + updateState { copy(loadState = LoadState.SUCCESS) } + } + + is MyPageEvent.OnClickProfileEditButtonClicked -> { + sendEffect({ MyPageSideEffect.NavigateToEditProfile }) + } + + is MyPageEvent.OnQuestionClicked -> { + sendEffect({ MyPageSideEffect.NavigateToQuestion }) + } + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/profile/MyPageProfileActivity.kt b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/profile/MyPageProfileActivity.kt deleted file mode 100644 index 33930ab..0000000 --- a/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/profile/MyPageProfileActivity.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.meongmoryteam.presentation.ui.myPage.profile - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import dagger.hilt.android.AndroidEntryPoint - - -@AndroidEntryPoint -class MyPageProfileActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - MypageProfileScreen() - } - } -} \ No newline at end of file diff --git a/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/profile/MyPageProfileContract.kt b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/profile/MyPageProfileContract.kt new file mode 100644 index 0000000..05d727d --- /dev/null +++ b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/profile/MyPageProfileContract.kt @@ -0,0 +1,31 @@ +package com.meongmoryteam.presentation.ui.myPage.profile + +import com.meongmoryteam.presentation.base.LoadState +import com.meongmoryteam.presentation.base.ViewEvent +import com.meongmoryteam.presentation.base.ViewSideEffect +import com.meongmoryteam.presentation.base.ViewState + +class MyPageProfileContract { + // 모델 클래스 + data class MyPageProfileViewState( + val loadState: LoadState = LoadState.SUCCESS, + val nickName: String = "", + val nickNameHint: String = "", + val isFilled: Boolean = false, + val isError: Boolean = true + ) : ViewState + + sealed class MyPageProfileSideEffect : ViewSideEffect { + object NavigateToMyPageScreen : MyPageProfileSideEffect() + } + + sealed class MyPageProfileEvent : ViewEvent { + data class FillNickName( + val nickName: String + ) : MyPageProfileEvent() + + object ClearNickName : MyPageProfileEvent() + object OnClickPreviousButton : MyPageProfileEvent() + object OnClickChangeButton : MyPageProfileEvent() + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/profile/MyPageProfileScreen.kt b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/profile/MyPageProfileScreen.kt index 4d71c9c..828a21b 100644 --- a/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/profile/MyPageProfileScreen.kt +++ b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/profile/MyPageProfileScreen.kt @@ -1,6 +1,8 @@ package com.meongmoryteam.presentation.ui.myPage.profile +import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -9,6 +11,7 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.material3.Button @@ -17,12 +20,12 @@ import androidx.compose.material3.Divider import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState 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.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource @@ -31,18 +34,31 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel import com.meongmoryteam.presentation.R -import com.meongmoryteam.presentation.ui.theme.MeongmoryTheme -import com.meongmoryteam.presentation.ui.theme.EditButtonFalse +import com.meongmoryteam.presentation.ui.theme.ButtonContent +import com.meongmoryteam.presentation.ui.theme.EditChangeFill +import com.meongmoryteam.presentation.ui.theme.EditChangeStroke import com.meongmoryteam.presentation.ui.theme.EditDivider import com.meongmoryteam.presentation.ui.theme.EditStroke import com.meongmoryteam.presentation.ui.theme.EditText +import com.meongmoryteam.presentation.ui.theme.LightGrey +import com.meongmoryteam.presentation.ui.theme.MeongmoryTheme +import com.meongmoryteam.presentation.ui.theme.Orange val PADDING_16 = 16.dp val PADDING_24 = 24.dp @Composable -fun MypageProfileScreen() { +fun MyPageProfileScreen( + viewModel: MyPageProfileViewModel = hiltViewModel(), + navigateToPrevious: () -> Unit, +) { + val uiState by viewModel.viewState.collectAsState() + + // ViewModel에서 refreshButton 상태 가져오기 + val refreshButton by viewModel.refreshButton + Column( modifier = Modifier .fillMaxHeight() @@ -57,25 +73,42 @@ fun MypageProfileScreen() { modifier = Modifier.fillMaxHeight(), Arrangement.spacedBy(150.dp) ) { - MyPageToolBar(stringResource(R.string.profile_change_title)) + MyPageToolBar( + stringResource(R.string.profile_change_title), + onBackClick = { + // ViewModel 내부의 refreshButton 상태 변경 + viewModel.setEvent(MyPageProfileContract.MyPageProfileEvent.OnClickPreviousButton) + } + ) ProfileChangeEdit() } Column( Modifier.fillMaxHeight(), Arrangement.Bottom ) { - ProfileChangeButton() + ProfileChangeButton( + isFilled = uiState.isFilled, + isOverflow = uiState.isError + ) } } } + + // refreshButton 상태가 변경되었을 때 이전 페이지로 이동 + LaunchedEffect(refreshButton) { + if (refreshButton) { + navigateToPrevious() + } + } } @Composable fun MyPageToolBar( - title: String + title: String, + onBackClick: () -> Unit, ) { - Column() { + Column { // 위 아래 여백 Row( modifier = Modifier @@ -92,10 +125,15 @@ fun MyPageToolBar( .height(24.dp) .fillMaxWidth() ) { + Icon( imageVector = ImageVector.vectorResource(R.drawable.ic_left_btn), contentDescription = stringResource(R.string.profile_back_btn_description), - modifier = Modifier.padding(start = PADDING_16), + modifier = Modifier + .padding(start = PADDING_16) + .clickable { + onBackClick() + } ) } Text( @@ -108,7 +146,6 @@ fun MyPageToolBar( color = EditDivider.copy(0.2f) ) } - } @@ -144,36 +181,50 @@ fun ProfileChangeExplain() { @Composable fun ProfileChangeEdit( + viewModel: MyPageProfileViewModel = hiltViewModel() ) { + val uiState by viewModel.viewState.collectAsState() Column { ProfileChangeLabel() - MyPageEditForm() + MyPageEditForm( + value = uiState.nickName, + isOverflow = uiState.isError + ) { + viewModel.setEvent(MyPageProfileContract.MyPageProfileEvent.FillNickName(it)) + } ProfileChangeExplain() } } @Composable -fun MyPageEditForm() { - var text by remember { mutableStateOf("") } +fun MyPageEditForm( + value: String, + isOverflow: Boolean, + onValueChange: (String) -> Unit, +) { Box( modifier = Modifier .padding(PADDING_16) .fillMaxWidth() .height(48.dp) + .background( + if (value.isEmpty()) Color.White + else EditChangeFill + ) .border( - color = EditStroke, + color = + if (value.isEmpty()) EditStroke + else if (isOverflow) Color.Red + else EditChangeStroke, width = 1.dp, shape = RoundedCornerShape(10.dp) ), contentAlignment = Alignment.CenterStart // 정렬 ) { BasicTextField( - value = text, - onValueChange = { newText -> - // 한 줄만 입력 가능하게 \n키를 누르면 입력 반영 안함 - text = newText.replace(Regex("[\n]"), "") - }, + value = value, + onValueChange = onValueChange, textStyle = TextStyle( fontSize = 14.sp, textAlign = TextAlign.Start @@ -181,7 +232,7 @@ fun MyPageEditForm() { modifier = Modifier.padding(start = PADDING_16, end = PADDING_16) ) - if (text.isEmpty()) { + if (value.isEmpty()) { Text( text = stringResource(R.string.profile_now_nickname), color = EditText, @@ -192,27 +243,42 @@ fun MyPageEditForm() { } @Composable -fun ProfileChangeButton() { +fun ProfileChangeButton( + isFilled: Boolean, + isOverflow: Boolean, + viewModel: MyPageProfileViewModel = hiltViewModel() +) { Button( - onClick = { /*TODO*/ }, - colors = ButtonDefaults.buttonColors(EditButtonFalse), + onClick = { + viewModel.setEvent(MyPageProfileContract.MyPageProfileEvent.OnClickPreviousButton) + }, + colors = if (!isFilled || isOverflow) { + ButtonDefaults.textButtonColors(LightGrey) + } else { + ButtonDefaults.textButtonColors(Orange) + }, modifier = Modifier .padding(16.dp) .fillMaxWidth() - .height(45.dp), - shape = RoundedCornerShape(10.dp) + .wrapContentHeight(), + shape = RoundedCornerShape(10.dp), ) { Text( text = stringResource(R.string.profile_change_button), - fontSize = 15.sp + fontSize = 15.sp, + color = ButtonContent ) } } + @Preview(showBackground = true) @Composable fun PreviewProfileScreen() { MeongmoryTheme { - MypageProfileScreen() + MyPageProfileScreen( + viewModel = MyPageProfileViewModel(), + navigateToPrevious = { } + ) } } \ No newline at end of file diff --git a/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/profile/MyPageProfileViewModel.kt b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/profile/MyPageProfileViewModel.kt new file mode 100644 index 0000000..4dfdde0 --- /dev/null +++ b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/profile/MyPageProfileViewModel.kt @@ -0,0 +1,72 @@ +package com.meongmoryteam.presentation.ui.myPage.profile + +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.viewModelScope +import com.meongmoryteam.presentation.base.BaseViewModel +import com.meongmoryteam.presentation.base.LoadState +import com.meongmoryteam.presentation.ui.myPage.profile.MyPageProfileContract.MyPageProfileEvent +import com.meongmoryteam.presentation.ui.myPage.profile.MyPageProfileContract.MyPageProfileSideEffect +import com.meongmoryteam.presentation.ui.myPage.profile.MyPageProfileContract.MyPageProfileViewState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class MyPageProfileViewModel @Inject constructor( +) : BaseViewModel( + MyPageProfileViewState() +) { + // refreshButton 상태를 ViewModel 내부에서 관리 + private val _refreshButton = mutableStateOf(false) + val refreshButton: State = _refreshButton + + override fun handleEvents(event: MyPageProfileEvent) { + when (event) { + is MyPageProfileEvent.ClearNickName -> reflectUpdatedState("") + is MyPageProfileEvent.FillNickName -> reflectUpdatedState(event.nickName) + MyPageProfileEvent.OnClickPreviousButton -> _refreshButton.value = true + is MyPageProfileEvent.OnClickChangeButton -> { + changeNickName() + _refreshButton.value = true + } + } + } + + private fun reflectUpdatedState( + nickName: String = viewState.value.nickName + ) { + updateState { + copy( + nickName = nickName, + isError = isOverflowed(nickName), + isFilled = isFilled(nickName) + ) + } + } + + private fun changeNickName( + nickName: String = viewState.value.nickName + ) = viewModelScope.launch { + updateState { + copy( + loadState = LoadState.SUCCESS, + nickName = nickName + ) + } + sendEffect({ MyPageProfileSideEffect.NavigateToMyPageScreen }) + } + + // 텍스트 길이 초과 + private fun isOverflowed(nickName: String): Boolean { + return nickName.isBlank() || (nickName.length > MAX_LENGTH_NICKNAME) + } + + private fun isFilled( + nickName: String + ): Boolean { + return (nickName.isNotEmpty()) + } +} + +const val MAX_LENGTH_NICKNAME = 6 diff --git a/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/question/MyPageQuestionActivity.kt b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/question/MyPageQuestionActivity.kt deleted file mode 100644 index 0d81010..0000000 --- a/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/question/MyPageQuestionActivity.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.meongmoryteam.presentation.ui.myPage.question - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent - -class MyPageQuestionActivity: ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - MyPageQuestionScreen() - } - } -} \ No newline at end of file diff --git a/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/question/MyPageQuestionConstract.kt b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/question/MyPageQuestionConstract.kt new file mode 100644 index 0000000..7b9d702 --- /dev/null +++ b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/question/MyPageQuestionConstract.kt @@ -0,0 +1,35 @@ +package com.meongmoryteam.presentation.ui.myPage.question + +import com.meongmoryteam.presentation.base.LoadState +import com.meongmoryteam.presentation.base.ViewEvent +import com.meongmoryteam.presentation.base.ViewSideEffect +import com.meongmoryteam.presentation.base.ViewState + +class MyPageQuestionConstract { + data class MyPageQuestionViewState( + val loadState: LoadState = LoadState.SUCCESS, + val email: String = "", + val question: String = "", + val isError: Boolean = true, + val isAllFilled: Boolean = false, + ) : ViewState + + sealed class MyPageQuestionSideEffect : ViewSideEffect { + object NavigateToMyPageScreen : MyPageQuestionSideEffect() + } + + sealed class MyPageQuestionEvent : ViewEvent { + data class FillEmail( + val email: String + ) : MyPageQuestionEvent() + + data class FillQuestion( + val question: String + ) : MyPageQuestionEvent() + + object ClearEmail : MyPageQuestionEvent() + object ClearQuestion : MyPageQuestionEvent() + object OnClickPreviousButton : MyPageQuestionEvent() + object OnClickPostButton : MyPageQuestionEvent() + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/question/MyPageQuestionScreen.kt b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/question/MyPageQuestionScreen.kt index ea596da..d064113 100644 --- a/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/question/MyPageQuestionScreen.kt +++ b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/question/MyPageQuestionScreen.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.material3.Button @@ -19,10 +20,9 @@ import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState 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.graphics.vector.ImageVector @@ -33,54 +33,84 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel import com.meongmoryteam.presentation.R import com.meongmoryteam.presentation.ui.myPage.profile.MyPageToolBar -import com.meongmoryteam.presentation.ui.theme.EditButtonFalse -import com.meongmoryteam.presentation.ui.theme.MeongmoryTheme import com.meongmoryteam.presentation.ui.theme.EditStroke import com.meongmoryteam.presentation.ui.theme.EditText +import com.meongmoryteam.presentation.ui.theme.LightGrey +import com.meongmoryteam.presentation.ui.theme.MeongmoryTheme import com.meongmoryteam.presentation.ui.theme.QuestionButtonText +import com.meongmoryteam.presentation.ui.theme.QuestionChangeButtonFill +import com.meongmoryteam.presentation.ui.theme.QuestionChangeFill +import com.meongmoryteam.presentation.ui.theme.QuestionChangeStroke import com.meongmoryteam.presentation.ui.theme.QuestionEditFill import com.meongmoryteam.presentation.ui.theme.QuestionSubTitle val PADDING_8 = 8.dp val PADDING_16 = 16.dp -val PADDING_24 = 24.dp @Composable -fun MyPageQuestionScreen() { +fun MyPageQuestionScreen( + viewModel: MyPageQuestionViewModel = hiltViewModel(), + navigateToPrevious: () -> Unit, +) { + val uiState by viewModel.viewState.collectAsState() + val refreshButton by viewModel.refreshButton + Column( modifier = Modifier .fillMaxHeight() .fillMaxWidth(), verticalArrangement = Arrangement.Top, ) { - MyPageToolBar(stringResource(R.string.question_title)) + MyPageToolBar( + stringResource(R.string.question_title), + onBackClick = { + viewModel.setEvent(MyPageQuestionConstract.MyPageQuestionEvent.OnClickPreviousButton) + } + ) Spacer(modifier = Modifier.padding(PADDING_8)) EmailEdit() - Column(modifier = Modifier.fillMaxHeight(0.5f)) { + Column( + modifier = Modifier.fillMaxHeight(0.5f) + ) { DetailEdit() } Box( modifier = Modifier.fillMaxHeight(), contentAlignment = Alignment.BottomCenter ) { - QuestionButton() + QuestionButton( + isAllFilled = uiState.isAllFilled + ) + } + } + // refreshButton 상태가 변경되었을 때 이전 페이지로 이동 + LaunchedEffect(refreshButton) { + if (refreshButton) { + navigateToPrevious() } - } } @Composable -fun EmailEdit() { +fun EmailEdit( + viewModel: MyPageQuestionViewModel = hiltViewModel() +) { + val uiState by viewModel.viewState.collectAsState() Column { QuestionLabel(stringResource(R.string.question_email_form_title)) Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, ) { - EmailForm() + EmailForm( + value = uiState.email + ) { + viewModel.setEvent(MyPageQuestionConstract.MyPageQuestionEvent.FillEmail(it)) + } Text(text = stringResource(R.string.question_at)) EmailSelect() } @@ -107,28 +137,32 @@ fun QuestionLabel( @Composable -fun EmailForm() { - var email by remember { mutableStateOf("") } +fun EmailForm( + value: String, + onValueChange: (String) -> Unit +) { Box( modifier = Modifier .padding(all = PADDING_16) .height(48.dp) .fillMaxWidth(0.5f) .border( - color = EditStroke, + color = if (value.isEmpty()) EditStroke + else QuestionChangeStroke, width = 1.dp, shape = RoundedCornerShape(10.dp), ) - .background(color = QuestionEditFill, shape = RoundedCornerShape(10.dp)), + .background( + color = if (value.isEmpty()) QuestionEditFill + else QuestionChangeFill, + shape = RoundedCornerShape(10.dp) + ), contentAlignment = Alignment.CenterStart // 정렬 ) { BasicTextField( - value = email, - onValueChange = { newText -> - // 한 줄만 입력 가능하게 \n키를 누르면 입력 반영 안함 - email = newText.replace(Regex("[\n]"), "") - }, + value = value, + onValueChange = onValueChange, textStyle = TextStyle( fontSize = 14.sp, textAlign = TextAlign.Start @@ -136,7 +170,7 @@ fun EmailForm() { modifier = Modifier.padding(start = PADDING_16, end = PADDING_16) ) - if (email.isEmpty()) { + if (value.isEmpty()) { Text( text = stringResource(R.string.question_email_form_hint), color = EditText, @@ -179,40 +213,52 @@ fun EmailSelect() { @Composable -fun DetailEdit() { +fun DetailEdit( + viewModel: MyPageQuestionViewModel = hiltViewModel() +) { + val uiState by viewModel.viewState.collectAsState() QuestionLabel(stringResource(R.string.question_content_title)) - DetailForm() + DetailForm( + value = uiState.question + ) { + viewModel.setEvent(MyPageQuestionConstract.MyPageQuestionEvent.FillQuestion(it)) + } } @Composable -fun DetailForm() { - var detail by remember { mutableStateOf("") } +fun DetailForm( + value: String, + onValueChange: (String) -> Unit +) { Box( modifier = Modifier .padding(PADDING_16) .fillMaxSize() .border( - color = EditStroke, + color = if (value.isEmpty()) EditStroke + else QuestionChangeStroke, width = 1.dp, shape = RoundedCornerShape(10.dp), ) - .background(color = QuestionEditFill, shape = RoundedCornerShape(10.dp)), + .background( + color = if (value.isEmpty()) QuestionEditFill + else QuestionChangeFill, + shape = RoundedCornerShape(10.dp) + ), contentAlignment = Alignment.TopStart // 정렬 ) { BasicTextField( - value = detail, - onValueChange = { newText -> - // 한 줄만 입력 가능하게 \n키를 누르면 입력 반영 안함 - detail = newText - }, + value = value, + onValueChange = onValueChange, textStyle = TextStyle( fontSize = 14.sp, textAlign = TextAlign.Start ), - modifier = Modifier.padding(PADDING_16) + modifier = Modifier + .padding(PADDING_16) ) - if (detail.isEmpty()) { + if (value.isEmpty()) { Text( text = stringResource(R.string.question_content_detail), color = EditText, @@ -225,20 +271,29 @@ fun DetailForm() { } @Composable -fun QuestionButton() { +fun QuestionButton( + isAllFilled: Boolean, + viewModel: MyPageQuestionViewModel = hiltViewModel() +) { Button( - onClick = { /*TODO*/ }, - colors = ButtonDefaults.buttonColors(EditButtonFalse), + onClick = { + viewModel.setEvent(MyPageQuestionConstract.MyPageQuestionEvent.OnClickPreviousButton) + }, + colors = if (!isAllFilled) { + ButtonDefaults.textButtonColors(LightGrey) + } else { + ButtonDefaults.textButtonColors(QuestionChangeButtonFill) + }, modifier = Modifier .padding(16.dp) .fillMaxWidth() - .height(45.dp), + .wrapContentHeight(), shape = RoundedCornerShape(10.dp) ) { Text( text = stringResource(R.string.question_button), color = QuestionButtonText, - fontSize = 15.sp + fontSize = 15.sp, ) } } @@ -247,6 +302,8 @@ fun QuestionButton() { @Composable fun PreviewQuestionScreen() { MeongmoryTheme { - MyPageQuestionScreen() + MyPageQuestionScreen( + navigateToPrevious = { } + ) } } diff --git a/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/question/MyPageQuestionViewModel.kt b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/question/MyPageQuestionViewModel.kt new file mode 100644 index 0000000..fe81a25 --- /dev/null +++ b/presentation/src/main/java/com/meongmoryteam/presentation/ui/myPage/question/MyPageQuestionViewModel.kt @@ -0,0 +1,73 @@ +package com.meongmoryteam.presentation.ui.myPage.question + +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.viewModelScope +import com.meongmoryteam.presentation.base.BaseViewModel +import com.meongmoryteam.presentation.base.LoadState +import com.meongmoryteam.presentation.ui.myPage.question.MyPageQuestionConstract.MyPageQuestionEvent +import com.meongmoryteam.presentation.ui.myPage.question.MyPageQuestionConstract.MyPageQuestionSideEffect +import com.meongmoryteam.presentation.ui.myPage.question.MyPageQuestionConstract.MyPageQuestionViewState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class MyPageQuestionViewModel @Inject constructor( +) : BaseViewModel( + MyPageQuestionViewState() +) { + private val _refreshButton = mutableStateOf(false) + val refreshButton: State = _refreshButton + + override fun handleEvents(event: MyPageQuestionEvent) { + when (event) { + MyPageQuestionEvent.ClearEmail -> reflectUpdatedState(email = "") + MyPageQuestionEvent.ClearQuestion -> reflectUpdatedState(question = "") + is MyPageQuestionEvent.FillEmail -> reflectUpdatedState(email = event.email) + is MyPageQuestionEvent.FillQuestion -> reflectUpdatedState(question = event.question) + MyPageQuestionEvent.OnClickPreviousButton -> _refreshButton.value = true + is MyPageQuestionEvent.OnClickPostButton -> { + setEmail() + _refreshButton.value = true + } + } + } + + private fun reflectUpdatedState( + email: String = viewState.value.email, + question: String = viewState.value.question + ) { + updateState { + copy( + email = email, + question = question, + isAllFilled = isFilled( + email, + question + ) + ) + } + } + + private fun setEmail( + email: String = viewState.value.email, + question: String = viewState.value.question + ) = viewModelScope.launch { + updateState { + copy( + loadState = LoadState.SUCCESS, + email = email, + question = question + ) + } + sendEffect({ MyPageQuestionSideEffect.NavigateToMyPageScreen }) + } + + private fun isFilled( + email: String, + question: String, + ): Boolean { + return (email.isNotEmpty() && question.isNotEmpty()) + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/meongmoryteam/presentation/ui/theme/Color.kt b/presentation/src/main/java/com/meongmoryteam/presentation/ui/theme/Color.kt index fd293c0..7d3ce8c 100644 --- a/presentation/src/main/java/com/meongmoryteam/presentation/ui/theme/Color.kt +++ b/presentation/src/main/java/com/meongmoryteam/presentation/ui/theme/Color.kt @@ -42,6 +42,10 @@ val EditText = Color(0xFF737373) val EditStroke = Color(0xFFE7E7E7) val EditButtonFalse = Color(0xFFD9D9D9) val EditDivider = Color(0xFF757575) +val EditChangeStroke = Color(0xFFBEB7AD) +val EditChangeFill = Color(0xFFF5F4F2) +val EditChangeButtonFill = Color(0xFF3C270A) + // 문의 및 오류 제보 색상 val QuestionSubTitle = Color(0xFF454545)