Skip to content

Commit 8a029a4

Browse files
authored
Merge pull request #33 from YAPP-Github/feature/NDGL-102/impl-app-settings
[NDGL-102] 앱 설정 기능 구현
2 parents 0412fea + 312d7f0 commit 8a029a4

File tree

18 files changed

+373
-12
lines changed

18 files changed

+373
-12
lines changed

data/auth/src/main/java/com/yapp/ndgl/data/auth/repository/AuthRepository.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class AuthRepository @Inject constructor(
2222
private val localAuthDataSource: LocalAuthDataSource,
2323
) {
2424
suspend fun initSession(): Boolean {
25-
val uuid = localAuthDataSource.getUuid()
25+
val uuid = getIdentifierCode()
2626
var isFirstUser = false
2727
val response = if (uuid.isNotEmpty()) {
2828
suspendRunCatching {
@@ -65,4 +65,6 @@ class AuthRepository @Inject constructor(
6565
throw IllegalStateException("Failed to get FCM token", e)
6666
}
6767
}
68+
69+
suspend fun getIdentifierCode(): String = localAuthDataSource.getUuid()
6870
}

feature/home/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ android {
77
}
88

99
dependencies {
10+
implementation(project(":data:auth"))
1011
implementation(project(":data:travel"))
1112
}

feature/home/src/main/java/com/yapp/ndgl/feature/home/main/HomeContract.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,15 @@ data class HomeState(
6464

6565
sealed interface HomeIntent : UiIntent {
6666
data object ClickSearchTravelTemplate : HomeIntent
67+
data object ClickSettings : HomeIntent
6768
data class SelectPopularTravelTab(val index: Int) : HomeIntent
6869
data class ClickTravel(val travelId: Long) : HomeIntent
6970
data object ClickTravelMore : HomeIntent
7071
}
7172

7273
sealed interface HomeSideEffect : UiSideEffect {
7374
data object NavigateToSearchTravelTemplate : HomeSideEffect
75+
data object NavigateToSettings : HomeSideEffect
7476
data class NavigateToFollowTravel(val travelId: Long, val days: Int) : HomeSideEffect
7577
data object NavigateToTravelMore : HomeSideEffect
7678
}

feature/home/src/main/java/com/yapp/ndgl/feature/home/main/HomeScreen.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import java.time.LocalDate
2929
internal fun HomeRoute(
3030
viewModel: HomeViewModel = hiltViewModel(),
3131
navigateToTemplateSearch: () -> Unit,
32+
navigateToSettings: () -> Unit,
3233
navigateToFollowTravel: (Long, Int) -> Unit,
3334
navigateToPopularTravelList: () -> Unit,
3435
) {
@@ -39,6 +40,9 @@ internal fun HomeRoute(
3940
onSearchClick = {
4041
viewModel.onIntent(HomeIntent.ClickSearchTravelTemplate)
4142
},
43+
onSettingsClick = {
44+
viewModel.onIntent(HomeIntent.ClickSettings)
45+
},
4246
onTabSelected = { index ->
4347
viewModel.onIntent(HomeIntent.SelectPopularTravelTab(index))
4448
},
@@ -53,6 +57,7 @@ internal fun HomeRoute(
5357
viewModel.collectSideEffect { sideEffect ->
5458
when (sideEffect) {
5559
HomeSideEffect.NavigateToSearchTravelTemplate -> navigateToTemplateSearch()
60+
HomeSideEffect.NavigateToSettings -> navigateToSettings()
5661
is HomeSideEffect.NavigateToFollowTravel -> navigateToFollowTravel(sideEffect.travelId, sideEffect.days)
5762
HomeSideEffect.NavigateToTravelMore -> navigateToPopularTravelList()
5863
}
@@ -63,6 +68,7 @@ internal fun HomeRoute(
6368
private fun HomeScreen(
6469
state: HomeState,
6570
onSearchClick: () -> Unit,
71+
onSettingsClick: () -> Unit,
6672
onTabSelected: (Int) -> Unit,
6773
onTravelClick: (Long) -> Unit,
6874
onTravelMoreClick: () -> Unit,
@@ -82,7 +88,7 @@ private fun HomeScreen(
8288
)
8389
NDGLNavigationIcon(
8490
icon = R.drawable.ic_28_settings,
85-
onClick = { /* FIXME: 설정 */ },
91+
onClick = onSettingsClick,
8692
)
8793
},
8894
)
@@ -204,6 +210,7 @@ private fun HomeScreenPreview() {
204210
allPopularTravels = sampleTravels,
205211
),
206212
onSearchClick = {},
213+
onSettingsClick = {},
207214
onTabSelected = {},
208215
onTravelClick = {},
209216
onTravelMoreClick = {},

feature/home/src/main/java/com/yapp/ndgl/feature/home/main/HomeViewModel.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,10 @@ class HomeViewModel @Inject constructor(
173173
override suspend fun handleIntent(intent: HomeIntent) {
174174
when (intent) {
175175
HomeIntent.ClickSearchTravelTemplate -> postNavigateToSearchTravelTemplate()
176-
176+
HomeIntent.ClickSettings -> postNavigateToSettings()
177177
is HomeIntent.SelectPopularTravelTab -> {
178178
reduce { copy(popularTravelSelectedTabIndex = intent.index) }
179179
}
180-
181180
is HomeIntent.ClickTravel -> postNavigateToTravelTemplate(travelId = intent.travelId)
182181
HomeIntent.ClickTravelMore -> postNavigateToTravelMore()
183182
}
@@ -187,6 +186,10 @@ class HomeViewModel @Inject constructor(
187186
postSideEffect(HomeSideEffect.NavigateToSearchTravelTemplate)
188187
}
189188

189+
private fun postNavigateToSettings() {
190+
postSideEffect(HomeSideEffect.NavigateToSettings)
191+
}
192+
190193
private fun postNavigateToTravelTemplate(travelId: Long) {
191194
postSideEffect(HomeSideEffect.NavigateToFollowTravel(travelId = travelId, days = 1))
192195
}

feature/home/src/main/java/com/yapp/ndgl/feature/home/navigation/HomeEntry.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.navigation3.runtime.NavKey
55
import com.yapp.ndgl.feature.home.main.HomeRoute
66
import com.yapp.ndgl.feature.home.popular.PopularTravelListRoute
77
import com.yapp.ndgl.feature.home.search.TemplateSearchRoute
8+
import com.yapp.ndgl.feature.home.settings.SettingsRoute
89
import com.yapp.ndgl.navigation.Navigator
910
import com.yapp.ndgl.navigation.Route
1011

@@ -16,6 +17,9 @@ fun EntryProviderScope<NavKey>.homeEntry(
1617
navigateToTemplateSearch = {
1718
navigator.navigate(Route.TemplateSearch)
1819
},
20+
navigateToSettings = {
21+
navigator.navigate(Route.Settings)
22+
},
1923
navigateToFollowTravel = { travelId, days ->
2024
navigator.navigate(Route.FollowTravel(travelId = travelId, days = days))
2125
},
@@ -47,4 +51,11 @@ fun EntryProviderScope<NavKey>.homeEntry(
4751
},
4852
)
4953
}
54+
entry<Route.Settings> {
55+
SettingsRoute(
56+
goBack = {
57+
navigator.goBack()
58+
},
59+
)
60+
}
5061
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.yapp.ndgl.feature.home.settings
2+
3+
import androidx.annotation.StringRes
4+
import androidx.compose.runtime.Immutable
5+
import androidx.compose.runtime.Stable
6+
import com.yapp.ndgl.core.base.UiIntent
7+
import com.yapp.ndgl.core.base.UiSideEffect
8+
import com.yapp.ndgl.core.base.UiState
9+
import com.yapp.ndgl.feature.home.R
10+
import com.yapp.ndgl.feature.home.settings.SettingsState.UrlMenu
11+
import kotlinx.collections.immutable.ImmutableList
12+
13+
@Immutable
14+
data class SettingsState(
15+
val menuItems: ImmutableList<SettingsMenu>,
16+
) : UiState {
17+
@Stable
18+
sealed interface SettingsMenu {
19+
@get:StringRes
20+
val labelRes: Int
21+
22+
data class OpenUrl(
23+
val menu: UrlMenu,
24+
) : SettingsMenu {
25+
override val labelRes = menu.labelRes
26+
}
27+
28+
data object CopyIdentifierCode : SettingsMenu {
29+
override val labelRes = R.string.home_settings_identification_code
30+
}
31+
32+
data class AppVersion(
33+
val versionName: String,
34+
) : SettingsMenu {
35+
override val labelRes = R.string.home_settings_version_info
36+
}
37+
}
38+
39+
enum class UrlMenu(
40+
@get:StringRes val labelRes: Int,
41+
val url: String,
42+
) {
43+
FAQ(
44+
labelRes = R.string.home_settings_faq,
45+
url = "https://repeated-tapir-33f.notion.site/FAQ-30ccbdc5a38380d6af4af7b7c412921e?source=copy_link",
46+
),
47+
RECOMMEND_LINK(
48+
labelRes = R.string.home_settings_recommend_link,
49+
url = "https://forms.gle/3q1uhQVeeKRrz11y5",
50+
),
51+
TERMS_OF_SERVICE(
52+
labelRes = R.string.home_settings_terms_of_service,
53+
url = "https://repeated-tapir-33f.notion.site/2c8cbdc5a3838070a8d8ccdcd0631c9a?source=copy_link",
54+
),
55+
PRIVACY_POLICY(
56+
labelRes = R.string.home_settings_privacy_policy,
57+
url = "https://repeated-tapir-33f.notion.site/30ccbdc5a38380e3a50ace64a9b0f398?source=copy_link",
58+
),
59+
}
60+
}
61+
62+
sealed interface SettingsIntent : UiIntent {
63+
data class ClickUrlMenu(val menu: UrlMenu) : SettingsIntent
64+
data object ClickCopyIdentifierCodeMenu : SettingsIntent
65+
}
66+
67+
sealed interface SettingsSideEffect : UiSideEffect {
68+
data class OpenUrl(val url: String) : SettingsSideEffect
69+
data class CopyIdentifierCode(val code: String) : SettingsSideEffect
70+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package com.yapp.ndgl.feature.home.settings
2+
3+
import android.content.ClipData
4+
import android.content.ClipboardManager
5+
import androidx.compose.foundation.background
6+
import androidx.compose.foundation.clickable
7+
import androidx.compose.foundation.layout.PaddingValues
8+
import androidx.compose.foundation.layout.Row
9+
import androidx.compose.foundation.layout.fillMaxSize
10+
import androidx.compose.foundation.layout.fillMaxWidth
11+
import androidx.compose.foundation.layout.padding
12+
import androidx.compose.foundation.layout.size
13+
import androidx.compose.foundation.layout.statusBarsPadding
14+
import androidx.compose.foundation.lazy.LazyColumn
15+
import androidx.compose.foundation.lazy.items
16+
import androidx.compose.material3.HorizontalDivider
17+
import androidx.compose.material3.Icon
18+
import androidx.compose.material3.Scaffold
19+
import androidx.compose.material3.Text
20+
import androidx.compose.runtime.Composable
21+
import androidx.compose.runtime.getValue
22+
import androidx.compose.ui.Alignment
23+
import androidx.compose.ui.Modifier
24+
import androidx.compose.ui.graphics.vector.ImageVector
25+
import androidx.compose.ui.platform.LocalContext
26+
import androidx.compose.ui.res.stringResource
27+
import androidx.compose.ui.res.vectorResource
28+
import androidx.compose.ui.unit.dp
29+
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
30+
import com.yapp.ndgl.core.ui.designsystem.NDGLNavigationBar
31+
import com.yapp.ndgl.core.ui.designsystem.NDGLNavigationBarAttr
32+
import com.yapp.ndgl.core.ui.theme.NDGLTheme
33+
import com.yapp.ndgl.core.ui.util.launchBrowser
34+
import com.yapp.ndgl.feature.home.R
35+
import com.yapp.ndgl.feature.home.settings.SettingsState.SettingsMenu
36+
import kotlinx.collections.immutable.ImmutableList
37+
import com.yapp.ndgl.core.ui.R as CoreR
38+
39+
@Composable
40+
internal fun SettingsRoute(
41+
viewModel: SettingsViewModel = hiltViewModel(),
42+
goBack: () -> Unit,
43+
) {
44+
val context = LocalContext.current
45+
46+
val state by viewModel.collectAsState()
47+
48+
SettingsScreen(
49+
menuItems = state.menuItems,
50+
goBack = goBack,
51+
onUrlItemClick = {
52+
viewModel.onIntent(SettingsIntent.ClickUrlMenu(it))
53+
},
54+
onCopyIdentifierCodeClick = {
55+
viewModel.onIntent(SettingsIntent.ClickCopyIdentifierCodeMenu)
56+
},
57+
)
58+
59+
viewModel.collectSideEffect { sideEffect ->
60+
when (sideEffect) {
61+
is SettingsSideEffect.OpenUrl -> context.launchBrowser(sideEffect.url)
62+
is SettingsSideEffect.CopyIdentifierCode -> {
63+
val clipboard = context.getSystemService(ClipboardManager::class.java)
64+
val clip = ClipData.newPlainText(
65+
context.getString(R.string.home_settings_identification_code),
66+
sideEffect.code,
67+
)
68+
clipboard?.setPrimaryClip(clip)
69+
}
70+
}
71+
}
72+
}
73+
74+
@Composable
75+
private fun SettingsScreen(
76+
menuItems: ImmutableList<SettingsMenu>,
77+
goBack: () -> Unit,
78+
onUrlItemClick: (SettingsState.UrlMenu) -> Unit,
79+
onCopyIdentifierCodeClick: () -> Unit,
80+
) {
81+
Scaffold(
82+
topBar = {
83+
NDGLNavigationBar(
84+
textAlignType = NDGLNavigationBarAttr.TextAlignType.CENTER,
85+
modifier = Modifier
86+
.fillMaxWidth()
87+
.background(color = NDGLTheme.colors.white)
88+
.statusBarsPadding(),
89+
headline = stringResource(R.string.home_settings_title),
90+
leadingIcon = CoreR.drawable.ic_28_chevron_left,
91+
onLeadingIconClick = goBack,
92+
)
93+
},
94+
) { innerPadding ->
95+
LazyColumn(
96+
modifier = Modifier
97+
.fillMaxSize()
98+
.padding(innerPadding),
99+
contentPadding = PaddingValues(24.dp),
100+
) {
101+
items(
102+
items = menuItems,
103+
key = { item -> item.labelRes },
104+
) { item ->
105+
when (item) {
106+
is SettingsMenu.OpenUrl -> SettingsMenuItem(
107+
text = stringResource(item.labelRes),
108+
onClick = { onUrlItemClick(item.menu) },
109+
)
110+
111+
SettingsMenu.CopyIdentifierCode -> SettingsMenuItem(
112+
text = stringResource(item.labelRes),
113+
onClick = onCopyIdentifierCodeClick,
114+
)
115+
116+
is SettingsMenu.AppVersion -> VersionItem(versionName = item.versionName)
117+
}
118+
}
119+
}
120+
}
121+
}
122+
123+
@Composable
124+
private fun SettingsMenuItem(
125+
text: String,
126+
onClick: () -> Unit,
127+
) {
128+
Row(
129+
modifier = Modifier
130+
.fillMaxWidth()
131+
.clickable(onClick = onClick)
132+
.padding(horizontal = 8.dp, vertical = 16.dp),
133+
verticalAlignment = Alignment.CenterVertically,
134+
) {
135+
Text(
136+
text = text,
137+
style = NDGLTheme.typography.bodyLgRegular,
138+
color = NDGLTheme.colors.black700,
139+
modifier = Modifier.weight(1f),
140+
)
141+
Icon(
142+
imageVector = ImageVector.vectorResource(CoreR.drawable.ic_24_chevron_right),
143+
contentDescription = null,
144+
modifier = Modifier.size(24.dp),
145+
tint = NDGLTheme.colors.black600,
146+
)
147+
}
148+
HorizontalDivider(color = NDGLTheme.colors.black50)
149+
}
150+
151+
@Composable
152+
private fun VersionItem(
153+
versionName: String,
154+
) {
155+
Row(
156+
modifier = Modifier
157+
.fillMaxWidth()
158+
.padding(horizontal = 8.dp, vertical = 16.dp),
159+
verticalAlignment = Alignment.CenterVertically,
160+
) {
161+
Text(
162+
text = stringResource(R.string.home_settings_version_info),
163+
style = NDGLTheme.typography.bodyLgRegular,
164+
color = NDGLTheme.colors.black700,
165+
modifier = Modifier.weight(1f),
166+
)
167+
Text(
168+
text = versionName,
169+
style = NDGLTheme.typography.bodyLgRegular,
170+
color = NDGLTheme.colors.black400,
171+
)
172+
}
173+
HorizontalDivider(color = NDGLTheme.colors.black50)
174+
}

0 commit comments

Comments
 (0)