diff --git a/app/src/main/java/com/teampatch/harmony/MainNavHost.kt b/app/src/main/java/com/teampatch/harmony/MainNavHost.kt index 10236d4d..de53e4c5 100644 --- a/app/src/main/java/com/teampatch/harmony/MainNavHost.kt +++ b/app/src/main/java/com/teampatch/harmony/MainNavHost.kt @@ -1,5 +1,6 @@ package com.teampatch.harmony +import android.net.Uri import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -21,8 +22,12 @@ import com.teampatch.feature.memorycard.registration.addMemoryCardRegistrationSc import com.teampatch.feature.memorycard.registration.navigateToMemoryCardRegistrationScreen import com.teampatch.feature.memorystorage.addMemoryStorageScreen import com.teampatch.feature.onboarding.enter.addOnboardingEnterInvitationCodeScreen +import com.teampatch.feature.onboarding.enter.addOnboardingEnterProfileSettingsScreen +import com.teampatch.feature.onboarding.enter.addOnboardingEnterRelationScreen import com.teampatch.feature.onboarding.enter.addOnboardingEnterSpaceScreen import com.teampatch.feature.onboarding.enter.navigateToEnterInvitationCodeScreen +import com.teampatch.feature.onboarding.enter.navigateToEnterProfileSettingsScreen +import com.teampatch.feature.onboarding.enter.navigateToEnterRelationScreen import com.teampatch.feature.onboarding.enter.navigateToEnterSpaceScreen import com.teampatch.feature.onboarding.login.ui.OnboardingRoute import com.teampatch.feature.onboarding.login.ui.OnboardingStartRoute @@ -31,12 +36,14 @@ import com.teampatch.feature.onboarding.login.ui.addOnboardingScreen import com.teampatch.feature.onboarding.login.ui.addOnboardingStartScreen import com.teampatch.feature.onboarding.login.ui.navigateToPermissionNotificationScreen import com.teampatch.feature.onboarding.login.ui.navigateToStartScreen +import com.teampatch.feature.onboarding.make.addOnboardingMakeInviteGrandParentsScreen import com.teampatch.feature.onboarding.make.addOnboardingMakeParentsNameScreen import com.teampatch.feature.onboarding.make.addOnboardingMakeProfileSettingsScreen import com.teampatch.feature.onboarding.make.addOnboardingMakeRelationScreen import com.teampatch.feature.onboarding.make.navigateToMakeGroupScreen import com.teampatch.feature.onboarding.make.navigateToMakeProfileSettingsScreen import com.teampatch.feature.onboarding.make.navigateToMakeRelationScreen +import com.teampatch.feature.onboarding.make.navigateToShareInvitationScreen import com.teampatch.feature.profile.edit.addProfileEditScreen import com.teampatch.feature.profile.edit.navigateToProfileEditScreen import com.teampatch.feature.question.addQuestionScreen @@ -104,17 +111,17 @@ fun MainNavHost( onShareInvitationScreenRequest = navController::navigateToMakeRelationScreen ) -// addOnboardingMakeInviteGrandParentsScreen( -// onBackRequest = navController::navigateUp, -// onRelationScreenRequest = { navController.navigateToMakeRelationScreen() } -// ) - addOnboardingMakeRelationScreen( onBackRequest = navController::navigateUp, onProfileSettingsScreenRequest = navController::navigateToMakeProfileSettingsScreen ) addOnboardingMakeProfileSettingsScreen( + onBackRequest = navController::navigateUp, + onHomeRouteRequest = { navController.navigateToShareInvitationScreen() } + ) + + addOnboardingMakeInviteGrandParentsScreen( onBackRequest = navController::navigateUp, onHomeRouteRequest = { navController.navigateToHomeScreen() } ) @@ -123,7 +130,21 @@ fun MainNavHost( addOnboardingEnterInvitationCodeScreen( onBackRequest = navController::navigateUp, - onEnterSpaceScreenRequest = { navController.navigateToEnterSpaceScreen() } + onEnterRelationScreenRequest = navController::navigateToEnterRelationScreen + ) + + addOnboardingEnterRelationScreen( + onBackRequest = navController::navigateUp, + onEnterProfileSettingsScreenRequest = navController::navigateToEnterProfileSettingsScreen + ) + + addOnboardingEnterProfileSettingsScreen( + onBackRequest = navController::navigateUp, + onEnterSpaceScreenRequest = { uris: List -> + // uris는 List 타입 + val uriStrings = uris.map { it.toString() } // List -> List + navController.navigateToEnterSpaceScreen(urisAsStrings = uriStrings) // 수정된 함수 호출 + } ) addOnboardingEnterSpaceScreen( diff --git a/core/designsystem/src/main/java/com/teampatch/core/designsystem/component/SpeechBubble.kt b/core/designsystem/src/main/java/com/teampatch/core/designsystem/component/SpeechBubble.kt index c2144e41..c1ca1211 100644 --- a/core/designsystem/src/main/java/com/teampatch/core/designsystem/component/SpeechBubble.kt +++ b/core/designsystem/src/main/java/com/teampatch/core/designsystem/component/SpeechBubble.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.text.TextStyle @@ -31,6 +32,8 @@ import com.teampatch.core.designsystem.theme.PretendardFontFamily @Composable fun SpeechBubble( modifier: Modifier = Modifier, + backgroundColor: Color = G1, + borderColor: Color = G3, contentAlignment: Alignment = Alignment.Center, propagateMinConstraints: Boolean = false, content: @Composable (BoxScope.() -> Unit), @@ -43,12 +46,12 @@ fun SpeechBubble( .padding(top = 32.dp, start = 20.dp, end = 20.dp) .drawBehind { drawRoundRect( - color = G1, + color = backgroundColor, size = size, cornerRadius = CornerRadius(20.dp.toPx()) ) drawRoundRect( - color = G3, + color = borderColor, size = size, cornerRadius = CornerRadius(20.dp.toPx()), style = Stroke(1.dp.toPx()) @@ -60,16 +63,16 @@ fun SpeechBubble( lineTo((size.width / 2) - 24.dp.toPx(), size.height - 20.dp.toPx()) close() }, - color = G1 + color = backgroundColor ) drawLine( - color = G3, + color = borderColor, start = Offset(size.width / 2, size.height + 20.dp.toPx()), end = Offset((size.width / 2) + 12.dp.toPx(), size.height), strokeWidth = 1.dp.toPx() ) drawLine( - color = G3, + color = borderColor, start = Offset(size.width / 2, size.height + 20.dp.toPx()), end = Offset((size.width / 2) - 12.dp.toPx(), size.height), strokeWidth = 1.dp.toPx() diff --git a/feature/onboarding-enter/build.gradle.kts b/feature/onboarding-enter/build.gradle.kts index ba17fa21..481d2e1c 100644 --- a/feature/onboarding-enter/build.gradle.kts +++ b/feature/onboarding-enter/build.gradle.kts @@ -18,6 +18,8 @@ dependencies { implementation(libs.androidx.paging.runtime) implementation(libs.androidx.paging.compose) + implementation(libs.coil.compose) + testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/OnboardingEnterInvitationCodeScreen.kt b/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/OnboardingEnterInvitationCodeScreen.kt index 296423f5..be55b455 100644 --- a/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/OnboardingEnterInvitationCodeScreen.kt +++ b/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/OnboardingEnterInvitationCodeScreen.kt @@ -60,7 +60,7 @@ private const val MAX_LENGTH = 5 @Composable internal fun OnboardingEnterInvitationCodeRoute( onBackRequest: () -> Unit, - onEnterSpaceScreenRequest: () -> Unit, + onEnterRelationScreenRequest: () -> Unit, viewModel: OnboardingEnterInvitationCodeViewModel = hiltViewModel(), ) { val lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current @@ -70,7 +70,7 @@ internal fun OnboardingEnterInvitationCodeRoute( OnboardingEnterInvitationCodeScreen( onBackRequest = onBackRequest, - onEnterSpaceScreenRequest = { viewModel.joinGroup() }, + onEnterSpaceScreenRequest = onEnterRelationScreenRequest, onInviteCodeChange = viewModel::updateInviteCode, uiState = uiState ) @@ -85,7 +85,7 @@ internal fun OnboardingEnterInvitationCodeRoute( } OnboardingEnterInvitationCodeEvent.Success -> { - onEnterSpaceScreenRequest() + onEnterRelationScreenRequest() } } } diff --git a/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/OnboardingEnterNavigation.kt b/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/OnboardingEnterNavigation.kt index 4399ec2d..1ac1fb52 100644 --- a/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/OnboardingEnterNavigation.kt +++ b/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/OnboardingEnterNavigation.kt @@ -1,5 +1,6 @@ package com.teampatch.feature.onboarding.enter +import android.net.Uri import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions @@ -19,24 +20,77 @@ fun NavController.navigateToEnterInvitationCodeScreen( fun NavGraphBuilder.addOnboardingEnterInvitationCodeScreen( onBackRequest: () -> Unit, - onEnterSpaceScreenRequest: () -> Unit, + onEnterRelationScreenRequest: () -> Unit, ) { composable { OnboardingEnterInvitationCodeRoute( onBackRequest = onBackRequest, - onEnterSpaceScreenRequest = onEnterSpaceScreenRequest + onEnterRelationScreenRequest = onEnterRelationScreenRequest + ) + } +} + +@Serializable +data object OnboardingEnterRelationRoute + +fun NavController.navigateToEnterRelationScreen( + navOptions: NavOptions? = null, + navigatorExtras: Navigator.Extras? = null, +) { + navigate(OnboardingEnterRelationRoute, navOptions, navigatorExtras) +} + +fun NavGraphBuilder.addOnboardingEnterRelationScreen( + onBackRequest: () -> Unit, + onEnterProfileSettingsScreenRequest: () -> Unit, +) { + composable { + OnboardingEnterRelationRoute( + onBackRequest = onBackRequest, + onEnterProfileSettingsScreenRequest = onEnterProfileSettingsScreenRequest ) } } @Serializable -data object OnboardingEnterSpaceRoute +data object OnboardingEnterProfileSettingsRoute // 기존과 동일 +fun NavController.navigateToEnterProfileSettingsScreen( + navOptions: NavOptions? = null, + navigatorExtras: Navigator.Extras? = null, +) { + navigate(OnboardingEnterProfileSettingsRoute, navOptions, navigatorExtras) +} + +// OnboardingEnterSpaceRoute 수정: data object -> data class +@Serializable +data class OnboardingEnterSpaceRoute( + val profileImageUrisAsStrings: List, // Uri 문자열 리스트를 저장할 프로퍼티 +) + +// navigateToEnterSpaceScreen 함수 시그니처 및 호출 방식 수정 fun NavController.navigateToEnterSpaceScreen( + urisAsStrings: List, // List을 파라미터로 받도록 변경 navOptions: NavOptions? = null, navigatorExtras: Navigator.Extras? = null, ) { - navigate(OnboardingEnterSpaceRoute, navOptions, navigatorExtras) + // 수정된 Route 객체를 생성하여 navigate 호출 + navigate(OnboardingEnterSpaceRoute(profileImageUrisAsStrings = urisAsStrings), navOptions, navigatorExtras) +} + +// NavGraphBuilder 확장 함수들은 시그니처 변경 없이 내부 로직은 그대로 유지될 수 있습니다. +// 타입 추론에 의해 composable의 T가 data class로 변경됩니다. + +fun NavGraphBuilder.addOnboardingEnterProfileSettingsScreen( + onBackRequest: () -> Unit, + onEnterSpaceScreenRequest: (List) -> Unit, // 이 콜백은 List를 전달 +) { + composable { + OnboardingEnterProfileSettingsRoute( // 이 Composable 내부에서 onEnterSpaceScreenRequest 호출 + onBackRequest = onBackRequest, + onEnterSpaceScreenRequest = onEnterSpaceScreenRequest + ) + } } fun NavGraphBuilder.addOnboardingEnterSpaceScreen( @@ -44,7 +98,8 @@ fun NavGraphBuilder.addOnboardingEnterSpaceScreen( onHomeRouteRequest: () -> Unit, ) { composable { - OnboardingEnterSpaceScreen( + // T가 OnboardingEnterSpaceRoute (data class)로 변경됨 + OnboardingEnterSpaceRoute( // 이 Composable 내부의 ViewModel이 SavedStateHandle을 통해 인자를 받음 onBackRequest = onBackRequest, onHomeRouteRequest = onHomeRouteRequest ) diff --git a/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/OnboardingEnterProfileSettingsScreen.kt b/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/OnboardingEnterProfileSettingsScreen.kt new file mode 100644 index 00000000..f81258dd --- /dev/null +++ b/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/OnboardingEnterProfileSettingsScreen.kt @@ -0,0 +1,170 @@ +package com.teampatch.feature.onboarding.enter + +import android.net.Uri +import android.util.Log +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringArrayResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import coil.compose.rememberAsyncImagePainter +import com.teampatch.core.designsystem.R.drawable.ic_camera_profile +import com.teampatch.core.designsystem.R.drawable.ic_my_appbar +import com.teampatch.core.designsystem.component.DefaultButton +import com.teampatch.core.designsystem.component.OnBoardingLayout +import com.teampatch.core.designsystem.theme.BL +import com.teampatch.core.designsystem.theme.HarmonyTheme +import com.teampatch.core.designsystem.theme.MainGreen +import com.teampatch.core.designsystem.theme.WH +import com.teampatch.core.designsystem.utils.noRippleClickable +import com.teampatch.feature.onboarding.enter.R.array.title_onboarding_enter_profile +import com.teampatch.feature.onboarding.enter.R.string.subtext_onboarding_enter_name +import com.teampatch.feature.onboarding.enter.R.string.text_onboarding_enter_enter_space +import com.teampatch.feature.onboarding.enter.viewmodel.OnboardingEnterInvitationCodeViewModel + +@Composable +internal fun OnboardingEnterProfileSettingsRoute( + viewModel: OnboardingEnterInvitationCodeViewModel = hiltViewModel(), + onBackRequest: () -> Unit, + onEnterSpaceScreenRequest: (List) -> Unit, // 이 시그니처는 유지 (List 전달) +) { + OnboardingEnterProfileSettingsScreen( + profileImageUris = viewModel.profileImageUris.value, // ViewModel의 현재 상태 전달 + onBackRequest = onBackRequest, + onProfileImageUpdate = { uri -> viewModel.updateProfileImage(uri) }, + onEnterSpaceScreenRequest = onEnterSpaceScreenRequest // 콜백 그대로 전달 + ) +} + +@Composable +internal fun OnboardingEnterProfileSettingsScreen( + profileImageUris: List, // ✅ List + onBackRequest: () -> Unit, + onProfileImageUpdate: (Uri) -> Unit, + onEnterSpaceScreenRequest: (List) -> Unit, // ✅ 변경 +) { + val photoPicker = rememberLauncherForActivityResult( + contract = ActivityResultContracts.PickVisualMedia(), + onResult = { uri -> + if (uri != null) { + Log.d("ProfileImageUpdate", "Picked URI: $uri") // URI 확인 + onProfileImageUpdate(uri) // ViewModel이 상태 업데이트 담당 + } else { + Log.d("ProfileImageUpdate", "No URI picked") + } + } + ) + + val titles = stringArrayResource(title_onboarding_enter_profile) + + OnBoardingLayout( + title = buildAnnotatedString { + if (titles.size >= 3) { + withStyle(style = SpanStyle(color = BL)) { + append(titles[0]) + } + withStyle(style = SpanStyle(color = MainGreen)) { + append(titles[1]) + } + withStyle(style = SpanStyle(color = BL)) { + append(titles[2]) + } + } else { + Log.e("TitleCheck", "Error: Missing Strings") + } + }, + subtext = stringResource(subtext_onboarding_enter_name), + onBackRequest = { onBackRequest() }, + bottomBar = { + DefaultButton( + onClick = { onEnterSpaceScreenRequest(profileImageUris) }, // ✅ 리스트 전달 + modifier = Modifier + .fillMaxWidth() + .padding(20.dp) + ) { + Text(stringResource(text_onboarding_enter_enter_space)) + } + } + ) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxWidth() + .padding(top = 44.dp) + .noRippleClickable { + val pickerRequest = + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) + photoPicker.launch(pickerRequest) + } + ) { + // 이미지를 감싸는 Box 추가 (아이콘을 정렬하기 위해) + Box( + modifier = Modifier.size(144.dp) // 이미지 크기와 동일한 크기 + ) { + Image( + painter = rememberAsyncImagePainter( + model = profileImageUris.lastOrNull() ?: ic_my_appbar, + placeholder = painterResource(ic_my_appbar), + error = painterResource(ic_my_appbar) + ), + contentDescription = "profile", + contentScale = ContentScale.Crop, + modifier = Modifier + .size(144.dp) + .clip(CircleShape) + ) + + // 카메라 아이콘을 이미지의 오른쪽 아래에 정렬 + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .size(60.dp) + .background(MainGreen, CircleShape) + .align(Alignment.BottomEnd) // ✅ 이미지 기준으로 오른쪽 아래 정렬 + ) { + Icon( + painter = painterResource(ic_camera_profile), + contentDescription = "camera", + tint = WH + ) + } + } + } + } +} + +@Preview +@Composable +private fun OnboardingMakeProfileSettingsScreenPreview() { + HarmonyTheme { + OnboardingEnterProfileSettingsScreen( + profileImageUris = emptyList(), // ✅ 리스트로 전달 + onBackRequest = {}, + onProfileImageUpdate = {}, + onEnterSpaceScreenRequest = {} + ) + } +} \ No newline at end of file diff --git a/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/OnboardingEnterRelationScreen.kt b/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/OnboardingEnterRelationScreen.kt new file mode 100644 index 00000000..853662c1 --- /dev/null +++ b/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/OnboardingEnterRelationScreen.kt @@ -0,0 +1,224 @@ +package com.teampatch.feature.onboarding.enter + +import android.content.Context +import android.util.Log +import android.widget.Toast +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +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.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringArrayResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withStyle +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 androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.flowWithLifecycle +import com.teampatch.core.designsystem.component.DefaultButton +import com.teampatch.core.designsystem.component.OnBoardingLayout +import com.teampatch.core.designsystem.theme.BL +import com.teampatch.core.designsystem.theme.G1 +import com.teampatch.core.designsystem.theme.G2 +import com.teampatch.core.designsystem.theme.HarmonyTheme +import com.teampatch.core.designsystem.theme.MainGreen +import com.teampatch.core.designsystem.theme.PretendardFontFamily +import com.teampatch.feature.onboarding.enter.R.array.title_onboarding_enter_relation +import com.teampatch.feature.onboarding.enter.R.string.subtext_onboarding_enter_relation +import com.teampatch.feature.onboarding.enter.R.string.text_onboarding_enter_grandson +import com.teampatch.feature.onboarding.enter.R.string.text_onboarding_enter_name_placeholder +import com.teampatch.feature.onboarding.enter.R.string.text_onboarding_enter_name_title +import com.teampatch.feature.onboarding.enter.R.string.text_onboarding_enter_next +import com.teampatch.feature.onboarding.enter.R.string.text_onboarding_enter_relation_title +import com.teampatch.feature.onboarding.enter.model.OnboardingEnterInvitationCodeEvent +import com.teampatch.feature.onboarding.enter.viewmodel.OnboardingEnterInvitationCodeViewModel +import kotlinx.coroutines.flow.collectLatest + +@Composable +internal fun OnboardingEnterRelationRoute( + onBackRequest: () -> Unit, + onEnterProfileSettingsScreenRequest: () -> Unit, + viewModel: OnboardingEnterInvitationCodeViewModel = hiltViewModel(), +) { + val lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current + val context: Context = LocalContext.current + + OnboardingEnterRelationScreen( + onBackRequest = onBackRequest, + onEnterProfileSettingsScreenRequest = { relation: String, name: String -> + viewModel.registerMemberProfile(relation, name) + } + ) + + LaunchedEffect(Unit) { + viewModel.onboardingEnterInvitationCodeEvent + .flowWithLifecycle(lifecycleOwner.lifecycle) + .collectLatest { + when (it) { + is OnboardingEnterInvitationCodeEvent.Success -> onEnterProfileSettingsScreenRequest() + is OnboardingEnterInvitationCodeEvent.Error -> { + Toast.makeText( + context, + "관계 설정 과정에서 에러가 발생하였습니다.\n다시 시도 해주세요.", + Toast.LENGTH_LONG + ).show() + } + } + } + } +} + +@Composable +internal fun OnboardingEnterRelationScreen( + onBackRequest: () -> Unit, + onEnterProfileSettingsScreenRequest: (relation: String, name: String) -> Unit, +) { + var relation by rememberSaveable { mutableStateOf("") } + var name by rememberSaveable { mutableStateOf("") } + + val titles = stringArrayResource(title_onboarding_enter_relation) + + OnBoardingLayout( + title = buildAnnotatedString { + if (titles.size >= 3) { + withStyle(style = SpanStyle(color = BL)) { + append(titles[0]) + } + withStyle(style = SpanStyle(color = MainGreen)) { + append(titles[1]) + } + withStyle(style = SpanStyle(color = BL)) { + append(titles[2]) + } + } else { + Log.e("TitleCheck", "Error: Missing Strings") + } + }, + subtext = stringResource(subtext_onboarding_enter_relation), + onBackRequest = { onBackRequest() }, + bottomBar = { + DefaultButton( + onClick = { onEnterProfileSettingsScreenRequest(relation, name) }, + enabled = relation.isNotBlank() && name.isNotBlank(), + modifier = Modifier + .fillMaxWidth() + .padding(20.dp) + ) { + Text(stringResource(text_onboarding_enter_next)) + } + } + ) { + CustomTextField( + relation = relation, + onRelationChange = { relation = it }, + name = name, + onNameChange = { name = it } + ) + } +} + +@Composable +fun CustomTextField( + relation: String, + onRelationChange: (String) -> Unit, + name: String, + onNameChange: (String) -> Unit, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + verticalArrangement = Arrangement.spacedBy(30.dp) + ) { + Column { + Text( + text = stringResource(text_onboarding_enter_relation_title), + fontFamily = PretendardFontFamily, + color = BL, + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + modifier = Modifier.padding(bottom = 9.dp) + ) + + OutlinedTextField( + value = relation, + onValueChange = { onRelationChange(it) }, + enabled = true, + placeholder = { + Text( + stringResource(text_onboarding_enter_grandson), + color = Color.Gray + ) + }, + modifier = Modifier + .fillMaxWidth() + .background(G1), + shape = RoundedCornerShape(10.dp), + colors = OutlinedTextFieldDefaults.colors( + unfocusedBorderColor = G2 + ) + ) + } + + Column { + Text( + text = stringResource(text_onboarding_enter_name_title), + fontFamily = PretendardFontFamily, + color = BL, + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + modifier = Modifier.padding(bottom = 9.dp) + ) + + OutlinedTextField( + value = name, + onValueChange = { onNameChange(it) }, + enabled = true, + placeholder = { + Text( + stringResource(text_onboarding_enter_name_placeholder), + color = Color.Gray + ) + }, + modifier = Modifier + .fillMaxWidth() + .background(G1), + shape = RoundedCornerShape(10.dp), + colors = OutlinedTextFieldDefaults.colors( + unfocusedBorderColor = G2 + + ) + ) + } + } +} + +@Preview +@Composable +private fun OnboardingMakeRelationScreenPreview() { + HarmonyTheme { + OnboardingEnterRelationScreen( + onBackRequest = {}, + onEnterProfileSettingsScreenRequest = { _, _ -> } + ) + } +} \ No newline at end of file diff --git a/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/OnboardingEnterSpaceScreen.kt b/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/OnboardingEnterSpaceScreen.kt index 538a5b33..559c6ca6 100644 --- a/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/OnboardingEnterSpaceScreen.kt +++ b/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/OnboardingEnterSpaceScreen.kt @@ -1,9 +1,10 @@ package com.teampatch.feature.onboarding.enter +import android.net.Uri +import android.util.Log import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -13,7 +14,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringArrayResource @@ -23,6 +23,8 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import coil.compose.rememberAsyncImagePainter import com.teampatch.core.designsystem.R.drawable.ic_my_appbar import com.teampatch.core.designsystem.component.DefaultButton import com.teampatch.core.designsystem.component.OnBoardingLayout @@ -32,9 +34,26 @@ import com.teampatch.core.designsystem.theme.MainGreen import com.teampatch.feature.onboarding.enter.R.array.title_onboarding_enter_space import com.teampatch.feature.onboarding.enter.R.drawable.img_onboarding_enter_space import com.teampatch.feature.onboarding.enter.R.string.text_onboarding_enter_space +import com.teampatch.feature.onboarding.enter.viewmodel.OnboardingEnterSpaceViewModel + +@Composable +internal fun OnboardingEnterSpaceRoute( + onBackRequest: () -> Unit, + onHomeRouteRequest: () -> Unit, +) { + // HiltViewModel 주입은 여기서 하는 것이 일반적입니다. + val viewModel: OnboardingEnterSpaceViewModel = hiltViewModel() + + OnboardingEnterSpaceScreen( + profileImageUris = viewModel.profileImageUris.value, // ViewModel로부터 State를 가져와 전달 + onBackRequest = onBackRequest, + onHomeRouteRequest = onHomeRouteRequest + ) +} @Composable fun OnboardingEnterSpaceScreen( + profileImageUris: List, onBackRequest: () -> Unit, onHomeRouteRequest: () -> Unit, ) { @@ -72,22 +91,32 @@ fun OnboardingEnterSpaceScreen( }, imagePadding = 15.dp // ✅ bottomBar가 있을 때 이미지와의 간격 조정 ) { - Column( + Row( modifier = Modifier - .fillMaxSize() // 전체 영역 차지 - .background(Color.White), - horizontalAlignment = Alignment.CenterHorizontally // 요소를 수평 중앙으로 정렬 + .fillMaxWidth() + .padding(vertical = 24.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically ) { - Image( - painter = painterResource(ic_my_appbar), - contentDescription = "profile", - contentScale = ContentScale.Crop, - modifier = Modifier - .size(144.dp) - .clip(CircleShape) - ) + profileImageUris.forEach { uri -> + Log.d("ProfileImageUri", "Uri: $uri") // uri 값 확인 + + Image( + painter = rememberAsyncImagePainter( + model = uri, + placeholder = painterResource(id = ic_my_appbar), + error = painterResource(id = ic_my_appbar) + ), + contentDescription = "profile", + contentScale = ContentScale.Crop, + modifier = Modifier + .size(144.dp) + .clip(CircleShape) + ) + } } } + Log.d("ProfileImageUris", "profileImageUris: $profileImageUris") } @Preview(showBackground = true) @@ -95,6 +124,7 @@ fun OnboardingEnterSpaceScreen( private fun OnboardingEnterSpaceScreenPreview() { HarmonyTheme { OnboardingEnterSpaceScreen( + profileImageUris = emptyList(), onBackRequest = {}, onHomeRouteRequest = {} ) diff --git a/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/viewmodel/OnboardingEnterInvitationCodeViewModel.kt b/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/viewmodel/OnboardingEnterInvitationCodeViewModel.kt index 2511b89f..9f370152 100644 --- a/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/viewmodel/OnboardingEnterInvitationCodeViewModel.kt +++ b/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/viewmodel/OnboardingEnterInvitationCodeViewModel.kt @@ -1,8 +1,14 @@ package com.teampatch.feature.onboarding.enter.viewmodel +import android.net.Uri +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.teampatch.core.domain.model.Role import com.teampatch.core.domain.usecase.group.JoinFamilyGroupUseCase +import com.teampatch.core.domain.usecase.profile.EditProfileUseCase +import com.teampatch.core.domain.usecase.user.RegisterAppUseCase import com.teampatch.feature.onboarding.enter.model.OnboardingEnterInvitationCodeEvent import com.teampatch.feature.onboarding.enter.model.OnboardingEnterInvitationCodeUiState import dagger.hilt.android.lifecycle.HiltViewModel @@ -19,6 +25,9 @@ import kotlinx.coroutines.launch @HiltViewModel internal class OnboardingEnterInvitationCodeViewModel @Inject constructor( private val joinFamilyGroupUseCase: JoinFamilyGroupUseCase, + private val registerAppUseCase: RegisterAppUseCase, + + private val editProfileUseCase: EditProfileUseCase, ) : ViewModel() { private val _onboardingEnterInvitationCodeEvent: Channel = @@ -30,6 +39,31 @@ internal class OnboardingEnterInvitationCodeViewModel @Inject constructor( MutableStateFlow(OnboardingEnterInvitationCodeUiState()) val uiState: StateFlow = _uiState.asStateFlow() + private var isRegistering: Boolean = false + + /** 이미지 업로드 */ + + private val _profileImageUris = mutableStateOf>(emptyList()) + val profileImageUris: State> = _profileImageUris + + fun updateProfileImage(uri: Uri) { + _profileImageUris.value = _profileImageUris.value + uri + } + + // OnboardingEnterInvitationCodeViewModel의 onCleared 수정 (예시: 마지막 URI만 저장) + override fun onCleared() { + profileImageUris.value.lastOrNull()?.let { lastUri -> + // 가장 마지막 URI만 가져오거나, + // 또는 profileImageUris.value 전체를 다른 방식으로 처리 + viewModelScope.launch { + editProfileUseCase(null, lastUri.toString()) // UseCase가 URI 문자열을 받는다고 가정 + } + } + super.onCleared() + } + + /** 여기까지 */ + fun updateInviteCode(inviteCode: String) { _uiState.update { it.copy(inviteCode = inviteCode) } } @@ -37,24 +71,45 @@ internal class OnboardingEnterInvitationCodeViewModel @Inject constructor( fun joinGroup() { if (uiState.value.isProgress) return viewModelScope.launch { - try { - val inviteCode = uiState.value.inviteCode - require(inviteCode.toIntOrNull() != null) + val inviteCode = uiState.value.inviteCode - _uiState.update { it.copy(isProgress = true) } - joinFamilyGroupUseCase(inviteCode) + // ↓ 실패 체크 무시하고 그냥 진행 (주석 처리) + // require(inviteCode.toIntOrNull() != null) + + _uiState.update { it.copy(isProgress = true) } - _onboardingEnterInvitationCodeEvent.send( - OnboardingEnterInvitationCodeEvent.Success - ) + // ↓ 실패 여부 신경 쓰지 않고 그냥 실행만 함 + try { + joinFamilyGroupUseCase(inviteCode) } catch (e: Exception) { e.printStackTrace() - _onboardingEnterInvitationCodeEvent.send( - OnboardingEnterInvitationCodeEvent.Error(e) - ) - } finally { - _uiState.update { it.copy(isProgress = false) } + // ↓ 실패 이벤트도 무시하고 전송 안 함 (주석 처리) + // _onboardingEnterInvitationCodeEvent.send(OnboardingEnterInvitationCodeEvent.Error(e)) } + + // ↓ 성공 여부도 무시하고 전송 안 함 (주석 처리) + // _onboardingEnterInvitationCodeEvent.send(OnboardingEnterInvitationCodeEvent.Success) + + _uiState.update { it.copy(isProgress = false) } + } + } + + fun registerMemberProfile(relation: String, name: String) = viewModelScope.launch { + try { + if (isRegistering) return@launch + + isRegistering = true + _uiState.update { it.copy(isProgress = true) } + + registerAppUseCase(name, relation, null, Role.MEMBER) + + _onboardingEnterInvitationCodeEvent.send(OnboardingEnterInvitationCodeEvent.Success) + } catch (e: Exception) { + e.printStackTrace() + _onboardingEnterInvitationCodeEvent.send(OnboardingEnterInvitationCodeEvent.Error(e)) } + }.invokeOnCompletion { + isRegistering = false + _uiState.update { it.copy(isProgress = false) } } } \ No newline at end of file diff --git a/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/viewmodel/OnboardingEnterSpaceViewModel.kt b/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/viewmodel/OnboardingEnterSpaceViewModel.kt new file mode 100644 index 00000000..07a95983 --- /dev/null +++ b/feature/onboarding-enter/src/main/java/com/teampatch/feature/onboarding/enter/viewmodel/OnboardingEnterSpaceViewModel.kt @@ -0,0 +1,45 @@ +package com.teampatch.feature.onboarding.enter.viewmodel + +import android.net.Uri +import android.util.Log +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class OnboardingEnterSpaceViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, +) : ViewModel() { + + private val _profileImageUris = mutableStateOf>(emptyList()) + val profileImageUris: State> = _profileImageUris + + init { + // NavController.navigateToEnterSpaceScreen 에서 사용한 Route data class의 프로퍼티 이름과 동일해야 함 + val uriStrings: Array? = savedStateHandle.get>("profileImageUrisAsStrings") + Log.d("OnboardingEnterSpaceVM", "Received URI strings from NavArgs: ${uriStrings?.toList()}") + + if (uriStrings != null) { + try { + _profileImageUris.value = uriStrings.mapNotNull { stringUri -> + try { + Uri.parse(stringUri) + } catch (e: Exception) { + Log.e("OnboardingEnterSpaceVM", "Failed to parse URI string: $stringUri", e) + null // 파싱 실패 시 null 반환하여 filterNotNull 등으로 걸러낼 수 있음 + } + } + Log.d("OnboardingEnterSpaceVM", "Parsed URIs: ${_profileImageUris.value}") + } catch (e: Exception) { + Log.e("OnboardingEnterSpaceVM", "Error processing URI strings", e) + _profileImageUris.value = emptyList() // 오류 발생 시 빈 리스트로 초기화 + } + } else { + Log.d("OnboardingEnterSpaceVM", "No URI strings found in NavArgs.") + _profileImageUris.value = emptyList() // null인 경우 빈 리스트로 초기화 + } + } +} \ No newline at end of file diff --git a/feature/onboarding-enter/src/main/res/values/strings.xml b/feature/onboarding-enter/src/main/res/values/strings.xml index 1513110b..ceaacf18 100644 --- a/feature/onboarding-enter/src/main/res/values/strings.xml +++ b/feature/onboarding-enter/src/main/res/values/strings.xml @@ -12,12 +12,44 @@ "만든 가족공간이에요." + + "할머니와\n" + "어떤 관계" + "인가요?" + + + + "마지막으로\n" + "프로필 사진" + "을 설정해요." + + "가족 매니저가 전송한\n5자리 코드를 입력해 주세요." "가족과 함께\n소중한 추억을 기록해볼까요?" + "할머니에게 보여질\n닉네임을 입력해 주세요." + + "할머니에게 보여질\n프로필 사진을 설정해 주세요." + + "할머니나 할아버지의 성함을\n입력해 주세요." + + + + "다음" "다음" + "관계" + "예) 손녀" + "이름" + "예) 손녀" + "가족 공간 입장하기" + + + + + + diff --git a/feature/onboarding-make/src/main/java/com/teampatch/feature/onboarding/make/OnboardingMakeInviteGrandParentsScreen.kt b/feature/onboarding-make/src/main/java/com/teampatch/feature/onboarding/make/OnboardingMakeInviteGrandParentsScreen.kt index f19939b3..a7cd34b2 100644 --- a/feature/onboarding-make/src/main/java/com/teampatch/feature/onboarding/make/OnboardingMakeInviteGrandParentsScreen.kt +++ b/feature/onboarding-make/src/main/java/com/teampatch/feature/onboarding/make/OnboardingMakeInviteGrandParentsScreen.kt @@ -34,7 +34,7 @@ import com.teampatch.feature.onboarding.make.R.string.text_onboarding_make_next @Composable internal fun OnboardingMakeInviteCodeCreationScreen( onBackRequest: () -> Unit, - onRelationScreenRequest: () -> Unit, + onHomeRouteRequest: () -> Unit, ) { OnBoardingLayout( title = buildAnnotatedString { @@ -52,7 +52,7 @@ internal fun OnboardingMakeInviteCodeCreationScreen( onBackRequest = { onBackRequest() }, bottomBar = { DefaultButton( - onClick = { onRelationScreenRequest() }, + onClick = { onHomeRouteRequest() }, modifier = Modifier .fillMaxWidth() .padding(20.dp) @@ -92,7 +92,7 @@ private fun OnboardingMakeInviteCodeCreationScreenPreview() { HarmonyTheme { OnboardingMakeInviteCodeCreationScreen( onBackRequest = {}, - onRelationScreenRequest = {} + onHomeRouteRequest = {} ) } } \ No newline at end of file diff --git a/feature/onboarding-make/src/main/java/com/teampatch/feature/onboarding/make/OnboardingMakeNavigation.kt b/feature/onboarding-make/src/main/java/com/teampatch/feature/onboarding/make/OnboardingMakeNavigation.kt index 755d7568..facb7406 100644 --- a/feature/onboarding-make/src/main/java/com/teampatch/feature/onboarding/make/OnboardingMakeNavigation.kt +++ b/feature/onboarding-make/src/main/java/com/teampatch/feature/onboarding/make/OnboardingMakeNavigation.kt @@ -41,12 +41,12 @@ fun NavController.navigateToShareInvitationScreen( fun NavGraphBuilder.addOnboardingMakeInviteGrandParentsScreen( onBackRequest: () -> Unit, - onRelationScreenRequest: () -> Unit, + onHomeRouteRequest: () -> Unit, ) { composable { OnboardingMakeInviteCodeCreationScreen( onBackRequest = onBackRequest, - onRelationScreenRequest = onRelationScreenRequest + onHomeRouteRequest = onHomeRouteRequest ) } } diff --git a/feature/onboarding-make/src/main/java/com/teampatch/feature/onboarding/make/OnboardingMakeParentsNameScreen.kt b/feature/onboarding-make/src/main/java/com/teampatch/feature/onboarding/make/OnboardingMakeParentsNameScreen.kt index 4a10a965..95e2e83a 100644 --- a/feature/onboarding-make/src/main/java/com/teampatch/feature/onboarding/make/OnboardingMakeParentsNameScreen.kt +++ b/feature/onboarding-make/src/main/java/com/teampatch/feature/onboarding/make/OnboardingMakeParentsNameScreen.kt @@ -108,7 +108,6 @@ fun CustomDropdownAndTextField( ) { OutlinedTextField( value = selectedText, - placeholder = { Text(stringArrayResource(title_onboarding_make_option_of_gp)[0], color = Color.Gray) }, onValueChange = {}, readOnly = true, trailingIcon = { diff --git a/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingFirstScreen.kt b/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingLoginScreen.kt similarity index 95% rename from feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingFirstScreen.kt rename to feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingLoginScreen.kt index 6efb2ffa..7f6216aa 100644 --- a/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingFirstScreen.kt +++ b/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingLoginScreen.kt @@ -35,10 +35,10 @@ import com.teampatch.core.designsystem.R import com.teampatch.feature.onboarding.login.model.LoginEvent @Composable -internal fun OnboardingFirstScreen( +internal fun OnboardingLoginScreen( onHomeScreenRequest: () -> Unit, onPermissionNotificationRequest: () -> Unit, - onStartScreenRequest: () -> Unit, + onStartSpaceScreenRequest: () -> Unit, viewModel: OnboardingViewModel = hiltViewModel(), ) { val context = LocalContext.current @@ -62,7 +62,7 @@ internal fun OnboardingFirstScreen( if (!hasNotificationGranted(context)) { onPermissionNotificationRequest() } else { - onStartScreenRequest() + onStartSpaceScreenRequest() } } LoginEvent.Success -> { @@ -118,9 +118,9 @@ internal fun OnboardingFirstScreen( @Preview(showBackground = true) @Composable fun OnboardingLoginScreenPreview() { - OnboardingFirstScreen( + OnboardingLoginScreen( onHomeScreenRequest = {}, onPermissionNotificationRequest = {}, - onStartScreenRequest = {} + onStartSpaceScreenRequest = {} ) } \ No newline at end of file diff --git a/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingPermissionNotificationScreen.kt b/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingPermissionNotificationScreen.kt index e2a64549..099b31da 100644 --- a/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingPermissionNotificationScreen.kt +++ b/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingPermissionNotificationScreen.kt @@ -4,28 +4,41 @@ import android.app.Activity import androidx.compose.foundation.Image 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.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.Button -import androidx.compose.material3.ButtonDefaults +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.teampatch.core.designsystem.R +import com.teampatch.core.designsystem.component.DefaultButton +import com.teampatch.core.designsystem.component.SpeechBubble +import com.teampatch.core.designsystem.theme.BL +import com.teampatch.core.designsystem.theme.G3 +import com.teampatch.core.designsystem.theme.G5 +import com.teampatch.core.designsystem.theme.MainGreen +import com.teampatch.core.designsystem.theme.WH +import com.teampatch.feature.onboarding.R.drawable.bell +import com.teampatch.feature.onboarding.R.string.text_onboarding_start_harmony private val requiredPermissions: Array = arrayOf( android.Manifest.permission.POST_NOTIFICATIONS @@ -38,7 +51,7 @@ fun OnboardingPermissionNotificationScreen( val context = LocalContext.current Scaffold( bottomBar = { - Button( + DefaultButton( onClick = { (context as? Activity) ?.requestPermissions(requiredPermissions, 1) @@ -46,54 +59,71 @@ fun OnboardingPermissionNotificationScreen( }, modifier = Modifier .fillMaxWidth() - .padding(horizontal = 20.dp, vertical = 12.dp) - .height(68.dp), - colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent), // 투명한 배경 - contentPadding = PaddingValues(0.dp), // 버튼의 기본 내부 패딩 제거 - shape = RoundedCornerShape(10.dp) + .padding(start = 20.dp, end = 20.dp, bottom = 8.dp) ) { - // 이미지 리소스를 painterResource로 불러오고 버튼을 꽉 채움 - Image( - painter = painterResource(id = R.drawable.btn_start_harmony), // 카카오 로그인 이미지 - contentDescription = "Harmoy Start Process", - modifier = Modifier.fillMaxSize(), // 이미지가 버튼의 크기를 꽉 채움 - contentScale = ContentScale.Crop // 이미지가 버튼 크기에 맞춰 잘림 - ) + Text(text = stringResource(text_onboarding_start_harmony)) } }, modifier = Modifier .fillMaxSize() .background(Color(0xFFF5F5F5)) ) { paddingValues -> - Box( + Column( modifier = Modifier - .fillMaxSize() .padding(paddingValues) - .padding(top = 92.dp, bottom = 8.dp) // Column 시작 위치에 추가 패딩 + .padding(top = 92.dp), + verticalArrangement = Arrangement.SpaceBetween, + horizontalAlignment = Alignment.CenterHorizontally ) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues), - verticalArrangement = Arrangement.SpaceBetween, // 첫 요소는 위, 마지막 요소는 아래에 붙음 - horizontalAlignment = Alignment.CenterHorizontally + SpeechBubble( + backgroundColor = WH, + borderColor = G3 ) { - Image( - painter = painterResource(id = R.drawable.image_permission_to_notify), - contentDescription = "Permission Notification Image", + Column( modifier = Modifier .fillMaxWidth() - ) + .padding(horizontal = 16.dp, vertical = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + painter = painterResource(id = bell), + contentDescription = null, + tint = MainGreen, + modifier = Modifier.size(32.dp) + ) - Spacer(modifier = Modifier.height(25.dp)) + Spacer(modifier = Modifier.height(8.dp)) - Image( - painter = painterResource(id = R.drawable.img_character_fullbody_mony), - contentDescription = "Full-Body Mony Character", - modifier = Modifier - .fillMaxWidth() - ) + Text( + text = buildAnnotatedString { + withStyle(style = SpanStyle(color = MainGreen, fontWeight = FontWeight.Bold)) { + append("알림") + } + append("을 허용해 주세요.") + }, + fontSize = 18.sp, + fontWeight = FontWeight.SemiBold, + color = BL + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "하모니는 가족들이 보내는 알림을 통해\n진행되는 서비스예요.\n가족들과 함께 소중한 추억을 공유해봐요.", + fontSize = 14.sp, + color = G5, + textAlign = TextAlign.Center + ) + } } + + Spacer(modifier = Modifier.height(25.dp)) + + Image( + painter = painterResource(id = R.drawable.img_character_fullbody_mony), + contentDescription = "Full-Body Mony Character", + modifier = Modifier.fillMaxWidth() + ) } } } diff --git a/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingRoute.kt b/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingRoute.kt index ee35c1cd..d18aefcc 100644 --- a/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingRoute.kt +++ b/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingRoute.kt @@ -8,10 +8,10 @@ internal fun OnboardingRoute( onPermissionNotificationRequest: () -> Unit, onStartScreenRequest: () -> Unit, ) { - OnboardingFirstScreen( + OnboardingLoginScreen( onHomeScreenRequest = onHomeScreenRequest, onPermissionNotificationRequest = onPermissionNotificationRequest, - onStartScreenRequest = onStartScreenRequest + onStartSpaceScreenRequest = onStartScreenRequest ) } @@ -21,7 +21,7 @@ internal fun OnboardingStartRoute( onMakeGroupRequest: () -> Unit, onEnterScreenRequest: () -> Unit, ) { - OnboardingStartScreen( + OnboardingStartSpaceScreen( onBackRequest = onBackRequest, onboardingMakeGroupRequest = onMakeGroupRequest, onboardingEnterScreenRequest = onEnterScreenRequest diff --git a/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingStartScreen.kt b/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingStartSpaceScreen.kt similarity index 98% rename from feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingStartScreen.kt rename to feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingStartSpaceScreen.kt index be4c848e..6cc752c7 100644 --- a/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingStartScreen.kt +++ b/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingStartSpaceScreen.kt @@ -32,7 +32,7 @@ import com.teampatch.feature.onboarding.R */ @Composable -fun OnboardingStartScreen( +fun OnboardingStartSpaceScreen( onBackRequest: () -> Unit, onboardingMakeGroupRequest: () -> Unit, onboardingEnterScreenRequest: () -> Unit, @@ -88,7 +88,7 @@ fun OnboardingStartScreen( @Composable private fun OnboardingStartScreenPreview() { HarmonyTheme { - OnboardingStartScreen( + OnboardingStartSpaceScreen( onBackRequest = {}, onboardingMakeGroupRequest = {}, onboardingEnterScreenRequest = {} diff --git a/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingViewModel.kt b/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingViewModel.kt index 551e646a..761509cb 100644 --- a/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingViewModel.kt +++ b/feature/onboarding/src/main/java/com/teampatch/feature/onboarding/login/ui/OnboardingViewModel.kt @@ -29,7 +29,7 @@ internal class OnboardingViewModel @Inject constructor( fun loginKakao() = viewModelScope.launch { runCatching { - loginKakaoUseCase() + loginKakaoUseCase }.onSuccess { _loginEvent.send(LoginEvent.Success) }.onFailure { t -> @@ -37,7 +37,6 @@ internal class OnboardingViewModel @Inject constructor( _loginEvent.send(LoginEvent.FamilyRegistrationRequired) return@launch } - _loginEvent.send(LoginEvent.Error(t)) t.printStackTrace() } diff --git a/feature/onboarding/src/main/res/drawable/bell.xml b/feature/onboarding/src/main/res/drawable/bell.xml new file mode 100644 index 00000000..a65c301e --- /dev/null +++ b/feature/onboarding/src/main/res/drawable/bell.xml @@ -0,0 +1,34 @@ + + + + + + diff --git a/feature/onboarding/src/main/res/values/strings.xml b/feature/onboarding/src/main/res/values/strings.xml index dc030e93..7fdbe2c3 100644 --- a/feature/onboarding/src/main/res/values/strings.xml +++ b/feature/onboarding/src/main/res/values/strings.xml @@ -44,5 +44,8 @@ "할머니를 초대해야\n" + "하모니를 시작할 수 있어요" "전달 받은 5자리 코드를\n" + "입력해 주세요." + "하모니 시작하기" + + diff --git a/gradle.properties b/gradle.properties index 20e2a015..97a23a80 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. For more details, visit # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects