diff --git a/.github/ci-expected-failures.json b/.github/ci-expected-failures.json index ac499f97b3..9972fcdee7 100644 --- a/.github/ci-expected-failures.json +++ b/.github/ci-expected-failures.json @@ -18,20 +18,5 @@ } ], "androidTests": [], - "screenshotModules": [ - { - "module": ":feature:setting:presentation", - "issues": [ - 1360 - ], - "reason": "설정 화면 screenshotTest용 Content 경계 분리 대기 — compileDebugScreenshotTestKotlin 실패" - }, - { - "module": ":feature:timeletter:presentation", - "issues": [ - 1361 - ], - "reason": "목록 화면 screenshotTest용 Content 경계 분리 대기 — compileDebugScreenshotTestKotlin 실패" - } - ] + "screenshotModules": [] } diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/AppLockSetupContent.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/AppLockSetupContent.kt new file mode 100644 index 0000000000..7980f9b4ba --- /dev/null +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/AppLockSetupContent.kt @@ -0,0 +1,108 @@ +package com.afternote.feature.setting.presentation.screen + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +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.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.afternote.core.ui.topbar.DetailTopBar +import com.afternote.feature.setting.presentation.component.InsertPasswordContent +import com.afternote.feature.setting.presentation.component.PinSetupStep + +/** + * 앱 잠금 비밀번호 설정 화면의 상태 없는 본문. + * + * ViewModel·부수효과는 [AppLockSetupScreen] 이 들고, 이 함수는 넘겨받은 상태만 그린다. + */ +@Composable +internal fun AppLockSetupContent( + step: PinSetupStep, + passwordLength: Int, + onDigitClick: (String) -> Unit, + onDeleteClick: () -> Unit, + onConfirmClick: () -> Unit, + onBack: () -> Unit, + modifier: Modifier = Modifier, +) { + val titleText = + when (step) { + PinSetupStep.ENTER_NEW -> "비밀번호를 입력해 주세요." + PinSetupStep.CONFIRM_NEW -> "비밀번호를 다시 한 번 입력해 주세요." + PinSetupStep.ENTER_CURRENT -> "변경할 비밀번호를 입력해 주세요." + } + + Scaffold( + topBar = { + DetailTopBar( + title = "앱 잠금 비밀번호 설정", + onBackClick = onBack, + ) + }, + modifier = modifier, + containerColor = Color.Transparent, + ) { innerPadding -> + Column( + modifier = + Modifier + .fillMaxSize() + .padding(innerPadding), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Spacer(modifier = Modifier.height(40.dp)) + + InsertPasswordContent( + titleText = titleText, + passwordLength = passwordLength, + onDigitClick = onDigitClick, + onDeleteClick = onDeleteClick, + onConfirmClick = onConfirmClick, + ) + } + } +} + +@Preview(name = "비밀번호 입력", showBackground = true) +@Composable +private fun PreviewEnterNew() { + AppLockSetupContent( + step = PinSetupStep.ENTER_NEW, + passwordLength = 0, + onDigitClick = {}, + onDeleteClick = {}, + onConfirmClick = {}, + onBack = {}, + ) +} + +@Preview(name = "비밀번호 재입력", showBackground = true) +@Composable +private fun PreviewConfirmNew() { + AppLockSetupContent( + step = PinSetupStep.CONFIRM_NEW, + passwordLength = 3, + onDigitClick = {}, + onDeleteClick = {}, + onConfirmClick = {}, + onBack = {}, + ) +} + +@Preview(name = "변경할 비밀번호 입력", showBackground = true) +@Composable +private fun PreviewEnterCurrent() { + AppLockSetupContent( + step = PinSetupStep.ENTER_CURRENT, + passwordLength = 0, + onDigitClick = {}, + onDeleteClick = {}, + onConfirmClick = {}, + onBack = {}, + ) +} diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/AppLockSetupScreen.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/AppLockSetupScreen.kt index fb6faeaf00..6202f828b3 100644 --- a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/AppLockSetupScreen.kt +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/AppLockSetupScreen.kt @@ -1,24 +1,12 @@ package com.afternote.feature.setting.presentation.screen -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.afternote.core.ui.topbar.DetailTopBar -import com.afternote.feature.setting.presentation.component.InsertPasswordContent import com.afternote.feature.setting.presentation.component.PinSetupStep import com.afternote.feature.setting.presentation.viewmodel.AppLockSetupViewModel @@ -50,89 +38,3 @@ fun AppLockSetupScreen( modifier = modifier, ) } - -@Composable -private fun AppLockSetupContent( - step: PinSetupStep, - passwordLength: Int, - onDigitClick: (String) -> Unit, - onDeleteClick: () -> Unit, - onConfirmClick: () -> Unit, - onBack: () -> Unit, - modifier: Modifier = Modifier, -) { - val titleText = - when (step) { - PinSetupStep.ENTER_NEW -> "비밀번호를 입력해 주세요." - PinSetupStep.CONFIRM_NEW -> "비밀번호를 다시 한 번 입력해 주세요." - PinSetupStep.ENTER_CURRENT -> "변경할 비밀번호를 입력해 주세요." - } - - Scaffold( - topBar = { - DetailTopBar( - title = "앱 잠금 비밀번호 설정", - onBackClick = onBack, - ) - }, - modifier = modifier, - containerColor = Color.Transparent, - ) { innerPadding -> - Column( - modifier = - Modifier - .fillMaxSize() - .padding(innerPadding), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Spacer(modifier = Modifier.height(40.dp)) - - InsertPasswordContent( - titleText = titleText, - passwordLength = passwordLength, - onDigitClick = onDigitClick, - onDeleteClick = onDeleteClick, - onConfirmClick = onConfirmClick, - ) - } - } -} - -@Preview(name = "비밀번호 입력", showBackground = true) -@Composable -private fun PreviewEnterNew() { - AppLockSetupContent( - step = PinSetupStep.ENTER_NEW, - passwordLength = 0, - onDigitClick = {}, - onDeleteClick = {}, - onConfirmClick = {}, - onBack = {}, - ) -} - -@Preview(name = "비밀번호 재입력", showBackground = true) -@Composable -private fun PreviewConfirmNew() { - AppLockSetupContent( - step = PinSetupStep.CONFIRM_NEW, - passwordLength = 3, - onDigitClick = {}, - onDeleteClick = {}, - onConfirmClick = {}, - onBack = {}, - ) -} - -@Preview(name = "변경할 비밀번호 입력", showBackground = true) -@Composable -private fun PreviewEnterCurrent() { - AppLockSetupContent( - step = PinSetupStep.ENTER_CURRENT, - passwordLength = 0, - onDigitClick = {}, - onDeleteClick = {}, - onConfirmClick = {}, - onBack = {}, - ) -} diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/ConnectedAccountsContent.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/ConnectedAccountsContent.kt new file mode 100644 index 0000000000..9fbb3592ef --- /dev/null +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/ConnectedAccountsContent.kt @@ -0,0 +1,74 @@ +package com.afternote.feature.setting.presentation.screen + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.afternote.core.ui.theme.AfternoteDesign +import com.afternote.core.ui.topbar.DetailTopBar +import com.afternote.feature.setting.presentation.R +import com.afternote.feature.setting.presentation.component.SocialAccountRow +import com.afternote.feature.setting.presentation.viewmodel.ConnectedAccountsUiState + +/** + * 연결된 계정 화면의 상태 없는 본문. + * + * ViewModel·소셜 인증 연동은 [ConnectedAccountsScreen] 이 들고, 이 함수는 넘겨받은 상태만 그린다. + */ +@Composable +internal fun ConnectedAccountsContent( + uiState: ConnectedAccountsUiState, + snackbarHostState: SnackbarHostState, + onBack: () -> Unit, + onToggle: (provider: String, enabled: Boolean) -> Unit, + modifier: Modifier = Modifier, +) { + Scaffold( + modifier = modifier, + containerColor = Color.Transparent, + snackbarHost = { SnackbarHost(snackbarHostState) }, + topBar = { + DetailTopBar( + title = "연결된 계정", + onBackClick = onBack, + ) + }, + ) { paddingValues -> + Column( + modifier = + Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(horizontal = 20.dp, vertical = 16.dp), + ) { + Text( + text = stringResource(R.string.sns_login_section_title), + style = AfternoteDesign.typography.bodyLargeR, + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = stringResource(R.string.sns_login_section_desc), + style = AfternoteDesign.typography.bodySmallR, + ) + Spacer(modifier = Modifier.height(24.dp)) + + uiState.accounts.forEach { account -> + SocialAccountRow( + account = account, + onToggle = { enabled -> onToggle(account.provider, enabled) }, + ) + Spacer(modifier = Modifier.height(8.dp)) + } + } + } +} diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/ConnectedAccountsScreen.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/ConnectedAccountsScreen.kt index 2be78a8fd5..a0a4c79cb2 100644 --- a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/ConnectedAccountsScreen.kt +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/ConnectedAccountsScreen.kt @@ -1,35 +1,22 @@ package com.afternote.feature.setting.presentation.screen import android.app.Activity -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import androidx.credentials.CredentialManager import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.afternote.core.domain.error.CoreAuthFailure import com.afternote.core.ui.findActivity -import com.afternote.core.ui.theme.AfternoteDesign -import com.afternote.core.ui.topbar.DetailTopBar import com.afternote.feature.setting.presentation.BuildConfig import com.afternote.feature.setting.presentation.R -import com.afternote.feature.setting.presentation.component.SocialAccountRow import com.afternote.feature.setting.presentation.social.KakaoAuthResult import com.afternote.feature.setting.presentation.social.requestGoogleIdToken import com.afternote.feature.setting.presentation.social.requestKakaoAccessToken @@ -100,44 +87,13 @@ fun ConnectedAccountsScreen( } } - Scaffold( + ConnectedAccountsContent( + uiState = uiState, + snackbarHostState = snackbarHostState, + onBack = onBack, + onToggle = viewModel::onToggle, modifier = modifier, - containerColor = Color.Transparent, - snackbarHost = { SnackbarHost(snackbarHostState) }, - topBar = { - DetailTopBar( - title = "연결된 계정", - onBackClick = onBack, - ) - }, - ) { paddingValues -> - Column( - modifier = - Modifier - .fillMaxSize() - .padding(paddingValues) - .padding(horizontal = 20.dp, vertical = 16.dp), - ) { - Text( - text = stringResource(R.string.sns_login_section_title), - style = AfternoteDesign.typography.bodyLargeR, - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.sns_login_section_desc), - style = AfternoteDesign.typography.bodySmallR, - ) - Spacer(modifier = Modifier.height(24.dp)) - - uiState.accounts.forEach { account -> - SocialAccountRow( - account = account, - onToggle = { enabled -> viewModel.onToggle(account.provider, enabled) }, - ) - Spacer(modifier = Modifier.height(8.dp)) - } - } - } + ) } @Preview(showBackground = true) diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PassKeyMakingContent.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PassKeyMakingContent.kt new file mode 100644 index 0000000000..66d6832b96 --- /dev/null +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PassKeyMakingContent.kt @@ -0,0 +1,91 @@ +package com.afternote.feature.setting.presentation.screen + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +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.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.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.afternote.core.ui.button.AfternoteButton +import com.afternote.core.ui.button.AfternoteButtonType +import com.afternote.core.ui.theme.AfternoteDesign +import com.afternote.core.ui.topbar.DetailTopBar +import com.afternote.feature.setting.presentation.R + +/** + * 패스키 생성 화면의 상태 없는 본문. + * + * 생체 인증 실행·완료 팝업은 [PassKeyMakingScreen] 이 들고, 이 함수는 넘겨받은 상태만 그린다. + */ +@Composable +internal fun PassKeyMakingContent( + onBackClick: () -> Unit, + onBiometricAuthClick: () -> Unit, + onPasswordAuthClick: () -> Unit, + isBiometricAvailable: Boolean, + modifier: Modifier = Modifier, +) { + Scaffold( + modifier = modifier, + containerColor = Color.Transparent, + topBar = { + DetailTopBar( + title = stringResource(id = R.string.passkey_management_title), + onBackClick = onBackClick, + ) + }, + ) { innerPadding -> + Box( + modifier = + Modifier + .fillMaxSize() + .padding(horizontal = 20.dp), + ) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Spacer(modifier = Modifier.height(137.dp)) + Text( + text = stringResource(R.string.passkey_fingerprint_guide), + style = AfternoteDesign.typography.bodyLargeR, + modifier = Modifier.padding(innerPadding), + ) + Spacer(modifier = Modifier.height(40.dp)) + Image( + painterResource(R.drawable.ic_fingerprint), + "지문", + ) + } + Column( + modifier = + Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 63.dp), + ) { + AfternoteButton( + text = "지문 인증하기", + onClick = onBiometricAuthClick, + type = if (isBiometricAvailable) AfternoteButtonType.Default else AfternoteButtonType.Un, + ) + Spacer(modifier = Modifier.height(8.dp)) + AfternoteButton( + text = "비밀번호로 인증하기", + onClick = onPasswordAuthClick, + type = AfternoteButtonType.Active, + ) + } + } + } +} diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PassKeyMakingScreen.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PassKeyMakingScreen.kt index a890c7ef68..9145ac69ed 100644 --- a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PassKeyMakingScreen.kt +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PassKeyMakingScreen.kt @@ -2,41 +2,21 @@ package com.afternote.feature.setting.presentation.screen import androidx.biometric.BiometricManager import androidx.biometric.BiometricPrompt -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -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.material3.Scaffold -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope 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.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import androidx.fragment.app.FragmentActivity import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel -import com.afternote.core.ui.button.AfternoteButton -import com.afternote.core.ui.button.AfternoteButtonType import com.afternote.core.ui.findActivity import com.afternote.core.ui.popup.Popup import com.afternote.core.ui.popup.PopupType -import com.afternote.core.ui.theme.AfternoteDesign -import com.afternote.core.ui.topbar.DetailTopBar -import com.afternote.feature.setting.presentation.R import com.afternote.feature.setting.presentation.viewmodel.PassKeyViewModel import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine @@ -93,82 +73,37 @@ fun PassKeyMakingScreen( ) } - Scaffold( - modifier = modifier, - containerColor = Color.Transparent, - topBar = { - DetailTopBar( - title = stringResource(id = R.string.passkey_management_title), - onBackClick = onBackClick, - ) - }, - ) { innerPadding -> - Box( - modifier = - Modifier - .fillMaxSize() - .padding(horizontal = 20.dp), - ) { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Spacer(modifier = Modifier.height(137.dp)) - Text( - text = stringResource(R.string.passkey_fingerprint_guide), - style = AfternoteDesign.typography.bodyLargeR, - modifier = Modifier.padding(innerPadding), - ) - Spacer(modifier = Modifier.height(40.dp)) - Image( - painterResource(R.drawable.ic_fingerprint), - "지문", - ) - } - Column( - modifier = - Modifier - .align(Alignment.BottomCenter) - .padding(bottom = 63.dp), - ) { - AfternoteButton( - text = "지문 인증하기", - onClick = { - if (isAuthenticating || activity == null) return@AfternoteButton - isAuthenticating = true - coroutineScope.launch { - try { - val result = authenticate(activity) - when (result) { - BiometricResult.Success -> { - viewModel.savePasskeyRegistered() - showCompletionDialog = true - } - - BiometricResult.Canceled -> { - Unit - } - - is BiometricResult.Error -> { - errorMessage = result.message - } - } - } finally { - isAuthenticating = false + PassKeyMakingContent( + onBackClick = onBackClick, + onBiometricAuthClick = { + if (!isAuthenticating && activity != null) { + isAuthenticating = true + coroutineScope.launch { + try { + when (val result = authenticate(activity)) { + BiometricResult.Success -> { + viewModel.savePasskeyRegistered() + showCompletionDialog = true + } + + BiometricResult.Canceled -> { + Unit + } + + is BiometricResult.Error -> { + errorMessage = result.message } } - }, - type = if (isBiometricAvailable) AfternoteButtonType.Default else AfternoteButtonType.Un, - ) - Spacer(modifier = Modifier.height(8.dp)) - AfternoteButton( - text = "비밀번호로 인증하기", - onClick = onPasswordAuthClick, - type = AfternoteButtonType.Active, - ) + } finally { + isAuthenticating = false + } + } } - } - } + }, + onPasswordAuthClick = onPasswordAuthClick, + isBiometricAvailable = isBiometricAvailable, + modifier = modifier, + ) } private suspend fun authenticate(activity: FragmentActivity): BiometricResult = diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PassKeyPasswordContent.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PassKeyPasswordContent.kt new file mode 100644 index 0000000000..142871de1a --- /dev/null +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PassKeyPasswordContent.kt @@ -0,0 +1,60 @@ +package com.afternote.feature.setting.presentation.screen + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +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.res.stringResource +import androidx.compose.ui.unit.dp +import com.afternote.core.ui.topbar.DetailTopBar +import com.afternote.feature.setting.presentation.R +import com.afternote.feature.setting.presentation.component.InsertPasswordContent + +/** + * 패스키 비밀번호 인증 화면의 상태 없는 본문. + * + * ViewModel·완료 처리는 [PassKeyPasswordScreen] 이 들고, 이 함수는 넘겨받은 상태만 그린다. + */ +@Composable +internal fun PassKeyPasswordContent( + passwordLength: Int, + onDigitClick: (String) -> Unit, + onDeleteClick: () -> Unit, + onConfirmClick: () -> Unit, + onBack: () -> Unit, + modifier: Modifier = Modifier, +) { + Scaffold( + topBar = { + DetailTopBar( + title = stringResource(id = R.string.passkey_management_title), + onBackClick = onBack, + ) + }, + modifier = modifier, + containerColor = Color.Transparent, + ) { innerPadding -> + Column( + modifier = + Modifier + .fillMaxSize() + .padding(innerPadding), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Spacer(modifier = Modifier.height(40.dp)) + InsertPasswordContent( + titleText = "비밀번호를 입력해 주세요.", + passwordLength = passwordLength, + onDigitClick = onDigitClick, + onDeleteClick = onDeleteClick, + onConfirmClick = onConfirmClick, + ) + } + } +} diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PassKeyPasswordScreen.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PassKeyPasswordScreen.kt index 1990505a62..289b6aae7e 100644 --- a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PassKeyPasswordScreen.kt +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PassKeyPasswordScreen.kt @@ -1,26 +1,13 @@ package com.afternote.feature.setting.presentation.screen -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.afternote.core.ui.topbar.DetailTopBar -import com.afternote.feature.setting.presentation.R -import com.afternote.feature.setting.presentation.component.InsertPasswordContent import com.afternote.feature.setting.presentation.viewmodel.AppLockSetupViewModel import com.afternote.feature.setting.presentation.viewmodel.PassKeyViewModel @@ -43,33 +30,14 @@ fun PassKeyPasswordScreen( } } - Scaffold( - topBar = { - DetailTopBar( - title = stringResource(id = R.string.passkey_management_title), - onBackClick = onBack, - ) - }, + PassKeyPasswordContent( + passwordLength = uiState.pin.length, + onDigitClick = viewModel::onDigitInput, + onDeleteClick = viewModel::onDelete, + onConfirmClick = { currentOnPinComplete(uiState.pin) }, + onBack = onBack, modifier = modifier, - containerColor = Color.Transparent, - ) { innerPadding -> - Column( - modifier = - Modifier - .fillMaxSize() - .padding(innerPadding), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Spacer(modifier = Modifier.height(40.dp)) - InsertPasswordContent( - titleText = "비밀번호를 입력해 주세요.", - passwordLength = uiState.pin.length, - onDigitClick = viewModel::onDigitInput, - onDeleteClick = viewModel::onDelete, - onConfirmClick = { currentOnPinComplete(uiState.pin) }, - ) - } - } + ) } @Preview(showBackground = true) diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PushNotificationContent.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PushNotificationContent.kt new file mode 100644 index 0000000000..9d7e3b7b87 --- /dev/null +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PushNotificationContent.kt @@ -0,0 +1,144 @@ +package com.afternote.feature.setting.presentation.screen + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +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.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.afternote.core.ui.theme.AfternoteDesign +import com.afternote.core.ui.topbar.DetailTopBar +import com.afternote.feature.setting.presentation.R +import com.afternote.feature.setting.presentation.component.DeviceAlarmOffSection +import com.afternote.feature.setting.presentation.component.PushToggleSection +import com.afternote.feature.setting.presentation.viewmodel.PushNotificationUiState + +/** + * 푸시 알림 설정 화면의 상태 없는 본문. + * + * ViewModel·시스템 설정 이동은 [PushNotificationScreen] 이 들고, 이 함수는 넘겨받은 상태만 그린다. + */ +@Composable +internal fun PushNotificationContent( + uiState: PushNotificationUiState, + onBack: () -> Unit, + onDeviceAlarmClick: () -> Unit, + onNewsletterToggle: (Boolean) -> Unit, + onMindRecordToggle: (Boolean) -> Unit, + onAfternoteToggle: (Boolean) -> Unit, + onSmsCheck: (Boolean) -> Unit, + onEmailCheck: (Boolean) -> Unit, + onPushCheck: (Boolean) -> Unit, +) { + Scaffold( + topBar = { + DetailTopBar( + title = stringResource(R.string.push_notification_title), + onBackClick = onBack, + ) + }, + containerColor = Color.Transparent, + ) { padding -> + Column( + modifier = + Modifier + .fillMaxSize() + .padding(padding) + .padding(horizontal = 20.dp, vertical = 16.dp), + ) { + // 기기 알림 설정 행 — 항상 표시, 탭하면 Android 시스템 알림 설정으로 이동 + Row( + modifier = + Modifier + .fillMaxWidth() + .clickable { onDeviceAlarmClick() } + .padding(vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(R.string.device_alarm_setting), + style = AfternoteDesign.typography.bodyLargeR, + ) + Spacer(Modifier.width(12.dp)) + Text( + text = + if (uiState.isDeviceAlarmOn) { + stringResource(R.string.device_alarm_on) + } else { + stringResource(R.string.device_alarm_off) + }, + style = AfternoteDesign.typography.captionLargeR, + ) + } + + Spacer(Modifier.height(8.dp)) + + if (uiState.isDeviceAlarmOn) { + PushToggleSection( + uiState = uiState, + onNewsletterToggle = onNewsletterToggle, + onMindRecordToggle = onMindRecordToggle, + onAfternoteToggle = onAfternoteToggle, + ) + } else { + DeviceAlarmOffSection( + uiState = uiState, + onSmsCheck = onSmsCheck, + onEmailCheck = onEmailCheck, + onPushCheck = onPushCheck, + ) + } + + Spacer(Modifier.height(24.dp)) + Text( + text = stringResource(R.string.push_notification_device_guide), + style = AfternoteDesign.typography.captionLargeR, + color = AfternoteDesign.colors.gray5, + ) + } + } +} + +@Preview(name = "기기 알림 켜짐", showBackground = true) +@Composable +private fun PreviewAlarmOn() { + PushNotificationContent( + uiState = PushNotificationUiState(isDeviceAlarmOn = true), + onBack = {}, + onDeviceAlarmClick = {}, + onNewsletterToggle = {}, + onMindRecordToggle = {}, + onAfternoteToggle = {}, + onSmsCheck = {}, + onEmailCheck = {}, + onPushCheck = {}, + ) +} + +@Preview(name = "기기 알림 꺼짐", showBackground = true) +@Composable +private fun PreviewAlarmOff() { + PushNotificationContent( + uiState = PushNotificationUiState(isDeviceAlarmOn = false), + onBack = {}, + onDeviceAlarmClick = {}, + onNewsletterToggle = {}, + onMindRecordToggle = {}, + onAfternoteToggle = {}, + onSmsCheck = {}, + onEmailCheck = {}, + onPushCheck = {}, + ) +} diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PushNotificationScreen.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PushNotificationScreen.kt index a62a8c5c94..ea23e1bfc5 100644 --- a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PushNotificationScreen.kt +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/PushNotificationScreen.kt @@ -2,34 +2,11 @@ package com.afternote.feature.setting.presentation.screen import android.content.Intent import android.provider.Settings -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.afternote.core.ui.theme.AfternoteDesign -import com.afternote.core.ui.topbar.DetailTopBar -import com.afternote.feature.setting.presentation.R -import com.afternote.feature.setting.presentation.component.DeviceAlarmOffSection -import com.afternote.feature.setting.presentation.component.PushToggleSection -import com.afternote.feature.setting.presentation.viewmodel.PushNotificationUiState import com.afternote.feature.setting.presentation.viewmodel.PushNotificationViewModel @Composable @@ -58,116 +35,3 @@ fun PushNotificationScreen( onPushCheck = viewModel::onPushChecked, ) } - -@Composable -private fun PushNotificationContent( - uiState: PushNotificationUiState, - onBack: () -> Unit, - onDeviceAlarmClick: () -> Unit, - onNewsletterToggle: (Boolean) -> Unit, - onMindRecordToggle: (Boolean) -> Unit, - onAfternoteToggle: (Boolean) -> Unit, - onSmsCheck: (Boolean) -> Unit, - onEmailCheck: (Boolean) -> Unit, - onPushCheck: (Boolean) -> Unit, -) { - Scaffold( - topBar = { - DetailTopBar( - title = stringResource(R.string.push_notification_title), - onBackClick = onBack, - ) - }, - containerColor = Color.Transparent, - ) { padding -> - Column( - modifier = - Modifier - .fillMaxSize() - .padding(padding) - .padding(horizontal = 20.dp, vertical = 16.dp), - ) { - // 기기 알림 설정 행 — 항상 표시, 탭하면 Android 시스템 알림 설정으로 이동 - Row( - modifier = - Modifier - .fillMaxWidth() - .clickable { onDeviceAlarmClick() } - .padding(vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = stringResource(R.string.device_alarm_setting), - style = AfternoteDesign.typography.bodyLargeR, - ) - Spacer(Modifier.width(12.dp)) - Text( - text = - if (uiState.isDeviceAlarmOn) { - stringResource(R.string.device_alarm_on) - } else { - stringResource(R.string.device_alarm_off) - }, - style = AfternoteDesign.typography.captionLargeR, - ) - } - - Spacer(Modifier.height(8.dp)) - - if (uiState.isDeviceAlarmOn) { - PushToggleSection( - uiState = uiState, - onNewsletterToggle = onNewsletterToggle, - onMindRecordToggle = onMindRecordToggle, - onAfternoteToggle = onAfternoteToggle, - ) - } else { - DeviceAlarmOffSection( - uiState = uiState, - onSmsCheck = onSmsCheck, - onEmailCheck = onEmailCheck, - onPushCheck = onPushCheck, - ) - } - - Spacer(Modifier.height(24.dp)) - Text( - text = stringResource(R.string.push_notification_device_guide), - style = AfternoteDesign.typography.captionLargeR, - color = AfternoteDesign.colors.gray5, - ) - } - } -} - -@Preview(name = "기기 알림 켜짐", showBackground = true) -@Composable -private fun PreviewAlarmOn() { - PushNotificationContent( - uiState = PushNotificationUiState(isDeviceAlarmOn = true), - onBack = {}, - onDeviceAlarmClick = {}, - onNewsletterToggle = {}, - onMindRecordToggle = {}, - onAfternoteToggle = {}, - onSmsCheck = {}, - onEmailCheck = {}, - onPushCheck = {}, - ) -} - -@Preview(name = "기기 알림 꺼짐", showBackground = true) -@Composable -private fun PreviewAlarmOff() { - PushNotificationContent( - uiState = PushNotificationUiState(isDeviceAlarmOn = false), - onBack = {}, - onDeviceAlarmClick = {}, - onNewsletterToggle = {}, - onMindRecordToggle = {}, - onAfternoteToggle = {}, - onSmsCheck = {}, - onEmailCheck = {}, - onPushCheck = {}, - ) -} diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/ReceiverEditContent.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/ReceiverEditContent.kt new file mode 100644 index 0000000000..f345799eab --- /dev/null +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/ReceiverEditContent.kt @@ -0,0 +1,75 @@ +package com.afternote.feature.setting.presentation.screen + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.CircularProgressIndicator +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 com.afternote.core.ui.asString +import com.afternote.core.ui.theme.AfternoteDesign +import com.afternote.core.ui.topbar.DetailTopBar +import com.afternote.feature.setting.presentation.viewmodel.ReceiverEditUiState + +/** + * 수신자 수정 화면의 상태 없는 본문. + * + * ViewModel·수정 성공 이벤트는 [ReceiverEditScreen] 이 들고, 이 함수는 넘겨받은 상태만 그린다. + */ +@Composable +internal fun ReceiverEditContent( + uiState: ReceiverEditUiState, + onBackClick: () -> Unit, + onRegister: (name: String, relation: String, phone: String, email: String, message: String) -> Unit, + modifier: Modifier = Modifier, +) { + val receiver = uiState.receiver + if (receiver != null) { + ReceiverRegisterContent( + title = "수신자 수정", + actionText = "수정", + isPhoneRequired = true, + isLoading = uiState.isSaving, + errorMessage = uiState.errorMessage, + onBackClick = onBackClick, + onRegister = onRegister, + modifier = modifier, + initialName = receiver.name, + initialRelation = receiver.relation, + initialPhone = receiver.phone.orEmpty(), + initialEmail = receiver.email.orEmpty(), + initialMessage = receiver.message.orEmpty(), + ) + } else { + Scaffold( + modifier = modifier, + topBar = { + DetailTopBar( + title = "수신자 수정", + onBackClick = onBackClick, + ) + }, + ) { innerPadding -> + Box( + modifier = + Modifier + .fillMaxSize() + .padding(innerPadding), + contentAlignment = Alignment.Center, + ) { + if (uiState.isLoading) { + CircularProgressIndicator(color = AfternoteDesign.colors.gray9) + } else { + Text( + text = uiState.errorMessage?.asString().orEmpty(), + style = AfternoteDesign.typography.bodyBase, + color = AfternoteDesign.colors.error, + ) + } + } + } + } +} diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/ReceiverEditScreen.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/ReceiverEditScreen.kt index edd8e7e507..8854638062 100644 --- a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/ReceiverEditScreen.kt +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/ReceiverEditScreen.kt @@ -1,22 +1,12 @@ package com.afternote.feature.setting.presentation.screen -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.afternote.core.ui.asString -import com.afternote.core.ui.theme.AfternoteDesign -import com.afternote.core.ui.topbar.DetailTopBar import com.afternote.feature.setting.presentation.viewmodel.ReceiverEditEvent import com.afternote.feature.setting.presentation.viewmodel.ReceiverEditViewModel @@ -38,50 +28,10 @@ fun ReceiverEditScreen( } } - val receiver = uiState.receiver - if (receiver != null) { - ReceiverRegisterContent( - title = "수신자 수정", - actionText = "수정", - isPhoneRequired = true, - isLoading = uiState.isSaving, - errorMessage = uiState.errorMessage, - onBackClick = onBackClick, - onRegister = viewModel::update, - modifier = modifier, - initialName = receiver.name, - initialRelation = receiver.relation, - initialPhone = receiver.phone.orEmpty(), - initialEmail = receiver.email.orEmpty(), - initialMessage = receiver.message.orEmpty(), - ) - } else { - Scaffold( - modifier = modifier, - topBar = { - DetailTopBar( - title = "수신자 수정", - onBackClick = onBackClick, - ) - }, - ) { innerPadding -> - Box( - modifier = - Modifier - .fillMaxSize() - .padding(innerPadding), - contentAlignment = Alignment.Center, - ) { - if (uiState.isLoading) { - CircularProgressIndicator(color = AfternoteDesign.colors.gray9) - } else { - Text( - text = uiState.errorMessage?.asString().orEmpty(), - style = AfternoteDesign.typography.bodyBase, - color = AfternoteDesign.colors.error, - ) - } - } - } - } + ReceiverEditContent( + uiState = uiState, + onBackClick = onBackClick, + onRegister = viewModel::update, + modifier = modifier, + ) } diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/WithdrawConfirmContent.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/WithdrawConfirmContent.kt new file mode 100644 index 0000000000..088f9e0c5f --- /dev/null +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/WithdrawConfirmContent.kt @@ -0,0 +1,171 @@ +package com.afternote.feature.setting.presentation.screen + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.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.foundation.text.input.rememberTextFieldState +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.afternote.core.ui.AfternoteTextField +import com.afternote.core.ui.button.AfternoteButton +import com.afternote.core.ui.button.AfternoteButtonType +import com.afternote.core.ui.theme.AfternoteDesign +import com.afternote.core.ui.topbar.DetailTopBar +import com.afternote.feature.setting.presentation.R + +private const val WITHDRAW_CONFIRM_TEXT = "탈퇴하겠습니다" + +/** + * 회원 탈퇴 확인 화면의 상태 없는 본문. + * + * ViewModel·탈퇴 결과 팝업은 [WithdrawConfirmScreen] 이 들고, 이 함수는 넘겨받은 상태만 그린다. + * 확인 문구 입력은 화면 지역 상태라 이 함수 안에 남고, 문구가 일치할 때만 [onWithdrawClick] 을 부른다. + */ +@Composable +internal fun WithdrawConfirmContent( + userName: String, + userEmail: String, + onBackClick: () -> Unit, + onWithdrawClick: () -> Unit, + isLoading: Boolean, + modifier: Modifier = Modifier, +) { + val textState = rememberTextFieldState() + var showError by remember { mutableStateOf(false) } + + Scaffold( + topBar = { + DetailTopBar( + title = "회원 탈퇴 안내", + onBackClick = onBackClick, + ) + }, + modifier = modifier, + containerColor = Color.Transparent, + ) { innerPadding -> + Column( + modifier = + Modifier + .fillMaxSize() + .padding(innerPadding) + .padding(horizontal = 16.dp), + ) { + Spacer(Modifier.height(20.dp)) + Text( + text = stringResource(R.string.withdraw_confirm_top_message), + style = AfternoteDesign.typography.bodyBase, + color = AfternoteDesign.colors.gray9, + ) + Spacer(Modifier.height(24.dp)) + WithdrawAccountSection(userName = userName, userEmail = userEmail) + Spacer(Modifier.height(24.dp)) + AfternoteTextField( + state = textState, + placeholder = stringResource(R.string.withdraw_confirm_placeholder), + modifier = Modifier.fillMaxWidth(), + ) + if (showError) { + Spacer(Modifier.height(6.dp)) + Text( + text = stringResource(R.string.withdraw_confirm_error), + style = AfternoteDesign.typography.captionLargeR, + color = AfternoteDesign.colors.error, + ) + } + Spacer(Modifier.weight(1f)) + WithdrawConfirmBottomButtons( + onBackClick = onBackClick, + onWithdrawClick = { + if (textState.text.toString() == WITHDRAW_CONFIRM_TEXT) { + showError = false + onWithdrawClick() + } else { + showError = true + } + }, + isLoading = isLoading, + ) + Spacer(Modifier.height(24.dp)) + } + } +} + +@Composable +private fun WithdrawAccountSection( + userName: String, + userEmail: String, +) { + Column( + modifier = + Modifier + .fillMaxWidth() + .height(82.dp) + .clip(RoundedCornerShape(12.dp)) + .background(color = AfternoteDesign.colors.gray2) + .padding(start = 16.dp), + ) { + Spacer(modifier = Modifier.height(12.dp)) + Text( + text = stringResource(R.string.withdraw_account_section), + style = AfternoteDesign.typography.textField, + color = AfternoteDesign.colors.gray9, + ) + Spacer(modifier = Modifier.height(8.dp)) + Row { + Text( + text = userName, + style = AfternoteDesign.typography.bodySmallR, + color = AfternoteDesign.colors.gray9, + ) + Text( + text = " ($userEmail)", + style = AfternoteDesign.typography.bodySmallR, + color = AfternoteDesign.colors.gray6, + ) + } + Spacer(Modifier.height(12.dp)) + } +} + +@Composable +private fun WithdrawConfirmBottomButtons( + onBackClick: () -> Unit, + onWithdrawClick: () -> Unit, + isLoading: Boolean, +) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + AfternoteButton( + text = stringResource(R.string.withdraw_confirm_prev_button), + onClick = onBackClick, + type = AfternoteButtonType.Plain, + modifier = Modifier.fillMaxWidth(), + ) + AfternoteButton( + text = stringResource(R.string.withdraw_confirm_button), + onClick = onWithdrawClick, + type = AfternoteButtonType.Default, + isLoading = isLoading, + modifier = Modifier.fillMaxWidth(), + ) + } +} diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/WithdrawConfirmScreen.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/WithdrawConfirmScreen.kt index 49771d46b7..0bc203f7cc 100644 --- a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/WithdrawConfirmScreen.kt +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/WithdrawConfirmScreen.kt @@ -1,43 +1,17 @@ package com.afternote.feature.setting.presentation.screen -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.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.foundation.text.input.rememberTextFieldState -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.afternote.core.ui.AfternoteTextField -import com.afternote.core.ui.button.AfternoteButton -import com.afternote.core.ui.button.AfternoteButtonType import com.afternote.core.ui.popup.Popup import com.afternote.core.ui.popup.PopupType -import com.afternote.core.ui.theme.AfternoteDesign -import com.afternote.core.ui.topbar.DetailTopBar import com.afternote.feature.setting.presentation.R import com.afternote.feature.setting.presentation.viewmodel.SettingUiState import com.afternote.feature.setting.presentation.viewmodel.SettingViewModel import com.afternote.feature.setting.presentation.viewmodel.WithdrawUiState -private const val WITHDRAW_CONFIRM_TEXT = "탈퇴하겠습니다" - @Composable fun WithdrawConfirmScreen( uiState: SettingUiState, @@ -48,8 +22,6 @@ fun WithdrawConfirmScreen( ) { val userName = (uiState as? SettingUiState.Success)?.name.orEmpty() val userEmail = (uiState as? SettingUiState.Success)?.email.orEmpty() - val textState = rememberTextFieldState() - var showError by remember { mutableStateOf(false) } val withdrawUiState by viewModel.withdrawUiState.collectAsStateWithLifecycle() when (withdrawUiState) { @@ -79,122 +51,12 @@ fun WithdrawConfirmScreen( -> {} } - Scaffold( - topBar = { - DetailTopBar( - title = "회원 탈퇴 안내", - onBackClick = onBackClick, - ) - }, + WithdrawConfirmContent( + userName = userName, + userEmail = userEmail, + onBackClick = onBackClick, + onWithdrawClick = viewModel::deleteAccount, + isLoading = withdrawUiState == WithdrawUiState.Loading, modifier = modifier, - containerColor = Color.Transparent, - ) { innerPadding -> - Column( - modifier = - Modifier - .fillMaxSize() - .padding(innerPadding) - .padding(horizontal = 16.dp), - ) { - Spacer(Modifier.height(20.dp)) - Text( - text = stringResource(R.string.withdraw_confirm_top_message), - style = AfternoteDesign.typography.bodyBase, - color = AfternoteDesign.colors.gray9, - ) - Spacer(Modifier.height(24.dp)) - WithdrawAccountSection(userName = userName, userEmail = userEmail) - Spacer(Modifier.height(24.dp)) - AfternoteTextField( - state = textState, - placeholder = stringResource(R.string.withdraw_confirm_placeholder), - modifier = Modifier.fillMaxWidth(), - ) - if (showError) { - Spacer(Modifier.height(6.dp)) - Text( - text = stringResource(R.string.withdraw_confirm_error), - style = AfternoteDesign.typography.captionLargeR, - color = AfternoteDesign.colors.error, - ) - } - Spacer(Modifier.weight(1f)) - WithdrawConfirmBottomButtons( - onBackClick = onBackClick, - onWithdrawClick = { - if (textState.text.toString() == WITHDRAW_CONFIRM_TEXT) { - showError = false - viewModel.deleteAccount() - } else { - showError = true - } - }, - isLoading = withdrawUiState == WithdrawUiState.Loading, - ) - Spacer(Modifier.height(24.dp)) - } - } -} - -@Composable -private fun WithdrawAccountSection( - userName: String, - userEmail: String, -) { - Column( - modifier = - Modifier - .fillMaxWidth() - .height(82.dp) - .clip(RoundedCornerShape(12.dp)) - .background(color = AfternoteDesign.colors.gray2) - .padding(start = 16.dp), - ) { - Spacer(modifier = Modifier.height(12.dp)) - Text( - text = stringResource(R.string.withdraw_account_section), - style = AfternoteDesign.typography.textField, - color = AfternoteDesign.colors.gray9, - ) - Spacer(modifier = Modifier.height(8.dp)) - Row { - Text( - text = userName, - style = AfternoteDesign.typography.bodySmallR, - color = AfternoteDesign.colors.gray9, - ) - Text( - text = " ($userEmail)", - style = AfternoteDesign.typography.bodySmallR, - color = AfternoteDesign.colors.gray6, - ) - } - Spacer(Modifier.height(12.dp)) - } -} - -@Composable -private fun WithdrawConfirmBottomButtons( - onBackClick: () -> Unit, - onWithdrawClick: () -> Unit, - isLoading: Boolean, -) { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - AfternoteButton( - text = stringResource(R.string.withdraw_confirm_prev_button), - onClick = onBackClick, - type = AfternoteButtonType.Plain, - modifier = Modifier.fillMaxWidth(), - ) - AfternoteButton( - text = stringResource(R.string.withdraw_confirm_button), - onClick = onWithdrawClick, - type = AfternoteButtonType.Default, - isLoading = isLoading, - modifier = Modifier.fillMaxWidth(), - ) - } + ) } diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/AppLockSetupScreenScreenshotTestKt/appLockSetupConfirmNewCompactScreenshot_c2abd7e6_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/AppLockSetupScreenScreenshotTestKt/appLockSetupConfirmNewCompactScreenshot_c2abd7e6_0.png new file mode 100644 index 0000000000..94c179dad8 Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/AppLockSetupScreenScreenshotTestKt/appLockSetupConfirmNewCompactScreenshot_c2abd7e6_0.png differ diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/AppLockSetupScreenScreenshotTestKt/appLockSetupConfirmNewScreenshot_748aa731_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/AppLockSetupScreenScreenshotTestKt/appLockSetupConfirmNewScreenshot_748aa731_0.png new file mode 100644 index 0000000000..0900cf8e94 Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/AppLockSetupScreenScreenshotTestKt/appLockSetupConfirmNewScreenshot_748aa731_0.png differ diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/AppLockSetupScreenScreenshotTestKt/appLockSetupEnterCurrentScreenshot_748aa731_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/AppLockSetupScreenScreenshotTestKt/appLockSetupEnterCurrentScreenshot_748aa731_0.png new file mode 100644 index 0000000000..ea9449e627 Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/AppLockSetupScreenScreenshotTestKt/appLockSetupEnterCurrentScreenshot_748aa731_0.png differ diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/AppLockSetupScreenScreenshotTestKt/appLockSetupEnterNewScreenshot_748aa731_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/AppLockSetupScreenScreenshotTestKt/appLockSetupEnterNewScreenshot_748aa731_0.png new file mode 100644 index 0000000000..0732d15061 Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/AppLockSetupScreenScreenshotTestKt/appLockSetupEnterNewScreenshot_748aa731_0.png differ diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/ConnectedAccountsScreenScreenshotTestKt/connectedAccountsScreenCompactScreenshot_c2abd7e6_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/ConnectedAccountsScreenScreenshotTestKt/connectedAccountsScreenCompactScreenshot_c2abd7e6_0.png new file mode 100644 index 0000000000..669f4242f6 Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/ConnectedAccountsScreenScreenshotTestKt/connectedAccountsScreenCompactScreenshot_c2abd7e6_0.png differ diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/ConnectedAccountsScreenScreenshotTestKt/connectedAccountsScreenScreenshot_748aa731_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/ConnectedAccountsScreenScreenshotTestKt/connectedAccountsScreenScreenshot_748aa731_0.png new file mode 100644 index 0000000000..303a2852a4 Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/ConnectedAccountsScreenScreenshotTestKt/connectedAccountsScreenScreenshot_748aa731_0.png differ diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PassKeyMakingScreenScreenshotTestKt/passKeyMakingScreenCompactScreenshot_c2abd7e6_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PassKeyMakingScreenScreenshotTestKt/passKeyMakingScreenCompactScreenshot_c2abd7e6_0.png new file mode 100644 index 0000000000..e9cd727ded Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PassKeyMakingScreenScreenshotTestKt/passKeyMakingScreenCompactScreenshot_c2abd7e6_0.png differ diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PassKeyMakingScreenScreenshotTestKt/passKeyMakingScreenScreenshot_748aa731_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PassKeyMakingScreenScreenshotTestKt/passKeyMakingScreenScreenshot_748aa731_0.png new file mode 100644 index 0000000000..46ffdf454b Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PassKeyMakingScreenScreenshotTestKt/passKeyMakingScreenScreenshot_748aa731_0.png differ diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PassKeyPasswordScreenScreenshotTestKt/passKeyPasswordScreenCompactScreenshot_c2abd7e6_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PassKeyPasswordScreenScreenshotTestKt/passKeyPasswordScreenCompactScreenshot_c2abd7e6_0.png new file mode 100644 index 0000000000..9fcdcaddc4 Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PassKeyPasswordScreenScreenshotTestKt/passKeyPasswordScreenCompactScreenshot_c2abd7e6_0.png differ diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PassKeyPasswordScreenScreenshotTestKt/passKeyPasswordScreenScreenshot_748aa731_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PassKeyPasswordScreenScreenshotTestKt/passKeyPasswordScreenScreenshot_748aa731_0.png new file mode 100644 index 0000000000..8b3a23f5da Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PassKeyPasswordScreenScreenshotTestKt/passKeyPasswordScreenScreenshot_748aa731_0.png differ diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PassKeyScreenScreenshotTestKt/passKeyScreenCompactScreenshot_c2abd7e6_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PassKeyScreenScreenshotTestKt/passKeyScreenCompactScreenshot_c2abd7e6_0.png new file mode 100644 index 0000000000..8b0067a457 Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PassKeyScreenScreenshotTestKt/passKeyScreenCompactScreenshot_c2abd7e6_0.png differ diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PassKeyScreenScreenshotTestKt/passKeyScreenScreenshot_748aa731_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PassKeyScreenScreenshotTestKt/passKeyScreenScreenshot_748aa731_0.png new file mode 100644 index 0000000000..b6c805ea88 Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PassKeyScreenScreenshotTestKt/passKeyScreenScreenshot_748aa731_0.png differ diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PushNotificationScreenScreenshotTestKt/pushNotificationAlarmOffCompactScreenshot_c2abd7e6_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PushNotificationScreenScreenshotTestKt/pushNotificationAlarmOffCompactScreenshot_c2abd7e6_0.png new file mode 100644 index 0000000000..c9a959e252 Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PushNotificationScreenScreenshotTestKt/pushNotificationAlarmOffCompactScreenshot_c2abd7e6_0.png differ diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PushNotificationScreenScreenshotTestKt/pushNotificationAlarmOffScreenshot_748aa731_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PushNotificationScreenScreenshotTestKt/pushNotificationAlarmOffScreenshot_748aa731_0.png new file mode 100644 index 0000000000..5c9deedc8d Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PushNotificationScreenScreenshotTestKt/pushNotificationAlarmOffScreenshot_748aa731_0.png differ diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PushNotificationScreenScreenshotTestKt/pushNotificationAlarmOnScreenshot_748aa731_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PushNotificationScreenScreenshotTestKt/pushNotificationAlarmOnScreenshot_748aa731_0.png new file mode 100644 index 0000000000..3196d6b55b Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/PushNotificationScreenScreenshotTestKt/pushNotificationAlarmOnScreenshot_748aa731_0.png differ diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/ReceiverEditScreenScreenshotTestKt/receiverEditScreenCompactScreenshot_c2abd7e6_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/ReceiverEditScreenScreenshotTestKt/receiverEditScreenCompactScreenshot_c2abd7e6_0.png new file mode 100644 index 0000000000..0fcb3686e3 Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/ReceiverEditScreenScreenshotTestKt/receiverEditScreenCompactScreenshot_c2abd7e6_0.png differ diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/ReceiverEditScreenScreenshotTestKt/receiverEditScreenScreenshot_748aa731_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/ReceiverEditScreenScreenshotTestKt/receiverEditScreenScreenshot_748aa731_0.png new file mode 100644 index 0000000000..ec6d2a2138 Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/ReceiverEditScreenScreenshotTestKt/receiverEditScreenScreenshot_748aa731_0.png differ diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/WithdrawConfirmScreenScreenshotTestKt/withdrawConfirmScreenCompactScreenshot_c2abd7e6_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/WithdrawConfirmScreenScreenshotTestKt/withdrawConfirmScreenCompactScreenshot_c2abd7e6_0.png new file mode 100644 index 0000000000..471ef52b5c Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/WithdrawConfirmScreenScreenshotTestKt/withdrawConfirmScreenCompactScreenshot_c2abd7e6_0.png differ diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/WithdrawConfirmScreenScreenshotTestKt/withdrawConfirmScreenScreenshot_748aa731_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/WithdrawConfirmScreenScreenshotTestKt/withdrawConfirmScreenScreenshot_748aa731_0.png new file mode 100644 index 0000000000..b025f48034 Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/WithdrawConfirmScreenScreenshotTestKt/withdrawConfirmScreenScreenshot_748aa731_0.png differ diff --git a/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreen.kt b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreen.kt index 84639183fa..228f09a694 100644 --- a/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreen.kt +++ b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreen.kt @@ -1,11 +1,5 @@ package com.afternote.feature.timeletter.presentation.screen.sender -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -14,9 +8,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable 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.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel @@ -24,15 +16,12 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.repeatOnLifecycle -import com.afternote.core.ui.button.FAB.PenFloatingActionButton import com.afternote.core.ui.popup.Popup import com.afternote.core.ui.popup.PopupType -import com.afternote.core.ui.topbar.HomeTopBar import com.afternote.feature.timeletter.domain.model.TimeLetter import com.afternote.feature.timeletter.domain.model.TimeLetterList import com.afternote.feature.timeletter.domain.model.TimeLetterStatus import com.afternote.feature.timeletter.presentation.R -import com.afternote.feature.timeletter.presentation.component.EmptyTimeLetterContent import com.afternote.feature.timeletter.presentation.component.TimeLetterContent import com.afternote.feature.timeletter.presentation.viewmodel.TimeletterUiState import com.afternote.feature.timeletter.presentation.viewmodel.TimeletterViewModel @@ -86,43 +75,19 @@ fun TimeletterScreen( } } - Scaffold( + TimeletterScreenContent( + uiState = uiState, + viewMode = viewMode, + onViewModeChange = { viewMode = it }, + snackbarHostState = snackbarHostState, + onLetterClick = onLetterClick, + onSettingClick = onSettingClick, + onWriteClick = onWriteClick, + onEditClick = onEditClick, + onFilterRecipientClick = onFilterRecipientClick, + onDeleteClick = { pendingDeleteId = it }, modifier = modifier, - containerColor = Color.Transparent, - topBar = { HomeTopBar(onSettingClick = onSettingClick) }, - floatingActionButton = { PenFloatingActionButton(onClick = onWriteClick) }, - snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, - ) { paddingValues -> - when (val state = uiState) { - is TimeletterUiState.Loading -> { - Box( - modifier = Modifier.fillMaxSize().padding(paddingValues), - contentAlignment = Alignment.Center, - ) { - CircularProgressIndicator() - } - } - - is TimeletterUiState.Error -> { - EmptyTimeLetterContent(modifier = Modifier.padding(paddingValues)) - } - - is TimeletterUiState.Success -> { - TimeLetterContent( - letters = state.letters, - receiverNameMap = state.receiverNameMap, - viewMode = viewMode, - onViewModeChange = { viewMode = it }, - selectedFilterReceiverIds = state.selectedFilterReceiverIds, - onFilterClick = onFilterRecipientClick, - onLetterClick = onLetterClick, - onEditClick = onEditClick, - onDeleteClick = { pendingDeleteId = it }, - modifier = Modifier.padding(paddingValues), - ) - } - } - } + ) } private val previewLetters = diff --git a/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreenContent.kt b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreenContent.kt new file mode 100644 index 0000000000..beb3d155d3 --- /dev/null +++ b/feature/timeletter/presentation/src/main/kotlin/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreenContent.kt @@ -0,0 +1,78 @@ +package com.afternote.feature.timeletter.presentation.screen.sender + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import com.afternote.core.ui.button.FAB.PenFloatingActionButton +import com.afternote.core.ui.topbar.HomeTopBar +import com.afternote.feature.timeletter.presentation.component.EmptyTimeLetterContent +import com.afternote.feature.timeletter.presentation.component.TimeLetterContent +import com.afternote.feature.timeletter.presentation.viewmodel.TimeletterUiState +import com.afternote.feature.timeletter.presentation.viewmodel.ViewMode + +/** + * 타임레터 목록 화면의 상태 없는 본문. + * + * ViewModel·부수효과는 [TimeletterScreen] 이 들고, 이 함수는 넘겨받은 상태만 그린다. + * screenshotTest 가 각 상태를 고정 입력으로 렌더하기 위한 경계다. + */ +@Composable +internal fun TimeletterScreenContent( + uiState: TimeletterUiState, + viewMode: ViewMode, + onViewModeChange: (ViewMode) -> Unit, + snackbarHostState: SnackbarHostState, + onLetterClick: (Long) -> Unit, + onSettingClick: () -> Unit, + onWriteClick: () -> Unit, + onEditClick: (Long) -> Unit, + onFilterRecipientClick: () -> Unit, + onDeleteClick: (Long) -> Unit, + modifier: Modifier = Modifier, +) { + Scaffold( + modifier = modifier, + containerColor = Color.Transparent, + topBar = { HomeTopBar(onSettingClick = onSettingClick) }, + floatingActionButton = { PenFloatingActionButton(onClick = onWriteClick) }, + snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, + ) { paddingValues -> + when (uiState) { + is TimeletterUiState.Loading -> { + Box( + modifier = Modifier.fillMaxSize().padding(paddingValues), + contentAlignment = Alignment.Center, + ) { + CircularProgressIndicator() + } + } + + is TimeletterUiState.Error -> { + EmptyTimeLetterContent(modifier = Modifier.padding(paddingValues)) + } + + is TimeletterUiState.Success -> { + TimeLetterContent( + letters = uiState.letters, + receiverNameMap = uiState.receiverNameMap, + viewMode = viewMode, + onViewModeChange = onViewModeChange, + selectedFilterReceiverIds = uiState.selectedFilterReceiverIds, + onFilterClick = onFilterRecipientClick, + onLetterClick = onLetterClick, + onEditClick = onEditClick, + onDeleteClick = onDeleteClick, + modifier = Modifier.padding(paddingValues), + ) + } + } + } +} diff --git a/feature/timeletter/presentation/src/screenshotTestDebug/reference/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreenScreenshotTestKt/timeletterScreenBlockCompactScreenshot_c2abd7e6_0.png b/feature/timeletter/presentation/src/screenshotTestDebug/reference/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreenScreenshotTestKt/timeletterScreenBlockCompactScreenshot_c2abd7e6_0.png new file mode 100644 index 0000000000..5a54c7a096 Binary files /dev/null and b/feature/timeletter/presentation/src/screenshotTestDebug/reference/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreenScreenshotTestKt/timeletterScreenBlockCompactScreenshot_c2abd7e6_0.png differ diff --git a/feature/timeletter/presentation/src/screenshotTestDebug/reference/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreenScreenshotTestKt/timeletterScreenBlockScreenshot_748aa731_0.png b/feature/timeletter/presentation/src/screenshotTestDebug/reference/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreenScreenshotTestKt/timeletterScreenBlockScreenshot_748aa731_0.png new file mode 100644 index 0000000000..6c905530e7 Binary files /dev/null and b/feature/timeletter/presentation/src/screenshotTestDebug/reference/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreenScreenshotTestKt/timeletterScreenBlockScreenshot_748aa731_0.png differ diff --git a/feature/timeletter/presentation/src/screenshotTestDebug/reference/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreenScreenshotTestKt/timeletterScreenEmptyScreenshot_748aa731_0.png b/feature/timeletter/presentation/src/screenshotTestDebug/reference/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreenScreenshotTestKt/timeletterScreenEmptyScreenshot_748aa731_0.png new file mode 100644 index 0000000000..1ac2bee7c2 Binary files /dev/null and b/feature/timeletter/presentation/src/screenshotTestDebug/reference/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreenScreenshotTestKt/timeletterScreenEmptyScreenshot_748aa731_0.png differ diff --git a/feature/timeletter/presentation/src/screenshotTestDebug/reference/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreenScreenshotTestKt/timeletterScreenListScreenshot_748aa731_0.png b/feature/timeletter/presentation/src/screenshotTestDebug/reference/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreenScreenshotTestKt/timeletterScreenListScreenshot_748aa731_0.png new file mode 100644 index 0000000000..438c204a42 Binary files /dev/null and b/feature/timeletter/presentation/src/screenshotTestDebug/reference/com/afternote/feature/timeletter/presentation/screen/sender/TimeletterScreenScreenshotTestKt/timeletterScreenListScreenshot_748aa731_0.png differ