Skip to content

Commit 9d1a183

Browse files
authored
Merge pull request #412 from teamterning/feat/#411
[FEAT/#411] ๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ / ์„œ๋ฒ„ ์ข…๋ฃŒ ๋ชจ๋‹ฌ ๊ตฌํ˜„
2 parents d2b3738 + 0860629 commit 9d1a183

File tree

12 files changed

+212
-12
lines changed

12 files changed

+212
-12
lines changed

โ€ŽREADME.mdโ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,12 @@
114114

115115
<br>
116116

117+
## ANDROID ARTICLE
118+
๐Ÿ”— [TERNING TISTORY](https://terning.tistory.com/category/Android)
117119

118120
## DESIGN SYSTEM
119121
๐Ÿ”— [TERNING DESIGN SYSTEM](https://teamterning.github.io/Terning-Android/index.html)
120122

121-
122123
## KANBAN BOARD
123124
๐Ÿ”— [TERNING PROJECT](https://github.com/orgs/teamterning/projects/1)
124125

โ€Žcore/local/src/main/java/com/terning/core/local/TerningDataStore.ktโ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ interface TerningDataStore {
77
var userId: Long
88
var alarmAvailable: Boolean
99
var hasRequestedPermission: Boolean
10+
var serverNoticeTimestamp: Long
1011
fun clearInfo()
11-
}
12+
}

โ€Žcore/local/src/main/java/com/terning/core/local/TerningDataStoreImpl.ktโ€Ž

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ class TerningDataStoreImpl @Inject constructor(
3131
get() = dataStore.getBoolean(PERMISSION_REQUESTED, false)
3232
set(value) = dataStore.edit { putBoolean(PERMISSION_REQUESTED, value) }
3333

34+
override var serverNoticeTimestamp: Long
35+
get() = dataStore.getLong(LAST_NOTICE_TIME, 0L)
36+
set(value) = dataStore.edit { putLong(LAST_NOTICE_TIME, value) }
37+
3438
override fun clearInfo() {
3539
dataStore.edit().clear().apply()
3640
}
@@ -42,5 +46,6 @@ class TerningDataStoreImpl @Inject constructor(
4246
private const val USER_ID = "USER_ID"
4347
private const val ALARM = "ALARM"
4448
private const val PERMISSION_REQUESTED = "PERMISSION_REQUESTED"
49+
private const val LAST_NOTICE_TIME = "LAST_NOTICE_TIME"
4550
}
46-
}
51+
}

โ€Ždata/user/src/main/java/com/terning/data/user/repositoryimpl/UserRepositoryImpl.ktโ€Ž

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,24 @@ class UserRepositoryImpl @Inject constructor(
4848
override fun clearInfo() {
4949
terningDataStore.clearInfo()
5050
}
51-
}
51+
52+
override fun hasNoticeCooldownPassed(): Boolean {
53+
val lastShownTimestamp = terningDataStore.serverNoticeTimestamp
54+
55+
if (lastShownTimestamp == 0L) return true
56+
57+
val currentTime = System.currentTimeMillis()
58+
59+
val elapsedTime = currentTime - lastShownTimestamp
60+
61+
return elapsedTime > THREE_HOURS_MS
62+
}
63+
64+
override fun setNoticeTimestampToNow() {
65+
terningDataStore.serverNoticeTimestamp = System.currentTimeMillis()
66+
}
67+
68+
companion object {
69+
private const val THREE_HOURS_MS = 3 * 60 * 60 * 1000L
70+
}
71+
}

โ€Ždomain/update/src/main/java/com/terning/domain/update/entity/UpdateState.ktโ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ sealed class UpdateState {
55
data object NoUpdateAvailable : UpdateState()
66
data class MajorUpdateAvailable(val title: String, val content: String) : UpdateState()
77
data class PatchUpdateAvailable(val title: String, val content: String) : UpdateState()
8-
}
8+
data object ServerNoticeAvailable: UpdateState()
9+
}

โ€Ždomain/user/src/main/java/com/terning/domain/user/repository/UserRepository.ktโ€Ž

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,7 @@ interface UserRepository {
2626
fun getPermissionRequested(): Boolean
2727

2828
fun clearInfo()
29+
30+
fun hasNoticeCooldownPassed(): Boolean
31+
fun setNoticeTimestampToNow()
2932
}

โ€Žfeature/splash/src/main/java/com/terning/feature/splash/SplashRoute.ktโ€Ž

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.terning.feature.splash
22

