diff --git a/app/src/androidTest/java/com/afternote/afternote_fe/SettingImplementedCoverageAndroidTest.kt b/app/src/androidTest/java/com/afternote/afternote_fe/SettingImplementedCoverageAndroidTest.kt index 796f44ebff..9dd1e14d1d 100644 --- a/app/src/androidTest/java/com/afternote/afternote_fe/SettingImplementedCoverageAndroidTest.kt +++ b/app/src/androidTest/java/com/afternote/afternote_fe/SettingImplementedCoverageAndroidTest.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.test.hasScrollAction import androidx.compose.ui.test.hasText import androidx.compose.ui.test.junit4.StateRestorationTester import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.onRoot import androidx.compose.ui.test.performClick @@ -174,6 +175,27 @@ class SettingImplementedCoverageAndroidTest { waitForRootHost() } + @Test + fun actualSettingNavHost_faqRowNavigatesToFaqScreenAndBackReturnsHome() { + waitForSettingHomeContent() + composeRule + .onNode(hasScrollAction()) + .performScrollToNode(hasText("FAQ")) + composeRule + .onNodeWithText("FAQ") + .performClick() + + waitForText("비밀번호를 잊어버렸어요.") + composeRule + .onNodeWithText("비밀번호를 잊어버렸어요.") + .assertIsDisplayed() + + composeRule + .onNodeWithContentDescription("뒤로가기") + .performClick() + waitForSettingHomeContent() + } + private fun openWithdrawGuide() { waitForSettingHomeContent() composeRule diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/navigation/SettingLocalNavActions.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/navigation/SettingLocalNavActions.kt index 1e8369dd16..5a615d5d62 100644 --- a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/navigation/SettingLocalNavActions.kt +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/navigation/SettingLocalNavActions.kt @@ -93,4 +93,8 @@ internal class SettingLocalNavActions( override fun onNavigateToInquiryWrite() { backStack.add(SettingRoute.InquiryWriteRoute) } + + override fun onNavigateToFaq() { + backStack.add(SettingRoute.FaqRoute) + } } diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/navigation/SettingNavActions.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/navigation/SettingNavActions.kt index 406dec333a..597bdec18f 100644 --- a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/navigation/SettingNavActions.kt +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/navigation/SettingNavActions.kt @@ -44,4 +44,6 @@ internal interface SettingNavActions { fun onNavigateToInquiryDetail(inquiryId: Long) fun onNavigateToInquiryWrite() + + fun onNavigateToFaq() } diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/navigation/SettingNavHost.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/navigation/SettingNavHost.kt index 26527e253b..3d235cc657 100644 --- a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/navigation/SettingNavHost.kt +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/navigation/SettingNavHost.kt @@ -15,6 +15,7 @@ import com.afternote.feature.setting.presentation.component.PinSetupStep import com.afternote.feature.setting.presentation.screen.AppLockSetupScreen import com.afternote.feature.setting.presentation.screen.ConnectedAccountsScreen import com.afternote.feature.setting.presentation.screen.DeliveryConditionScreen +import com.afternote.feature.setting.presentation.screen.FaqScreen import com.afternote.feature.setting.presentation.screen.InquiryDetailScreen import com.afternote.feature.setting.presentation.screen.InquiryListScreen import com.afternote.feature.setting.presentation.screen.InquiryWriteScreen @@ -85,7 +86,7 @@ public fun SettingNavHost( }, onPasskeyClick = actions::onNavigateToPasskey, onAppLockClick = actions::onNavigateToAppLock, - onFaqClick = {}, + onFaqClick = actions::onNavigateToFaq, onInquiryClick = actions::onNavigateToInquiry, onNoticeClick = actions::onNavigateToNotice, onTermsClick = {}, @@ -255,6 +256,10 @@ public fun SettingNavHost( entry { InquiryWriteScreen(onBackClick = actions::popBack) } + + entry { + FaqScreen(onBackClick = actions::popBack) + } }, ) } diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/navigation/SettingRoute.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/navigation/SettingRoute.kt index 9a5a2e175e..fdd87a6b37 100644 --- a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/navigation/SettingRoute.kt +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/navigation/SettingRoute.kt @@ -59,6 +59,9 @@ internal sealed interface SettingRoute : NavKey { @Serializable data object NoticeRoute : SettingRoute + @Serializable + data object FaqRoute : SettingRoute + @Serializable data object InquiryListRoute : SettingRoute diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/FaqScreen.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/FaqScreen.kt new file mode 100644 index 0000000000..882a7b0fe5 --- /dev/null +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/screen/FaqScreen.kt @@ -0,0 +1,129 @@ +package com.afternote.feature.setting.presentation.screen + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +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.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +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 com.afternote.core.ui.theme.AfternoteDesign +import com.afternote.core.ui.theme.AfternoteTheme +import com.afternote.core.ui.topbar.DetailTopBar +import com.afternote.feature.setting.presentation.R +import com.afternote.core.ui.R as CoreUiR + +@Composable +fun FaqScreen( + onBackClick: () -> Unit, + modifier: Modifier = Modifier, +) { + var expanded by rememberSaveable { mutableStateOf(false) } + + Scaffold( + modifier = modifier, + topBar = { + DetailTopBar( + title = stringResource(R.string.settings_support_faq), + onBackClick = onBackClick, + ) + }, + containerColor = AfternoteDesign.colors.gray1, + ) { innerPadding -> + LazyColumn( + modifier = + Modifier + .fillMaxSize() + .padding(innerPadding), + ) { + item { + FaqItem( + question = stringResource(R.string.faq_default_question), + answer = stringResource(R.string.faq_default_answer), + expanded = expanded, + onClick = { expanded = !expanded }, + ) + } + } + } +} + +@Composable +private fun FaqItem( + question: String, + answer: String, + expanded: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + modifier = + modifier + .fillMaxWidth() + .background(AfternoteDesign.colors.white), + ) { + Row( + modifier = + Modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(horizontal = 20.dp, vertical = 18.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = question, + modifier = Modifier.weight(1f), + style = AfternoteDesign.typography.bodyBase, + color = AfternoteDesign.colors.gray9, + ) + Icon( + painter = painterResource(CoreUiR.drawable.core_ui_arrowdown), + contentDescription = null, + modifier = + Modifier + .size(24.dp) + .rotate(if (expanded) 180f else 0f), + tint = AfternoteDesign.colors.gray7, + ) + } + if (expanded) { + Text( + text = answer, + modifier = + Modifier + .fillMaxWidth() + .background(AfternoteDesign.colors.gray1) + .padding(horizontal = 20.dp, vertical = 18.dp), + style = AfternoteDesign.typography.bodySmallR, + color = AfternoteDesign.colors.gray7, + ) + } + HorizontalDivider(color = AfternoteDesign.colors.gray2) + } +} + +@Preview(showBackground = true) +@Composable +private fun FaqScreenPreview() { + AfternoteTheme { + FaqScreen(onBackClick = {}) + } +} diff --git a/feature/setting/presentation/src/main/res/values/strings.xml b/feature/setting/presentation/src/main/res/values/strings.xml index c98b7d0d8b..1b31025a61 100644 --- a/feature/setting/presentation/src/main/res/values/strings.xml +++ b/feature/setting/presentation/src/main/res/values/strings.xml @@ -21,6 +21,8 @@ 앱 잠금 설정 FAQ + 비밀번호를 잊어버렸어요. + 로그인 화면의 \'비밀번호 찾기\'를 통해 재설정할 수 있습니다. 고객센터 1:1 문의 새 문의 접수하기 diff --git a/feature/setting/presentation/src/screenshotTest/kotlin/com/afternote/feature/setting/presentation/screen/FaqScreenScreenshotTest.kt b/feature/setting/presentation/src/screenshotTest/kotlin/com/afternote/feature/setting/presentation/screen/FaqScreenScreenshotTest.kt new file mode 100644 index 0000000000..826e6b3061 --- /dev/null +++ b/feature/setting/presentation/src/screenshotTest/kotlin/com/afternote/feature/setting/presentation/screen/FaqScreenScreenshotTest.kt @@ -0,0 +1,28 @@ +package com.afternote.feature.setting.presentation.screen + +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import com.afternote.core.ui.theme.AfternoteTheme +import com.afternote.feature.setting.presentation.COMPACT_DEVICE_SPEC +import com.android.tools.screenshot.PreviewTest + +@PreviewTest +@Preview(showBackground = true) +@Composable +internal fun faqScreenScreenshot() { + FaqScreenScreenshotContent() +} + +@PreviewTest +@Preview(showBackground = true, device = COMPACT_DEVICE_SPEC) +@Composable +internal fun faqScreenCompactScreenshot() { + FaqScreenScreenshotContent() +} + +@Composable +private fun FaqScreenScreenshotContent() { + AfternoteTheme { + FaqScreen(onBackClick = {}) + } +} diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/FaqScreenScreenshotTestKt/faqScreenCompactScreenshot_c2abd7e6_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/FaqScreenScreenshotTestKt/faqScreenCompactScreenshot_c2abd7e6_0.png new file mode 100644 index 0000000000..7dc7f12f1a Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/FaqScreenScreenshotTestKt/faqScreenCompactScreenshot_c2abd7e6_0.png differ diff --git a/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/FaqScreenScreenshotTestKt/faqScreenScreenshot_748aa731_0.png b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/FaqScreenScreenshotTestKt/faqScreenScreenshot_748aa731_0.png new file mode 100644 index 0000000000..e5f0176034 Binary files /dev/null and b/feature/setting/presentation/src/screenshotTestDebug/reference/com/afternote/feature/setting/presentation/screen/FaqScreenScreenshotTestKt/faqScreenScreenshot_748aa731_0.png differ diff --git a/feature/setting/presentation/src/test/kotlin/com/afternote/feature/setting/presentation/FaqScreenTest.kt b/feature/setting/presentation/src/test/kotlin/com/afternote/feature/setting/presentation/FaqScreenTest.kt new file mode 100644 index 0000000000..495f0a4a36 --- /dev/null +++ b/feature/setting/presentation/src/test/kotlin/com/afternote/feature/setting/presentation/FaqScreenTest.kt @@ -0,0 +1,60 @@ +package com.afternote.feature.setting.presentation + +import androidx.compose.ui.test.assertCountEquals +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.v2.createComposeRule +import androidx.compose.ui.test.onAllNodesWithText +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import com.afternote.core.ui.theme.AfternoteTheme +import com.afternote.feature.setting.presentation.screen.FaqScreen +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.robolectric.annotation.GraphicsMode + +private const val QUESTION = "비밀번호를 잊어버렸어요." +private const val ANSWER = "로그인 화면의 '비밀번호 찾기'를 통해 재설정할 수 있습니다." + +@RunWith(RobolectricTestRunner::class) +@GraphicsMode(GraphicsMode.Mode.NATIVE) +@Config(sdk = [35]) +class FaqScreenTest { + @get:Rule + val composeRule = createComposeRule() + + @Test + fun answer_isHiddenUntilQuestionRowIsClicked_thenToggleHidesItAgain() { + composeRule.setContent { + AfternoteTheme { + FaqScreen(onBackClick = {}) + } + } + + composeRule.onAllNodesWithText(ANSWER).assertCountEquals(0) + + composeRule.onNodeWithText(QUESTION).performClick() + composeRule.onNodeWithText(ANSWER).assertIsDisplayed() + + composeRule.onNodeWithText(QUESTION).performClick() + composeRule.onAllNodesWithText(ANSWER).assertCountEquals(0) + } + + @Test + fun backClick_invokesOnBackClick() { + var backClicked = false + composeRule.setContent { + AfternoteTheme { + FaqScreen(onBackClick = { backClicked = true }) + } + } + + composeRule.onNodeWithContentDescription("뒤로가기").performClick() + + assertTrue(backClicked) + } +} diff --git a/feature/setting/presentation/src/test/kotlin/com/afternote/feature/setting/presentation/navigation/SettingLocalNavActionsTest.kt b/feature/setting/presentation/src/test/kotlin/com/afternote/feature/setting/presentation/navigation/SettingLocalNavActionsTest.kt index 47e35921b9..7ef3c250ef 100644 --- a/feature/setting/presentation/src/test/kotlin/com/afternote/feature/setting/presentation/navigation/SettingLocalNavActionsTest.kt +++ b/feature/setting/presentation/src/test/kotlin/com/afternote/feature/setting/presentation/navigation/SettingLocalNavActionsTest.kt @@ -103,6 +103,14 @@ class SettingLocalNavActionsTest { assertEquals(listOf(SettingRoute.SettingHomeRoute), stack.toList()) } + @Test + fun `faq returns to the screen that opened it`() { + actions.onNavigateToFaq() + assertEquals(SettingRoute.FaqRoute, stack.last()) + actions.popBack() + assertEquals(listOf(SettingRoute.SettingHomeRoute), stack.toList()) + } + @Test fun `all destination keys and route arguments survive serialization`() { val routes = @@ -126,6 +134,7 @@ class SettingLocalNavActionsTest { SettingRoute.InquiryListRoute, SettingRoute.InquiryDetailRoute(51L), SettingRoute.InquiryWriteRoute, + SettingRoute.FaqRoute, ) assertEquals(routes, Json.decodeFromString>(Json.encodeToString(routes))) }