diff --git a/app/src/main/java/com/wafflestudio/snutt2/RemoteConfig.kt b/app/src/main/java/com/wafflestudio/snutt2/RemoteConfig.kt index 546e8cb6c..3982a2dbb 100644 --- a/app/src/main/java/com/wafflestudio/snutt2/RemoteConfig.kt +++ b/app/src/main/java/com/wafflestudio/snutt2/RemoteConfig.kt @@ -60,4 +60,6 @@ class RemoteConfig @Inject constructor( // NOTE: 평상시에는 필드가 null로 내려오고, 이는 로직상 false 취급이다. 지도를 급히 비활성화해야 할 경우 true가 내려온다. // https://wafflestudio.slack.com/archives/C0PAVPS5T/p1706542084934709?thread_ts=1706451688.745159&cid=C0PAVPS5T get() = config.map { it.disableMapFeature ?: false } + val noticeConfig: Flow + get() = config.map { it.noticeConfig ?: RemoteConfigDto.NoticeConfig(false, null, null) } } diff --git a/app/src/main/java/com/wafflestudio/snutt2/lib/network/dto/core/RemoteConfigDto.kt b/app/src/main/java/com/wafflestudio/snutt2/lib/network/dto/core/RemoteConfigDto.kt index b7dbf271e..eb8415b96 100644 --- a/app/src/main/java/com/wafflestudio/snutt2/lib/network/dto/core/RemoteConfigDto.kt +++ b/app/src/main/java/com/wafflestudio/snutt2/lib/network/dto/core/RemoteConfigDto.kt @@ -11,6 +11,7 @@ data class RemoteConfigDto( @Json(name = "vacancySugangSnuUrl") val vacancyUrlConfig: VacancyUrlConfig = VacancyUrlConfig(), @Json(name = "settingsBadge") val settingsBadgeConfig: SettingsBadgeConfig = SettingsBadgeConfig(), @Json(name = "disableMapFeature") val disableMapFeature: Boolean? = null, + @Json(name = "notice") val noticeConfig: NoticeConfig? = null, ) { data class ReactNativeBundleSrc( @Json(name = "src") val src: Map, @@ -27,6 +28,12 @@ data class RemoteConfigDto( data class VacancyUrlConfig( @Json(name = "url") val url: String? = null, ) + + data class NoticeConfig( + @Json(name = "visible") val visible: Boolean = false, + @Json(name = "title") val title: String? = null, + @Json(name = "content") val content: String? = null, + ) } fun RemoteConfigDtoNetwork.toExternalModel() = RemoteConfigDto( @@ -35,6 +42,7 @@ fun RemoteConfigDtoNetwork.toExternalModel() = RemoteConfigDto( vacancyUrlConfig = this.vacancyUrlConfig.toExternalModel(), settingsBadgeConfig = this.settingsBadgeConfig.toExternalModel(), disableMapFeature = this.disableMapFeature, + noticeConfig = this.noticeConfig?.toExternalModel(), ) fun RemoteConfigDtoNetwork.ReactNativeBundleSrc.toExternalModel() = RemoteConfigDto.ReactNativeBundleSrc( @@ -52,3 +60,9 @@ fun RemoteConfigDtoNetwork.VacancyBannerConfig.toExternalModel() = RemoteConfigD fun RemoteConfigDtoNetwork.VacancyUrlConfig.toExternalModel() = RemoteConfigDto.VacancyUrlConfig( url = this.url, ) + +fun RemoteConfigDtoNetwork.NoticeConfig.toExternalModel() = RemoteConfigDto.NoticeConfig( + visible = visible ?: false, + title = title, + content = content, +) diff --git a/app/src/main/java/com/wafflestudio/snutt2/model/ImportantNotice.kt b/app/src/main/java/com/wafflestudio/snutt2/model/ImportantNotice.kt new file mode 100644 index 000000000..c141426ff --- /dev/null +++ b/app/src/main/java/com/wafflestudio/snutt2/model/ImportantNotice.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.snutt2.model + +data class ImportantNotice( + val title: String?, + val content: String?, +) diff --git a/app/src/main/java/com/wafflestudio/snutt2/views/NavigationDestination.kt b/app/src/main/java/com/wafflestudio/snutt2/views/NavigationDestination.kt index 9ce1a6864..7aa9eeea1 100644 --- a/app/src/main/java/com/wafflestudio/snutt2/views/NavigationDestination.kt +++ b/app/src/main/java/com/wafflestudio/snutt2/views/NavigationDestination.kt @@ -31,4 +31,5 @@ object NavigationDestination { const val ThemeConfig = "theme_config" const val ThemeDetail = "theme_detail" const val SocialLink = "social_link" + const val ImportantNotice = "important_notice" } diff --git a/app/src/main/java/com/wafflestudio/snutt2/views/RootActivity.kt b/app/src/main/java/com/wafflestudio/snutt2/views/RootActivity.kt index b1a126cb2..3d3964f92 100644 --- a/app/src/main/java/com/wafflestudio/snutt2/views/RootActivity.kt +++ b/app/src/main/java/com/wafflestudio/snutt2/views/RootActivity.kt @@ -232,6 +232,14 @@ class RootActivity : AppCompatActivity() { LocalRemoteConfig provides remoteConfig, LocalNavBottomSheetState provides navBottomSheetState, ) { + LaunchedEffect(Unit) { + remoteConfig.noticeConfig.collect { + if (it.visible) { + navController.navigateAsOrigin(NavigationDestination.ImportantNotice) + } + } + } + InstallInAppDeeplinkExecutor() ModalBottomSheetLayout( bottomSheetNavigator = bottomSheetNavigator, @@ -247,6 +255,8 @@ class RootActivity : AppCompatActivity() { composableRoot(NavigationDestination.Home) { HomePage() } + composable2(NavigationDestination.ImportantNotice) { ImportantNoticePage() } + composable2(NavigationDestination.Notification) { NotificationPage() } composable2(NavigationDestination.LecturesOfTable) { LecturesOfTablePage() } diff --git a/app/src/main/java/com/wafflestudio/snutt2/views/logged_out/ImportantNoticePage.kt b/app/src/main/java/com/wafflestudio/snutt2/views/logged_out/ImportantNoticePage.kt new file mode 100644 index 000000000..28aba6777 --- /dev/null +++ b/app/src/main/java/com/wafflestudio/snutt2/views/logged_out/ImportantNoticePage.kt @@ -0,0 +1,101 @@ +package com.wafflestudio.snutt2.views.logged_out + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.wafflestudio.snutt2.R +import com.wafflestudio.snutt2.components.compose.clicks +import com.wafflestudio.snutt2.lib.network.dto.core.RemoteConfigDto +import com.wafflestudio.snutt2.ui.SNUTTColors +import com.wafflestudio.snutt2.ui.SNUTTTypography +import com.wafflestudio.snutt2.views.LocalNavController +import com.wafflestudio.snutt2.views.LocalRemoteConfig +import com.wafflestudio.snutt2.views.NavigationDestination + +@Composable +fun ImportantNoticePage() { + val navController = LocalNavController.current + val remoteConfig = LocalRemoteConfig.current + val noticeConfig by remoteConfig.noticeConfig.collectAsState(RemoteConfigDto.NoticeConfig()) + + Column( + modifier = Modifier + .fillMaxSize() + .padding(vertical = 12.dp, horizontal = 48.dp) + .background(SNUTTColors.White900), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Image( + painter = painterResource(id = R.drawable.ic_cat_retry), + contentDescription = stringResource(R.string.sign_in_logo_title), + modifier = Modifier.width(55.dp), + ) + + Spacer(modifier = Modifier.height(32.dp)) + + Text( + text = noticeConfig.title ?: "", + style = SNUTTTypography.h3.copy( + fontSize = 17.sp, + ), + color = SNUTTColors.Black900, + textAlign = TextAlign.Center, + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = noticeConfig.content ?: "", + style = SNUTTTypography.body1, + textAlign = TextAlign.Center, + ) + + Spacer(modifier = Modifier.height(20.dp)) + + Surface( + modifier = Modifier + .size(121.dp, 33.dp) + .clicks { + navController.navigate(NavigationDestination.AppReport) + }, + shape = RoundedCornerShape(18.dp), + color = SNUTTColors.SNUTTVacancy, + ) { + Box( + contentAlignment = Alignment.Center, + ) { + Text( + text = stringResource(R.string.send_report), + style = SNUTTTypography.h4.copy( + color = SNUTTColors.AllWhite, + fontWeight = FontWeight.SemiBold, + ), + maxLines = 1, + ) + } + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e9b839f8a..8754b1fe8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,6 +3,8 @@ privacy_policy member + 문의사항 보내기 + 회원가입을 누르시면 일반 이용 약관 @@ -401,7 +403,7 @@ 이메일 내용 피드백 전송 - SNUTT 최고예요 + 불편한 점이나 버그를 제보해주세요.\n더 나은 SNUTT를 위한 아이디어도 환영해요. 개인정보처리방침 빈자리 알림 diff --git a/core/network/src/main/java/com/wafflestudio/snutt2/core/network/model/RemoteConfigDto.kt b/core/network/src/main/java/com/wafflestudio/snutt2/core/network/model/RemoteConfigDto.kt index dc2a083c6..b81ce9f34 100644 --- a/core/network/src/main/java/com/wafflestudio/snutt2/core/network/model/RemoteConfigDto.kt +++ b/core/network/src/main/java/com/wafflestudio/snutt2/core/network/model/RemoteConfigDto.kt @@ -10,6 +10,7 @@ data class RemoteConfigDto( @Json(name = "vacancySugangSnuUrl") val vacancyUrlConfig: VacancyUrlConfig = VacancyUrlConfig(), @Json(name = "settingsBadge") val settingsBadgeConfig: SettingsBadgeConfig = SettingsBadgeConfig(), @Json(name = "disableMapFeature") val disableMapFeature: Boolean? = null, + @Json(name = "notice") val noticeConfig: NoticeConfig? = null, ) { data class ReactNativeBundleSrc( @Json(name = "src") val src: Map, @@ -26,4 +27,10 @@ data class RemoteConfigDto( data class VacancyUrlConfig( @Json(name = "url") val url: String? = null, ) + + data class NoticeConfig( + @Json(name = "visible") val visible: Boolean? = false, + @Json(name = "title") val title: String? = null, + @Json(name = "content") val content: String? = null, + ) }