3+
import android.content.Context
4+
import androidx.browser.customtabs.CustomTabsIntent
35
import androidx.compose.animation.AnimatedVisibility
46
import androidx.compose.foundation.Image
57
import androidx.compose.foundation.background
@@ -15,6 +17,7 @@ import androidx.compose.ui.Modifier
1517
import androidx.compose.ui.platform.LocalContext
1618
import androidx.compose.ui.res.painterResource
1719
import androidx.compose.ui.tooling.preview.Preview
20+
import androidx.core.net.toUri
1821
import androidx.hilt.navigation.compose.hiltViewModel
1922
import androidx.lifecycle.Lifecycle
2023
import androidx.lifecycle.LifecycleEventObserver
@@ -29,9 +32,9 @@ import com.terning.core.designsystem.theme.TerningMain
2932
import com.terning.core.designsystem.theme.TerningPointTheme
3033
import com.terning.core.designsystem.theme.White
3134
import com.terning.core.designsystem.type.DeeplinkType
32-
import com.terning.feature.splash.SplashUiState
3335
import com.terning.feature.splash.component.TerningMajorUpdateDialog
3436
import com.terning.feature.splash.component.TerningPatchUpdateDialog
37+
import com.terning.feature.splash.component.TerningServerNoticeDialog
3538
import kotlinx.coroutines.launch
3639

3740
@Composable
@@ -106,7 +109,11 @@ internal fun SplashRoute(
106109
SplashScreen(
107110
splashUiState = updateState.toUi(),
108111
onUpdateButtonClick = context::launchPlayStore,
109-
onUpdateSkipButtonClick = viewModel::checkIfAccessTokenAvailable
112+
onUpdateSkipButtonClick = viewModel::checkIfAccessTokenAvailable,
113+
onDetailButtonClick = {
114+
navigateToServerWebView(context)
115+
viewModel.checkIfAccessTokenAvailable()
116+
}
110117
)
111118
}
112119

@@ -115,6 +122,7 @@ private fun SplashScreen(
115122
splashUiState: SplashUiState,
116123
onUpdateButtonClick: () -> Unit,
117124
onUpdateSkipButtonClick: () -> Unit,
125+
onDetailButtonClick: () -> Unit,
118126
) {
119127
when (splashUiState) {
120128
is SplashUiState.MajorUpdateAvailable -> {
@@ -138,6 +146,13 @@ private fun SplashScreen(
138146
}
139147
}
140148

149+
is SplashUiState.ServerNoticeAvailable -> {
150+
TerningServerNoticeDialog(
151+
onDismissButtonClick = onUpdateSkipButtonClick,
152+
onDetailButtonClick = onDetailButtonClick,
153+
)
154+
}
155+
141156
else -> {}
142157
}
143158

@@ -156,6 +171,13 @@ private fun SplashScreen(
156171
}
157172
}
158173

174+
private fun navigateToServerWebView(context: Context) {
175+
CustomTabsIntent.Builder().build().launchUrl(context, SERVER_URL.toUri())
176+
}
177+
178+
private const val SERVER_URL =
179+
"https://abundant-quiver-13f.notion.site/2a22867b52c180649a5bfdf1704820a3?pvs=73"
180+
159181
@Preview(showBackground = true)
160182
@Composable
161183
private fun SplashScreenPreview() {
@@ -164,6 +186,7 @@ private fun SplashScreenPreview() {
164186
splashUiState = SplashUiState.NoUpdateAvailable,
165187
onUpdateButtonClick = {},
166188
onUpdateSkipButtonClick = {},
189+
onDetailButtonClick = {},
167190
)
168191
}
169192
}

โ€Žfeature/splash/src/main/java/com/terning/feature/splash/SplashUiState.ktโ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ sealed class SplashUiState {
99
data object NoUpdateAvailable : SplashUiState()
1010
data class MajorUpdateAvailable(val title: String, val content: String) : SplashUiState()
1111
data class PatchUpdateAvailable(val title: String, val content: String) : SplashUiState()
12+
data object ServerNoticeAvailable : SplashUiState()
1213
}
1314

1415
fun UpdateState.toUi(): SplashUiState = when (this) {
1516
UpdateState.InitialState -> SplashUiState.InitialState
1617
UpdateState.NoUpdateAvailable -> SplashUiState.NoUpdateAvailable
1718
is UpdateState.MajorUpdateAvailable -> SplashUiState.MajorUpdateAvailable(title, content)
1819
is UpdateState.PatchUpdateAvailable -> SplashUiState.PatchUpdateAvailable(title, content)
20+
is UpdateState.ServerNoticeAvailable -> SplashUiState.ServerNoticeAvailable
1921
}

