diff --git a/core/design-system/src/main/java/com/twix/designsystem/components/dialog/MarketingDialog.kt b/core/design-system/src/main/java/com/twix/designsystem/components/dialog/MarketingDialog.kt new file mode 100644 index 00000000..e6e9e811 --- /dev/null +++ b/core/design-system/src/main/java/com/twix/designsystem/components/dialog/MarketingDialog.kt @@ -0,0 +1,148 @@ +package com.twix.designsystem.components.dialog + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +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.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.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.twix.designsystem.R +import com.twix.designsystem.components.dialog.CommonDialog +import com.twix.designsystem.components.text.AppText +import com.twix.designsystem.theme.GrayColor +import com.twix.designsystem.theme.TwixTheme +import com.twix.domain.model.enums.AppTextStyle +import com.twix.ui.extension.noRippleClickable + +@Composable +fun MarketingDialog( + visible: Boolean, + onConfirm: (Boolean, Boolean) -> Unit, +) { + var isMarketingChecked by remember { mutableStateOf(true) } + var isNightMarketingChecked by remember { mutableStateOf(true) } + + CommonDialog( + visible = visible, + confirmText = stringResource(R.string.word_confirm), + dismissText = null, + onDismissRequest = { }, + onConfirm = { + onConfirm(isMarketingChecked, isNightMarketingChecked) + }, + content = { + MarketingDialogContent( + isMarketingChecked = isMarketingChecked, + isNightMarketingChecked = isNightMarketingChecked, + onMarketingToggle = { + isMarketingChecked = !isMarketingChecked + }, + onNightMarketingToggle = { + isNightMarketingChecked = !isNightMarketingChecked + }, + ) + }, + ) +} + +@Composable +private fun MarketingDialogContent( + isMarketingChecked: Boolean, + isNightMarketingChecked: Boolean, + onMarketingToggle: () -> Unit, + onNightMarketingToggle: () -> Unit, +) { + Column { + AppText( + text = stringResource(R.string.marketing_dialog_title), + style = AppTextStyle.T1, + color = GrayColor.C500, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + + Spacer(Modifier.height(24.dp)) + + MarketingCheckItem( + text = stringResource(R.string.marketing_dialog_marketing), + checked = isMarketingChecked, + onClick = onMarketingToggle, + ) + + Spacer(Modifier.height(12.dp)) + + MarketingCheckItem( + text = stringResource(R.string.marketing_dialog_night_marketing), + checked = isNightMarketingChecked, + onClick = onNightMarketingToggle, + ) + + Spacer(Modifier.height(14.dp)) + + AppText( + text = stringResource(R.string.marketing_dialog_description), + style = AppTextStyle.C2, + color = GrayColor.C300, + modifier = Modifier.padding(start = 10.dp), + ) + } +} + +@Composable +private fun MarketingCheckItem( + text: String, + checked: Boolean, + onClick: () -> Unit, +) { + val icon = if (checked) R.drawable.ic_checked_you else R.drawable.ic_empty_check + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier + .fillMaxWidth() + .noRippleClickable(onClick = onClick), + ) { + Image( + painter = painterResource(icon), + contentDescription = null, + modifier = + Modifier + .size(24.dp), + ) + + Spacer(Modifier.width(8.dp)) + + AppText( + text = text, + style = AppTextStyle.B2, + color = GrayColor.C500, + ) + } +} + +@Preview(showBackground = true, showSystemUi = true) +@Composable +private fun MarketingDialogPreview() { + TwixTheme { + MarketingDialog( + visible = true, + onConfirm = { _, _ -> }, + ) + } +} diff --git a/core/design-system/src/main/res/drawable/ic_empty_check.xml b/core/design-system/src/main/res/drawable/ic_empty_check.xml new file mode 100644 index 00000000..536425ab --- /dev/null +++ b/core/design-system/src/main/res/drawable/ic_empty_check.xml @@ -0,0 +1,18 @@ + + + + diff --git a/core/design-system/src/main/res/values/strings.xml b/core/design-system/src/main/res/values/strings.xml index 3cabb0b8..f796c47b 100644 --- a/core/design-system/src/main/res/values/strings.xml +++ b/core/design-system/src/main/res/values/strings.xml @@ -17,6 +17,7 @@ 취소 삭제 수정 + 확인 저장 설정 계정 @@ -122,6 +123,12 @@ 정말 탈퇴하시겠어요? 커플 연결이 끊어집니다.\n데이터는 전부 삭제되며 복구가 불가능합니다. + + 도움이 되는 정보를\n알림으로 받아보시겠어요? + [선택] 마케팅 정보 알림 + [선택] 야간 마케팅 정보 알림 + * 언제든지 설정 > 알림 설정에서 변경 가능해요 + 업로드 이미지 캡처에 실패했습니다. 다시 시도해 주세요. diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/vm/OnBoardingViewModel.kt b/feature/onboarding/src/main/java/com/twix/onboarding/OnBoardingViewModel.kt similarity index 90% rename from feature/onboarding/src/main/java/com/twix/onboarding/vm/OnBoardingViewModel.kt rename to feature/onboarding/src/main/java/com/twix/onboarding/OnBoardingViewModel.kt index 68ab9e67..51f9543a 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/vm/OnBoardingViewModel.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/OnBoardingViewModel.kt @@ -1,4 +1,4 @@ -package com.twix.onboarding.vm +package com.twix.onboarding import androidx.lifecycle.viewModelScope import com.twix.domain.model.OnboardingStatus @@ -16,10 +16,6 @@ class OnBoardingViewModel( private val onBoardingRepository: OnBoardingRepository, private val notificationRepository: NotificationRepository, ) : BaseViewModel(OnBoardingUiState()) { - init { - initNotificationSettings() - } - fun fetchMyInviteCode() { launchResult( block = { onBoardingRepository.fetchInviteCode() }, @@ -43,6 +39,9 @@ class OnBoardingViewModel( // 디데이 설정 화면 is OnBoardingIntent.SelectDate -> reduceDday(intent.value) OnBoardingIntent.SubmitDday -> anniversarySetup() + + is OnBoardingIntent.SubmitMarketingConsent -> + initNotificationSettings(intent.isPushEnabled, intent.isMarketingEnabled, intent.isNightMarketingEnabled) } } @@ -145,9 +144,19 @@ class OnBoardingViewModel( ) } - private fun initNotificationSettings() { + private fun initNotificationSettings( + isPushEnabled: Boolean, + isMarketingEnabled: Boolean, + isNightMarketingEnabled: Boolean, + ) { launchResult( - block = { notificationRepository.initNotificationSettings(true, true, true) }, + block = { + notificationRepository.initNotificationSettings( + isPushEnabled, + isMarketingEnabled, + isNightMarketingEnabled, + ) + }, onSuccess = {}, ) } diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/couple/CoupleConnectRoute.kt b/feature/onboarding/src/main/java/com/twix/onboarding/couple/CoupleConnectRoute.kt index fda3dbbb..51a31c09 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/couple/CoupleConnectRoute.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/couple/CoupleConnectRoute.kt @@ -1,5 +1,9 @@ package com.twix.onboarding.couple +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -13,18 +17,23 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberUpdatedState 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.vector.ImageVector +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat import com.twix.designsystem.components.bottomsheet.CommonBottomSheet import com.twix.designsystem.components.bottomsheet.model.CommonBottomSheetConfig +import com.twix.designsystem.components.dialog.MarketingDialog import com.twix.designsystem.components.text.AppText import com.twix.designsystem.components.toast.ToastManager import com.twix.designsystem.components.toast.model.ToastData @@ -33,11 +42,12 @@ import com.twix.designsystem.theme.CommonColor import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle +import com.twix.onboarding.OnBoardingViewModel import com.twix.onboarding.R import com.twix.onboarding.couple.component.ConnectButton import com.twix.onboarding.couple.component.RestoreCoupleBottomSheetContent +import com.twix.onboarding.model.OnBoardingIntent import com.twix.onboarding.model.OnBoardingSideEffect -import com.twix.onboarding.vm.OnBoardingViewModel import com.twix.ui.base.ObserveAsEvents import com.twix.ui.extension.noRippleClickable import org.koin.compose.koinInject @@ -48,7 +58,10 @@ fun CoupleConnectRoute( toastManager: ToastManager = koinInject(), navigateToNext: () -> Unit, ) { + var showMarketingDialog by rememberSaveable { mutableStateOf(true) } var showRestoreSheet by rememberSaveable { mutableStateOf(false) } + val context = LocalContext.current + val currentContext by rememberUpdatedState(context) LaunchedEffect(Unit) { viewModel.fetchMyInviteCode() @@ -71,13 +84,30 @@ fun CoupleConnectRoute( } } - CoupleConnectScreen( - showRestoreSheet = showRestoreSheet, - onClickSend = { }, - onClickConnect = navigateToNext, - onClickRestore = { showRestoreSheet = true }, - onDismissSheet = { showRestoreSheet = false }, - ) + Box { + CoupleConnectScreen( + showRestoreSheet = showRestoreSheet, + onClickSend = { }, + onClickConnect = navigateToNext, + onClickRestore = { showRestoreSheet = true }, + onDismissSheet = { showRestoreSheet = false }, + ) + + MarketingDialog( + visible = showMarketingDialog, + onConfirm = { marketing, nightMarketing -> + showMarketingDialog = false + val isPushEnabled = isNotificationPermissionGranted(context) + viewModel.dispatch( + OnBoardingIntent.SubmitMarketingConsent( + isPushEnabled = isPushEnabled, + isMarketingEnabled = marketing, + isNightMarketingEnabled = nightMarketing, + ), + ) + }, + ) + } } @Composable @@ -142,9 +172,23 @@ fun CoupleConnectScreen( } } +private fun isNotificationPermissionGranted(context: Context): Boolean { + val notificationsEnabled = NotificationManagerCompat.from(context).areNotificationsEnabled() + if (!notificationsEnabled) return false + + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + ContextCompat.checkSelfPermission( + context, + Manifest.permission.POST_NOTIFICATIONS, + ) == PackageManager.PERMISSION_GRANTED + } else { + true + } +} + @Preview(showBackground = true) @Composable -fun CoupleConnectScreenPreview() { +private fun CoupleConnectScreenPreview() { TwixTheme { CoupleConnectScreen( showRestoreSheet = false, diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/dday/DdayRoute.kt b/feature/onboarding/src/main/java/com/twix/onboarding/dday/DdayRoute.kt index a15bffbd..02cb7871 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/dday/DdayRoute.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/dday/DdayRoute.kt @@ -30,12 +30,12 @@ import com.twix.designsystem.theme.CommonColor import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle +import com.twix.onboarding.OnBoardingViewModel import com.twix.onboarding.R import com.twix.onboarding.dday.component.DDayField import com.twix.onboarding.dday.component.DdayTopBar import com.twix.onboarding.model.OnBoardingIntent import com.twix.onboarding.model.OnBoardingSideEffect -import com.twix.onboarding.vm.OnBoardingViewModel import com.twix.ui.base.ObserveAsEvents import org.koin.compose.koinInject import java.time.LocalDate diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/di/OnBoardingModule.kt b/feature/onboarding/src/main/java/com/twix/onboarding/di/OnBoardingModule.kt index 71a3aca8..47473f21 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/di/OnBoardingModule.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/di/OnBoardingModule.kt @@ -2,8 +2,8 @@ package com.twix.onboarding.di import com.twix.navigation.NavRoutes import com.twix.navigation.base.NavGraphContributor +import com.twix.onboarding.OnBoardingViewModel import com.twix.onboarding.navigation.OnboardingNavGraph -import com.twix.onboarding.vm.OnBoardingViewModel import org.koin.core.module.dsl.viewModelOf import org.koin.core.qualifier.named import org.koin.dsl.module diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/invite/InviteCodeScreen.kt b/feature/onboarding/src/main/java/com/twix/onboarding/invite/InviteCodeScreen.kt index 343f7f8c..fd000099 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/invite/InviteCodeScreen.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/invite/InviteCodeScreen.kt @@ -47,11 +47,11 @@ import com.twix.designsystem.theme.CommonColor import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle +import com.twix.onboarding.OnBoardingViewModel import com.twix.onboarding.R import com.twix.onboarding.invite.component.InviteCodeTextField import com.twix.onboarding.model.OnBoardingIntent import com.twix.onboarding.model.OnBoardingSideEffect -import com.twix.onboarding.vm.OnBoardingViewModel import com.twix.ui.base.ObserveAsEvents import com.twix.ui.extension.noRippleClickable import com.twix.ui.keyboard.Keyboard diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/model/OnBoardingIntent.kt b/feature/onboarding/src/main/java/com/twix/onboarding/model/OnBoardingIntent.kt index 49cf5b7d..e7ae7655 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/model/OnBoardingIntent.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/model/OnBoardingIntent.kt @@ -4,6 +4,12 @@ import com.twix.ui.base.Intent import java.time.LocalDate sealed interface OnBoardingIntent : Intent { + data class SubmitMarketingConsent( + val isPushEnabled: Boolean, + val isMarketingEnabled: Boolean, + val isNightMarketingEnabled: Boolean, + ) : OnBoardingIntent + data class WriteNickName( val value: String, ) : OnBoardingIntent diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/navigation/OnboardingNavGraph.kt b/feature/onboarding/src/main/java/com/twix/onboarding/navigation/OnboardingNavGraph.kt index 247b3b96..1408fb1f 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/navigation/OnboardingNavGraph.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/navigation/OnboardingNavGraph.kt @@ -7,11 +7,11 @@ import androidx.navigation.navigation import com.twix.navigation.NavRoutes import com.twix.navigation.base.NavGraphContributor import com.twix.navigation.graphViewModel +import com.twix.onboarding.OnBoardingViewModel import com.twix.onboarding.couple.CoupleConnectRoute import com.twix.onboarding.dday.DdayRoute import com.twix.onboarding.invite.InviteCodeRoute import com.twix.onboarding.profile.ProfileRoute -import com.twix.onboarding.vm.OnBoardingViewModel object OnboardingNavGraph : NavGraphContributor { override val graphRoute: NavRoutes diff --git a/feature/onboarding/src/main/java/com/twix/onboarding/profile/ProfileScreen.kt b/feature/onboarding/src/main/java/com/twix/onboarding/profile/ProfileScreen.kt index 639ce981..ff6c037f 100644 --- a/feature/onboarding/src/main/java/com/twix/onboarding/profile/ProfileScreen.kt +++ b/feature/onboarding/src/main/java/com/twix/onboarding/profile/ProfileScreen.kt @@ -37,10 +37,10 @@ import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.SystemColor import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle +import com.twix.onboarding.OnBoardingViewModel import com.twix.onboarding.R import com.twix.onboarding.model.OnBoardingIntent import com.twix.onboarding.model.OnBoardingSideEffect -import com.twix.onboarding.vm.OnBoardingViewModel import com.twix.ui.base.ObserveAsEvents import com.twix.ui.extension.noRippleClickable import org.koin.compose.koinInject