โ€Žfeature/splash/src/main/java/com/terning/feature/splash/SplashViewModel.ktโ€Ž

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import javax.inject.Inject
1818
@HiltViewModel
1919
class SplashViewModel @Inject constructor(
2020
private val userRepository: UserRepository,
21-
private val getLatestVersionUseCase: GetUpdateStateUseCase
21+
private val getLatestVersionUseCase: GetUpdateStateUseCase,
2222
) : ViewModel() {
2323

2424
private val _sideEffects = MutableSharedFlow<SplashSideEffect>()
@@ -47,6 +47,15 @@ class SplashViewModel @Inject constructor(
4747

4848
private fun checkIfUpdateNotAvailable(updateState: UpdateState) {
4949
if (updateState is UpdateState.NoUpdateAvailable) {
50+
checkServerNotice()
51+
}
52+
}
53+
54+
private fun checkServerNotice() = viewModelScope.launch {
55+
if (userRepository.hasNoticeCooldownPassed()) {
56+
_updateState.value = UpdateState.ServerNoticeAvailable
57+
userRepository.setNoticeTimestampToNow()
58+
} else {
5059
checkIfAccessTokenAvailable()
5160
}
5261
}
@@ -58,4 +67,4 @@ class SplashViewModel @Inject constructor(
5867
companion object {
5968
private const val DELAY_TIME = 500L
6069
}
61-
}
70+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package com.terning.feature.splash.component
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.Row
7+
import androidx.compose.foundation.layout.Spacer
8+
import androidx.compose.foundation.layout.height
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.foundation.shape.RoundedCornerShape
11+
import androidx.compose.material3.Text
12+
import androidx.compose.runtime.Composable
13+
import androidx.compose.ui.Alignment
14+
import androidx.compose.ui.Modifier
15+
import androidx.compose.ui.draw.clip
16+
import androidx.compose.ui.res.stringResource
17+
import androidx.compose.ui.text.style.TextAlign
18+
import androidx.compose.ui.text.style.TextOverflow
19+
import androidx.compose.ui.tooling.preview.Preview
20+
import androidx.compose.ui.unit.dp
21+
import androidx.compose.ui.window.Dialog
22+
import androidx.compose.ui.window.DialogProperties
23+
import com.terning.core.designsystem.component.dialog.NoticeDialogButton
24+
import com.terning.core.designsystem.theme.Back
25+
import com.terning.core.designsystem.theme.Black
26+
import com.terning.core.designsystem.theme.Grey150
27+
import com.terning.core.designsystem.theme.Grey200
28+
import com.terning.core.designsystem.theme.Grey350
29+
import com.terning.core.designsystem.theme.Grey400
30+
import com.terning.core.designsystem.theme.Grey500
31+
import com.terning.core.designsystem.theme.TerningMain
32+
import com.terning.core.designsystem.theme.TerningMain2
33+
import com.terning.core.designsystem.theme.TerningPointTheme
34+
import com.terning.core.designsystem.theme.TerningTheme
35+
import com.terning.core.designsystem.theme.White
36+
import com.terning.feature.splash.R
37+
38+
@Composable
39+
internal fun TerningServerNoticeDialog(
40+
onDismissButtonClick: () -> Unit,
41+
onDetailButtonClick: () -> Unit,
42+
) {
43+
Dialog(
44+
onDismissRequest = { },
45+
properties = DialogProperties(
46+
dismissOnBackPress = false,
47+
dismissOnClickOutside = false,
48+
usePlatformDefaultWidth = false,
49+
)
50+
) {
51+
Column(
52+
horizontalAlignment = Alignment.CenterHorizontally,
53+
modifier = Modifier
54+
.padding(horizontal = 28.dp)
55+
.background(color = White, shape = RoundedCornerShape(20.dp))
56+
.padding(12.dp)
57+
) {
58+
Text(
59+
text = stringResource(R.string.dialog_server_title),
60+
style = TerningTheme.typography.title2,
61+
color = Grey500,
62+
modifier = Modifier.padding(top = 20.dp)
63+
)
64+
Spacer(modifier = Modifier.height(20.dp))
65+
Text(
66+
text = stringResource(R.string.dialog_bodytitle),
67+
style = TerningTheme.typography.body4.copy(textAlign = TextAlign.Center),
68+
overflow = TextOverflow.Clip,
69+
color = Grey400,
70+
)
71+
Spacer(modifier = Modifier.height(26.dp))
72+
Column(
73+
modifier = Modifier
74+
.clip(RoundedCornerShape(5.dp))
75+
.background(Back)
76+
.padding(
77+
vertical = 18.dp,
78+
horizontal = 58.dp
79+
)
80+
) {
81+
Text(
82+
text = stringResource(R.string.dialog_server_over_title),
83+
style = TerningTheme.typography.body6,
84+
color = Black,
85+
)
86+
Spacer(modifier = Modifier.height(8.dp))
87+
Text(
88+
text = stringResource(R.string.dialog_server_over_day),
89+
style = TerningTheme.typography.detail4,
90+
color = Grey500
91+
)
92+
}
93+
Spacer(modifier = Modifier.height(26.dp))
94+
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
95+
NoticeDialogButton(
96+
text = stringResource(R.string.dialog_dismiss),
97+
contentColor = Grey350,
98+
pressedContainerColor = Grey200,
99+
containerColor = Grey150,
100+
onClick = onDismissButtonClick,
101+
modifier = Modifier.weight(1f)
102+
)
103+
NoticeDialogButton(
104+
text = stringResource(R.string.dialog_detail),
105+
contentColor = White,
106+
pressedContainerColor = TerningMain2,
107+
containerColor = TerningMain,
108+
onClick = onDetailButtonClick,
109+
modifier = Modifier.weight(1f)
110+
)
111+
}
112+
}
113+
}
114+
}
115+
116+
@Preview(showBackground = true, widthDp = 360, heightDp = 780)
117+
@Composable
118+
private fun TerningPatchUpdateDialogPreview() {
119+
TerningPointTheme {
120+
TerningServerNoticeDialog(
121+
onDismissButtonClick = {},
122+
onDetailButtonClick = {},
123+
)
124+
}
125+
}

0 commit comments

Comments
ย (0)