From 539ec97603d6952ded94eded87b22e40b5608a2d Mon Sep 17 00:00:00 2001 From: Junhyeok Date: Mon, 27 Jan 2025 01:27:06 +0900 Subject: [PATCH] =?UTF-8?q?Notification=20=EA=B8=B0=EB=B0=98=20=EC=9E=A1?= =?UTF-8?q?=EA=B8=B0=20(#20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore : notification core module base * chore : add POST_NOTIFICATIONS permission * chore : add notification dependency to main * feat: Implement base for notification settings update * feat : add temporary wrapping structure for dialog * feat : Implement dashboard permission request * feat : add NotificationUtil * chore : add comments * refactor : lint * refactor : NotificationUtil * fix : update initial notification permission logic * feat : Add default values and parameterization to DialogProperties * refactor : Rename GoalNotificationPermissionEffect to GoalNotificationPermission * refactor : Rename checkSystemNotificationEnabled -> areNotificationsEnabled * refactor : Rename setGoalNotificationChecked to setGoalNotificationEnabled * Setting 기능 추가 (#21) * feat : Implement base for feature:setting * feat : Connect dashboard and settings navigation * feat : add temporary wrapping structure for Switch * feat : add openPlayStore Util * feat : add SettingTopAppBar component * feat : add SettingRow component * feat : Implement settings feature requirements * refactor : lint * refactor : Rename onSettingClick to navigateToSetting * refactor : Encapsulate settings UI elements within SettingBody * refactor : Rename isSystemNotificationEnabled to updatedSystemNotificationEnabled * refactor : Remove Box and use modifiers for layout * refactor : Rename isGoalNotificationChecked to isGoalNotificationEnabled --- app/build.gradle.kts | 1 + .../dobedobe/navigation/DobeDobeNavHost.kt | 39 ++--- .../core/data/repository/UserRepository.kt | 4 + .../data/repository/UserRepositoryImpl.kt | 12 ++ .../dobedobe/data/notification_setting.proto | 9 ++ .../dobedobe/data/user_preferences.proto | 3 + .../datastore/UserPreferencesDataSource.kt | 30 ++++ .../core/designsystem/component/Dialog.kt | 85 +++++++++++ .../core/designsystem/component/Switch.kt | 47 ++++++ .../dobedobe/core/model/UserData.kt | 2 + core/notifications/.gitignore | 1 + core/notifications/build.gradle.kts | 15 ++ .../notifications/ExampleInstrumentedTest.kt | 22 +++ .../src/main/AndroidManifest.xml | 4 + .../core/notifications/NotificationUtil.kt | 38 +++++ .../core/notifications/ExampleUnitTest.kt | 16 ++ feature/dashboard/build.gradle.kts | 2 + .../feature/dashboard/DashboardScreen.kt | 84 +++++++++- .../feature/dashboard/DashboardUiState.kt | 1 + .../feature/dashboard/DashboardViewModel.kt | 62 +++++--- .../dashboard/component/DashboardTopAppBar.kt | 6 +- .../navigation/DashboardNavigation.kt | 2 + feature/setting/build.gradle.kts | 1 + .../dobedobe/feature/setting/SettingScreen.kt | 144 ++++++++++++++++++ .../feature/setting/SettingViewModel.kt | 24 ++- .../feature/setting/component/SettingRow.kt | 76 +++++++++ .../setting/component/SettingTopAppBar.kt | 47 ++++++ .../setting/navigation/SettingNavigation.kt | 27 ++++ .../feature/setting/util/SettingUtil.kt | 15 ++ gradle/libs.versions.toml | 2 + settings.gradle.kts | 1 + 31 files changed, 777 insertions(+), 45 deletions(-) create mode 100644 core/datastore-proto/src/main/proto/com/chipichipi/dobedobe/data/notification_setting.proto create mode 100644 core/designsystem/src/main/kotlin/com/chipichipi/dobedobe/core/designsystem/component/Dialog.kt create mode 100644 core/designsystem/src/main/kotlin/com/chipichipi/dobedobe/core/designsystem/component/Switch.kt create mode 100644 core/notifications/.gitignore create mode 100644 core/notifications/build.gradle.kts create mode 100644 core/notifications/src/androidTest/kotlin/com/chipichipi/dobedobe/core/notifications/ExampleInstrumentedTest.kt create mode 100644 core/notifications/src/main/AndroidManifest.xml create mode 100644 core/notifications/src/main/kotlin/com/chipichipi/dobedobe/core/notifications/NotificationUtil.kt create mode 100644 core/notifications/src/test/kotlin/com/chipichipi/dobedobe/core/notifications/ExampleUnitTest.kt create mode 100644 feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/SettingScreen.kt create mode 100644 feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/component/SettingRow.kt create mode 100644 feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/component/SettingTopAppBar.kt create mode 100644 feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/navigation/SettingNavigation.kt create mode 100644 feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/util/SettingUtil.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 53b75c8e..da07b077 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -37,6 +37,7 @@ dependencies { implementation(projects.core.data) implementation(projects.core.designsystem) implementation(projects.core.model) + implementation(projects.core.notifications) implementation(libs.androidx.core.ktx) implementation(libs.androidx.activity.compose) diff --git a/app/src/main/kotlin/com/chipichipi/dobedobe/navigation/DobeDobeNavHost.kt b/app/src/main/kotlin/com/chipichipi/dobedobe/navigation/DobeDobeNavHost.kt index 56bf36c7..ee093c2c 100644 --- a/app/src/main/kotlin/com/chipichipi/dobedobe/navigation/DobeDobeNavHost.kt +++ b/app/src/main/kotlin/com/chipichipi/dobedobe/navigation/DobeDobeNavHost.kt @@ -1,42 +1,35 @@ package com.chipichipi.dobedobe.navigation -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides -import androidx.compose.foundation.layout.only -import androidx.compose.foundation.layout.safeDrawing -import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.compose.NavHost import com.chipichipi.dobedobe.feature.dashboard.navigation.DashboardRoute import com.chipichipi.dobedobe.feature.dashboard.navigation.dashboardScreen +import com.chipichipi.dobedobe.feature.setting.navigation.navigateToSetting +import com.chipichipi.dobedobe.feature.setting.navigation.settingScreen import com.chipichipi.dobedobe.ui.DobeDobeAppState @Composable -fun DobeDobeNavHost( +internal fun DobeDobeNavHost( appState: DobeDobeAppState, onShowSnackbar: suspend (String, String?) -> Boolean, modifier: Modifier = Modifier, ) { val navController = appState.navController - Box( - modifier = - Modifier.windowInsetsPadding( - WindowInsets.safeDrawing.only( - WindowInsetsSides.Top, - ), - ), + NavHost( + navController = navController, + startDestination = DashboardRoute, + modifier = modifier, ) { - NavHost( - navController = navController, - startDestination = DashboardRoute, - modifier = modifier, - ) { - dashboardScreen( - onShowSnackbar = onShowSnackbar, - ) - } + dashboardScreen( + onShowSnackbar = onShowSnackbar, + navigateToSetting = navController::navigateToSetting, + ) + + settingScreen( + onShowSnackbar = onShowSnackbar, + navigateToBack = navController::popBackStack, + ) } } diff --git a/core/data/src/main/kotlin/com/chipichipi/dobedobe/core/data/repository/UserRepository.kt b/core/data/src/main/kotlin/com/chipichipi/dobedobe/core/data/repository/UserRepository.kt index de2075bd..1051c082 100644 --- a/core/data/src/main/kotlin/com/chipichipi/dobedobe/core/data/repository/UserRepository.kt +++ b/core/data/src/main/kotlin/com/chipichipi/dobedobe/core/data/repository/UserRepository.kt @@ -7,4 +7,8 @@ interface UserRepository { val userData: Flow suspend fun completeOnBoarding(): Result + + suspend fun setGoalNotificationEnabled(enabled: Boolean): Result + + suspend fun disableSystemNotificationDialog(): Result } diff --git a/core/data/src/main/kotlin/com/chipichipi/dobedobe/core/data/repository/UserRepositoryImpl.kt b/core/data/src/main/kotlin/com/chipichipi/dobedobe/core/data/repository/UserRepositoryImpl.kt index 5f638079..3a982bcc 100644 --- a/core/data/src/main/kotlin/com/chipichipi/dobedobe/core/data/repository/UserRepositoryImpl.kt +++ b/core/data/src/main/kotlin/com/chipichipi/dobedobe/core/data/repository/UserRepositoryImpl.kt @@ -15,4 +15,16 @@ internal class UserRepositoryImpl( userPreferencesDataSource.completeOnBoarding() } } + + override suspend fun setGoalNotificationEnabled(enabled: Boolean): Result { + return runCatching { + userPreferencesDataSource.setGoalNotificationEnabled(enabled) + } + } + + override suspend fun disableSystemNotificationDialog(): Result { + return runCatching { + userPreferencesDataSource.disableSystemNotificationDialog() + } + } } diff --git a/core/datastore-proto/src/main/proto/com/chipichipi/dobedobe/data/notification_setting.proto b/core/datastore-proto/src/main/proto/com/chipichipi/dobedobe/data/notification_setting.proto new file mode 100644 index 00000000..c929a1d7 --- /dev/null +++ b/core/datastore-proto/src/main/proto/com/chipichipi/dobedobe/data/notification_setting.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "com.chipichipi.dobedobe.core.datastore"; +option java_multiple_files = true; + +message NotificationSetting { + bool is_goal_notification_enabled = 1; + bool is_system_notification_dialog_disabled = 2; +} diff --git a/core/datastore-proto/src/main/proto/com/chipichipi/dobedobe/data/user_preferences.proto b/core/datastore-proto/src/main/proto/com/chipichipi/dobedobe/data/user_preferences.proto index 8ea2ca72..6b04dfea 100644 --- a/core/datastore-proto/src/main/proto/com/chipichipi/dobedobe/data/user_preferences.proto +++ b/core/datastore-proto/src/main/proto/com/chipichipi/dobedobe/data/user_preferences.proto @@ -1,8 +1,11 @@ syntax = "proto3"; +import "com/chipichipi/dobedobe/data/notification_setting.proto"; + option java_package = "com.chipichipi.dobedobe.core.datastore"; option java_multiple_files = true; message UserPreferences { bool is_onboarding_completed = 1; + NotificationSetting notification_setting = 2; } diff --git a/core/datastore/src/main/kotlin/com/chipichipi/dobedobe/core/datastore/UserPreferencesDataSource.kt b/core/datastore/src/main/kotlin/com/chipichipi/dobedobe/core/datastore/UserPreferencesDataSource.kt index 584f0e05..ab048928 100644 --- a/core/datastore/src/main/kotlin/com/chipichipi/dobedobe/core/datastore/UserPreferencesDataSource.kt +++ b/core/datastore/src/main/kotlin/com/chipichipi/dobedobe/core/datastore/UserPreferencesDataSource.kt @@ -27,9 +27,39 @@ class UserPreferencesDataSource( Log.e("UserPreferences", "Failed to update preferences", ioException) } } + + suspend fun setGoalNotificationEnabled(enabled: Boolean) { + try { + preferences.updateData { + it.copy { + notificationSetting = notificationSetting.copy { + isGoalNotificationEnabled = enabled + } + } + } + } catch (ioException: IOException) { + Log.e("UserPreferences", "Failed to update preferences", ioException) + } + } + + suspend fun disableSystemNotificationDialog() { + try { + preferences.updateData { + it.copy { + notificationSetting = notificationSetting.copy { + isSystemNotificationDialogDisabled = true + } + } + } + } catch (ioException: IOException) { + Log.e("UserPreferences", "Failed to update preferences", ioException) + } + } } private fun UserPreferences.toModel() = UserData( isOnboardingCompleted = isOnboardingCompleted, + isGoalNotificationEnabled = notificationSetting.isGoalNotificationEnabled, + isSystemNotificationDialogDisabled = notificationSetting.isSystemNotificationDialogDisabled, ) diff --git a/core/designsystem/src/main/kotlin/com/chipichipi/dobedobe/core/designsystem/component/Dialog.kt b/core/designsystem/src/main/kotlin/com/chipichipi/dobedobe/core/designsystem/component/Dialog.kt new file mode 100644 index 00000000..b23e58f1 --- /dev/null +++ b/core/designsystem/src/main/kotlin/com/chipichipi/dobedobe/core/designsystem/component/Dialog.kt @@ -0,0 +1,85 @@ +package com.chipichipi.dobedobe.core.designsystem.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import com.chipichipi.dobedobe.core.designsystem.theme.DobeDobeTheme + +/** + * TODO : Dialog 컴포넌트 단순 Wrapper 임시 처리, 각 상태 디자인 정의 필요 + */ +@Composable +fun DobeDobeDialog( + onDismissRequest: () -> Unit, + title: String, + modifier: Modifier = Modifier, + properties: DialogProperties = DialogProperties( + usePlatformDefaultWidth = false, + ), + content: @Composable () -> Unit, +) { + Dialog( + onDismissRequest = onDismissRequest, + properties = properties, + ) { + Surface( + modifier = modifier + .width(253.dp), + shape = RoundedCornerShape(16.dp), + color = Color.White, + ) { + Column( + modifier = Modifier.padding( + vertical = 24.dp, + horizontal = 15.dp, + ), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = title, + fontSize = 17.sp, + fontWeight = FontWeight.SemiBold, + color = Color.Black, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + content() + } + } + } +} + +@ThemePreviews +@Composable +private fun DobeDobeDialogPreview() { + DobeDobeTheme { + DobeDobeDialog( + onDismissRequest = {}, + title = "TEST", + ) { + Button( + onClick = {}, + ) { + Text(text = "TEST") + } + } + } +} diff --git a/core/designsystem/src/main/kotlin/com/chipichipi/dobedobe/core/designsystem/component/Switch.kt b/core/designsystem/src/main/kotlin/com/chipichipi/dobedobe/core/designsystem/component/Switch.kt new file mode 100644 index 00000000..fc64967e --- /dev/null +++ b/core/designsystem/src/main/kotlin/com/chipichipi/dobedobe/core/designsystem/component/Switch.kt @@ -0,0 +1,47 @@ +package com.chipichipi.dobedobe.core.designsystem.component + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.material3.Switch +import androidx.compose.material3.SwitchDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import com.chipichipi.dobedobe.core.designsystem.theme.DobeDobeTheme + +/** + * TODO : Switch 컴포넌트 단순 Wrapper 임시 처리, 각 상태 디자인 정의 필요 + */ +@Composable +fun DobeDobeSwitch( + checked: Boolean, + onCheckedChange: ((Boolean) -> Unit)?, + modifier: Modifier = Modifier, + enabled: Boolean = true, + interactionSource: MutableInteractionSource? = null, +) { + // TODO : 디자인시스템 나오면 컬러 정의 필요 + Switch( + modifier = modifier, + checked = checked, + onCheckedChange = onCheckedChange, + enabled = enabled, + interactionSource = interactionSource, + colors = SwitchDefaults.colors( + checkedThumbColor = Color.White, + checkedTrackColor = Color.Red, + uncheckedThumbColor = Color.Gray, + uncheckedTrackColor = Color.Transparent, + ), + ) +} + +@ThemePreviews +@Composable +private fun DobeDobeSwitchPreview() { + DobeDobeTheme { + DobeDobeSwitch( + checked = true, + onCheckedChange = {}, + ) + } +} diff --git a/core/model/src/main/kotlin/com/chipichipi/dobedobe/core/model/UserData.kt b/core/model/src/main/kotlin/com/chipichipi/dobedobe/core/model/UserData.kt index 84ec3551..aa66245d 100644 --- a/core/model/src/main/kotlin/com/chipichipi/dobedobe/core/model/UserData.kt +++ b/core/model/src/main/kotlin/com/chipichipi/dobedobe/core/model/UserData.kt @@ -2,4 +2,6 @@ package com.chipichipi.dobedobe.core.model data class UserData( val isOnboardingCompleted: Boolean, + val isGoalNotificationEnabled: Boolean, + val isSystemNotificationDialogDisabled: Boolean, ) diff --git a/core/notifications/.gitignore b/core/notifications/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core/notifications/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/notifications/build.gradle.kts b/core/notifications/build.gradle.kts new file mode 100644 index 00000000..e15c08b3 --- /dev/null +++ b/core/notifications/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + alias(libs.plugins.dobedobe.android.library) +} + +android { + namespace = "com.chipichipi.dobedobe.core.notifications" +} + +dependencies { + api(projects.core.model) + implementation(projects.core.common) + + compileOnly(platform(libs.androidx.compose.bom)) + implementation(libs.koin.android) +} diff --git a/core/notifications/src/androidTest/kotlin/com/chipichipi/dobedobe/core/notifications/ExampleInstrumentedTest.kt b/core/notifications/src/androidTest/kotlin/com/chipichipi/dobedobe/core/notifications/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..a476cc63 --- /dev/null +++ b/core/notifications/src/androidTest/kotlin/com/chipichipi/dobedobe/core/notifications/ExampleInstrumentedTest.kt @@ -0,0 +1,22 @@ +package com.chipichipi.dobedobe.core.notifications + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.chipichipi.dobedobe.core.notifications.test", appContext.packageName) + } +} diff --git a/core/notifications/src/main/AndroidManifest.xml b/core/notifications/src/main/AndroidManifest.xml new file mode 100644 index 00000000..972f3b97 --- /dev/null +++ b/core/notifications/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/core/notifications/src/main/kotlin/com/chipichipi/dobedobe/core/notifications/NotificationUtil.kt b/core/notifications/src/main/kotlin/com/chipichipi/dobedobe/core/notifications/NotificationUtil.kt new file mode 100644 index 00000000..15c13095 --- /dev/null +++ b/core/notifications/src/main/kotlin/com/chipichipi/dobedobe/core/notifications/NotificationUtil.kt @@ -0,0 +1,38 @@ +package com.chipichipi.dobedobe.core.notifications + +import android.content.Context +import android.content.Intent +import android.provider.Settings +import androidx.core.app.NotificationManagerCompat + +object NotificationUtil { + fun handleNotificationToggle( + context: Context, + enabled: Boolean, + onNotificationToggled: (Boolean) -> Unit, + ) { + if (enabled) { + if (areNotificationsEnabled(context)) { + onNotificationToggled(true) + } else { + openSystemNotificationSetting(context) + } + } else { + onNotificationToggled(false) + } + } + + fun areNotificationsEnabled(context: Context) = + NotificationManagerCompat + .from(context) + .areNotificationsEnabled() + + private fun openSystemNotificationSetting(context: Context) { + val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) + .apply { + putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) + } + + context.startActivity(intent) + } +} diff --git a/core/notifications/src/test/kotlin/com/chipichipi/dobedobe/core/notifications/ExampleUnitTest.kt b/core/notifications/src/test/kotlin/com/chipichipi/dobedobe/core/notifications/ExampleUnitTest.kt new file mode 100644 index 00000000..9503e373 --- /dev/null +++ b/core/notifications/src/test/kotlin/com/chipichipi/dobedobe/core/notifications/ExampleUnitTest.kt @@ -0,0 +1,16 @@ +package com.chipichipi.dobedobe.core.notifications + +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/feature/dashboard/build.gradle.kts b/feature/dashboard/build.gradle.kts index 9ec56d2c..a63d3168 100644 --- a/feature/dashboard/build.gradle.kts +++ b/feature/dashboard/build.gradle.kts @@ -10,4 +10,6 @@ android { dependencies { implementation(projects.core.data) implementation(projects.feature.goal) + + implementation(libs.accompanist.permission) } diff --git a/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/DashboardScreen.kt b/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/DashboardScreen.kt index e727c8e0..2de2f485 100644 --- a/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/DashboardScreen.kt +++ b/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/DashboardScreen.kt @@ -1,5 +1,6 @@ package com.chipichipi.dobedobe.feature.dashboard +import android.os.Build import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionLayout import androidx.compose.foundation.background @@ -8,13 +9,19 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SheetValue +import androidx.compose.material3.Text import androidx.compose.material3.rememberBottomSheetScaffoldState import androidx.compose.material3.rememberStandardBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect 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.graphics.Color @@ -22,15 +29,21 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.chipichipi.dobedobe.core.designsystem.component.DobeDobeBottomSheetScaffold +import com.chipichipi.dobedobe.core.designsystem.component.DobeDobeDialog import com.chipichipi.dobedobe.feature.dashboard.component.DashboardCharacter import com.chipichipi.dobedobe.feature.dashboard.component.DashboardPhotoFrameBox import com.chipichipi.dobedobe.feature.dashboard.component.DashboardTopAppBar import com.chipichipi.dobedobe.feature.dashboard.preview.GoalPreviewParameterProvider +import com.google.accompanist.permissions.ExperimentalPermissionsApi +import com.google.accompanist.permissions.PermissionStatus +import com.google.accompanist.permissions.isGranted +import com.google.accompanist.permissions.rememberPermissionState import org.koin.androidx.compose.koinViewModel @Composable internal fun DashboardRoute( onShowSnackbar: suspend (String, String?) -> Boolean, + navigateToSetting: () -> Unit, modifier: Modifier = Modifier, viewModel: DashboardViewModel = koinViewModel(), ) { @@ -40,6 +53,9 @@ internal fun DashboardRoute( modifier = modifier, onShowSnackbar = onShowSnackbar, uiState = uiState, + setGoalNotificationEnabled = viewModel::setGoalNotificationEnabled, + disableSystemNotificationDialog = viewModel::disableSystemNotificationDialog, + navigateToSetting = navigateToSetting, ) } @@ -48,6 +64,9 @@ internal fun DashboardRoute( private fun DashboardScreen( onShowSnackbar: suspend (String, String?) -> Boolean, uiState: DashboardUiState, + setGoalNotificationEnabled: (Boolean) -> Unit, + disableSystemNotificationDialog: () -> Unit, + navigateToSetting: () -> Unit, modifier: Modifier = Modifier, ) { val bottomSheetScaffoldState = rememberBottomSheetScaffoldState( @@ -74,7 +93,7 @@ private fun DashboardScreen( // TODO: 기능 추가 필요 DashboardTopAppBar( onEditClick = {}, - onSettingClick = {}, + navigateToSetting = navigateToSetting, ) }, ) { innerPadding -> @@ -96,6 +115,8 @@ private fun DashboardScreen( uiState = uiState, photoFramesState = photoFramesState, innerPadding = innerPadding, + setGoalNotificationEnabled = setGoalNotificationEnabled, + disableSystemNotificationDialog = disableSystemNotificationDialog, modifier = Modifier.fillMaxSize(), ) } @@ -110,6 +131,8 @@ private fun DashboardBody( uiState: DashboardUiState.Success, photoFramesState: DashboardPhotoFramesState, innerPadding: PaddingValues, + setGoalNotificationEnabled: (Boolean) -> Unit, + disableSystemNotificationDialog: () -> Unit, modifier: Modifier = Modifier, ) { SharedTransitionLayout( @@ -138,4 +161,63 @@ private fun DashboardBody( ) } } + + GoalNotificationPermission( + isSystemNotificationDialogDisabled = uiState.isSystemNotificationDialogDisabled, + setGoalNotificationEnabled = setGoalNotificationEnabled, + disableSystemNotificationDialog = disableSystemNotificationDialog, + ) +} + +@Composable +@OptIn(ExperimentalPermissionsApi::class) +private fun GoalNotificationPermission( + isSystemNotificationDialogDisabled: Boolean, + setGoalNotificationEnabled: (Boolean) -> Unit, + disableSystemNotificationDialog: () -> Unit, +) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return + val notificationsPermissionState = rememberPermissionState( + android.Manifest.permission.POST_NOTIFICATIONS, + ) + var showGoalNotificationDialog by remember { mutableStateOf(false) } + + LaunchedEffect(notificationsPermissionState.status, isSystemNotificationDialogDisabled) { + if (isSystemNotificationDialogDisabled) return@LaunchedEffect + val status = notificationsPermissionState.status + + when { + status is PermissionStatus.Denied && !status.shouldShowRationale -> { + showGoalNotificationDialog = true + } + status is PermissionStatus.Denied && status.shouldShowRationale -> { + setGoalNotificationEnabled(false) + disableSystemNotificationDialog() + } + status.isGranted -> { + setGoalNotificationEnabled(true) + disableSystemNotificationDialog() + } + } + } + + if (showGoalNotificationDialog) { + DobeDobeDialog( + onDismissRequest = { + showGoalNotificationDialog = false + }, + // TODO : 변경 필요 + title = "목표에 대한 알림을 위해\n 권한이 필요합니다.", + ) { + Button( + onClick = { + notificationsPermissionState.launchPermissionRequest() + showGoalNotificationDialog = false + }, + ) { + // TODO : 변경 필요 + Text("확인") + } + } + } } diff --git a/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/DashboardUiState.kt b/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/DashboardUiState.kt index 19201823..2654c8ac 100644 --- a/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/DashboardUiState.kt +++ b/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/DashboardUiState.kt @@ -7,6 +7,7 @@ sealed interface DashboardUiState { data class Success( val photoState: List, + val isSystemNotificationDialogDisabled: Boolean, ) : DashboardUiState data object Error : DashboardUiState diff --git a/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/DashboardViewModel.kt b/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/DashboardViewModel.kt index 30c0c8f6..7e0a6a6a 100644 --- a/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/DashboardViewModel.kt +++ b/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/DashboardViewModel.kt @@ -2,14 +2,18 @@ package com.chipichipi.dobedobe.feature.dashboard import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.chipichipi.dobedobe.core.data.repository.UserRepository import com.chipichipi.dobedobe.core.model.DashboardPhoto import com.chipichipi.dobedobe.feature.dashboard.model.DashboardPhotoConfig import com.chipichipi.dobedobe.feature.dashboard.model.DashboardPhotoState import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch // TODO : 제거 필요 private val fakeDashboardPhotoState = @@ -20,22 +24,46 @@ private val fakeDashboardPhotoState = ), ) -class DashboardViewModel() : ViewModel() { - val uiState: StateFlow = - fakeDashboardPhotoState.map { photoData -> - val dashboardPhotoStates = DashboardPhotoConfig.entries.map { config -> - val photo = photoData.find { it.id == config.id } - - DashboardPhotoState( - config = config, - url = photo?.url.orEmpty(), - ) - } - DashboardUiState.Success(dashboardPhotoStates) - } - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = DashboardUiState.Loading, +internal class DashboardViewModel( + private val userRepository: UserRepository, +) : ViewModel() { + private val isSystemNotificationDialogDisabledFlow = userRepository.userData + .map { it.isSystemNotificationDialogDisabled } + .distinctUntilChanged() + + val uiState: StateFlow = combine( + fakeDashboardPhotoState, + isSystemNotificationDialogDisabledFlow, + ) { photoState, isSystemNotificationDialogDisabled -> + val dashboardPhotoStates = DashboardPhotoConfig.entries.map { config -> + val photo = photoState.find { it.id == config.id } + + DashboardPhotoState( + config = config, + url = photo?.url.orEmpty(), ) + } + + DashboardUiState.Success( + dashboardPhotoStates, + isSystemNotificationDialogDisabled, + ) + } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = DashboardUiState.Loading, + ) + + fun setGoalNotificationEnabled(enabled: Boolean) { + viewModelScope.launch { + userRepository.setGoalNotificationEnabled(enabled) + } + } + + fun disableSystemNotificationDialog() { + viewModelScope.launch { + userRepository.disableSystemNotificationDialog() + } + } } diff --git a/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/component/DashboardTopAppBar.kt b/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/component/DashboardTopAppBar.kt index 19ad6b2b..960c64a6 100644 --- a/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/component/DashboardTopAppBar.kt +++ b/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/component/DashboardTopAppBar.kt @@ -22,7 +22,7 @@ import com.chipichipi.dobedobe.core.designsystem.theme.DobeDobeTheme @Composable internal fun DashboardTopAppBar( onEditClick: () -> Unit, - onSettingClick: () -> Unit, + navigateToSetting: () -> Unit, modifier: Modifier = Modifier, ) { DobeDobeTopAppBar( @@ -43,7 +43,7 @@ internal fun DashboardTopAppBar( } // TODO: 아이콘 교체 필요 IconButton( - onClick = onSettingClick, + onClick = navigateToSetting, ) { Icon( Icons.AutoMirrored.Filled.AltRoute, @@ -65,7 +65,7 @@ private fun DashboardTopAppBarPreview() { DobeDobeTheme { DashboardTopAppBar( onEditClick = {}, - onSettingClick = {}, + navigateToSetting = {}, ) } } diff --git a/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/navigation/DashboardNavigation.kt b/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/navigation/DashboardNavigation.kt index 74d2ae66..bca04968 100644 --- a/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/navigation/DashboardNavigation.kt +++ b/feature/dashboard/src/main/kotlin/com/chipichipi/dobedobe/feature/dashboard/navigation/DashboardNavigation.kt @@ -16,10 +16,12 @@ fun NavController.navigateToDashboard( fun NavGraphBuilder.dashboardScreen( onShowSnackbar: suspend (String, String?) -> Boolean, + navigateToSetting: () -> Unit, ) { composable { DashboardRoute( onShowSnackbar = onShowSnackbar, + navigateToSetting = navigateToSetting, ) } } diff --git a/feature/setting/build.gradle.kts b/feature/setting/build.gradle.kts index e02383ad..b27602b9 100644 --- a/feature/setting/build.gradle.kts +++ b/feature/setting/build.gradle.kts @@ -9,4 +9,5 @@ android { dependencies { implementation(projects.core.data) + implementation(projects.core.notifications) } diff --git a/feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/SettingScreen.kt b/feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/SettingScreen.kt new file mode 100644 index 00000000..2e5fcbce --- /dev/null +++ b/feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/SettingScreen.kt @@ -0,0 +1,144 @@ +package com.chipichipi.dobedobe.feature.setting + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowForwardIos +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +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.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.LifecycleResumeEffect +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.chipichipi.dobedobe.core.designsystem.component.DobeDobeSwitch +import com.chipichipi.dobedobe.core.notifications.NotificationUtil +import com.chipichipi.dobedobe.core.notifications.NotificationUtil.areNotificationsEnabled +import com.chipichipi.dobedobe.feature.setting.component.SettingRow +import com.chipichipi.dobedobe.feature.setting.component.SettingTopAppBar +import com.chipichipi.dobedobe.feature.setting.util.openPlayStore +import org.koin.androidx.compose.koinViewModel + +@Composable +internal fun SettingRoute( + onShowSnackbar: suspend (String, String?) -> Boolean, + navigateToBack: () -> Unit, + modifier: Modifier = Modifier, + viewModel: SettingViewModel = koinViewModel(), +) { + val isGoalNotificationEnabled by viewModel.isGoalNotificationEnabled.collectAsStateWithLifecycle() + + SettingScreen( + modifier = modifier, + isGoalNotificationEnabled = isGoalNotificationEnabled, + navigateToBack = navigateToBack, + onNotificationToggled = viewModel::setGoalNotificationEnabled, + ) +} + +@Composable +private fun SettingScreen( + isGoalNotificationEnabled: Boolean, + navigateToBack: () -> Unit, + onNotificationToggled: (Boolean) -> Unit, + modifier: Modifier = Modifier, +) { + Scaffold( + modifier = modifier, + topBar = { + SettingTopAppBar( + navigateToBack = navigateToBack, + ) + }, + ) { innerPadding -> + SettingBody( + isGoalNotificationEnabled = isGoalNotificationEnabled, + onNotificationToggled = onNotificationToggled, + modifier = Modifier + .fillMaxSize() + .padding(innerPadding), + ) + } + + GoalNotificationEffect( + onNotificationToggled = onNotificationToggled, + ) +} + +@Composable +private fun SettingBody( + isGoalNotificationEnabled: Boolean, + onNotificationToggled: (Boolean) -> Unit, + modifier: Modifier = Modifier, +) { + val context = LocalContext.current + + Column( + modifier = modifier, + ) { + // TODO : 언어 대응 필요 + SettingRow( + label = "알림", + ) { + DobeDobeSwitch( + modifier = Modifier.padding(end = 8.dp), + checked = isGoalNotificationEnabled, + onCheckedChange = { checked -> + NotificationUtil.handleNotificationToggle( + context = context, + enabled = checked, + onNotificationToggled = onNotificationToggled, + ) + }, + ) + } + + // TODO : 언어 대응 필요 + SettingRow( + label = "앱 피드백 남기기", + ) { + IconButton( + modifier = Modifier.size(42.dp), + onClick = { openPlayStore(context) }, + ) { + // TODO: 아이콘 변경 필요 + Icon( + modifier = Modifier.size(24.dp), + imageVector = Icons.Default.ArrowForwardIos, + contentDescription = null, + ) + } + } + } +} + +@Composable +private fun GoalNotificationEffect( + onNotificationToggled: (Boolean) -> Unit, +) { + val context = LocalContext.current + + var systemNotificationEnabled by remember { + mutableStateOf(areNotificationsEnabled(context)) + } + + LifecycleResumeEffect(Unit) { + val updatedSystemNotificationEnabled = areNotificationsEnabled(context) + + if (systemNotificationEnabled != updatedSystemNotificationEnabled) { + systemNotificationEnabled = updatedSystemNotificationEnabled + if (updatedSystemNotificationEnabled) { + onNotificationToggled(true) + } + } + onPauseOrDispose { } + } +} diff --git a/feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/SettingViewModel.kt b/feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/SettingViewModel.kt index 9f5196bd..f3d11faa 100644 --- a/feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/SettingViewModel.kt +++ b/feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/SettingViewModel.kt @@ -1,5 +1,27 @@ package com.chipichipi.dobedobe.feature.setting import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.chipichipi.dobedobe.core.data.repository.UserRepository +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch -class SettingViewModel : ViewModel() +internal class SettingViewModel( + private val userRepository: UserRepository, +) : ViewModel() { + val isGoalNotificationEnabled = userRepository.userData + .map { it.isGoalNotificationEnabled } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = false, + ) + + fun setGoalNotificationEnabled(checked: Boolean) { + viewModelScope.launch { + userRepository.setGoalNotificationEnabled(checked) + } + } +} diff --git a/feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/component/SettingRow.kt b/feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/component/SettingRow.kt new file mode 100644 index 00000000..780c6870 --- /dev/null +++ b/feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/component/SettingRow.kt @@ -0,0 +1,76 @@ +package com.chipichipi.dobedobe.feature.setting.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.chipichipi.dobedobe.core.designsystem.component.ThemePreviews +import com.chipichipi.dobedobe.core.designsystem.theme.DobeDobeTheme + +@Composable +internal fun SettingRow( + label: String, + modifier: Modifier = Modifier, + trailingContent: @Composable () -> Unit, +) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.Center, + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .heightIn(52.dp) + .padding( + start = 24.dp, + end = 8.dp, + ), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + // TODO : 폰트 스타일 변경 필요 + Text( + text = label, + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold, + color = Color.Black, + ) + + trailingContent() + } + + // TODO : 색상 변경 필요 + HorizontalDivider( + modifier = Modifier.fillMaxWidth(), + thickness = 8.dp, + color = Color(0xFFF2F3F6), + ) + } +} + +@ThemePreviews +@Composable +private fun SettingRowPreview() { + DobeDobeTheme { + SettingRow( + label = "TEST", + ) { + Row( + modifier = Modifier.fillMaxWidth(), + ) { + Text(text = "TEST") + } + } + } +} diff --git a/feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/component/SettingTopAppBar.kt b/feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/component/SettingTopAppBar.kt new file mode 100644 index 00000000..a972b3cf --- /dev/null +++ b/feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/component/SettingTopAppBar.kt @@ -0,0 +1,47 @@ +package com.chipichipi.dobedobe.feature.setting.component + +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBackIosNew +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.chipichipi.dobedobe.core.designsystem.component.DobeDobeTopAppBar +import com.chipichipi.dobedobe.core.designsystem.component.ThemePreviews +import com.chipichipi.dobedobe.core.designsystem.theme.DobeDobeTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun SettingTopAppBar( + navigateToBack: () -> Unit, + modifier: Modifier = Modifier, +) { + DobeDobeTopAppBar( + modifier = modifier, + navigationIcon = { + IconButton( + modifier = Modifier.size(48.dp), + onClick = navigateToBack, + ) { + Icon( + modifier = Modifier.size(24.dp), + imageVector = Icons.Default.ArrowBackIosNew, + contentDescription = null, + ) + } + }, + ) +} + +@ThemePreviews +@Composable +private fun SettingTopAppBarPreview() { + DobeDobeTheme { + SettingTopAppBar( + navigateToBack = {}, + ) + } +} diff --git a/feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/navigation/SettingNavigation.kt b/feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/navigation/SettingNavigation.kt new file mode 100644 index 00000000..3352961f --- /dev/null +++ b/feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/navigation/SettingNavigation.kt @@ -0,0 +1,27 @@ +package com.chipichipi.dobedobe.feature.setting.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import com.chipichipi.dobedobe.feature.setting.SettingRoute +import kotlinx.serialization.Serializable + +@Serializable +data object SettingRoute + +fun NavController.navigateToSetting( + navOptions: NavOptions? = null, +) = navigate(route = SettingRoute, navOptions) + +fun NavGraphBuilder.settingScreen( + onShowSnackbar: suspend (String, String?) -> Boolean, + navigateToBack: () -> Unit, +) { + composable { + SettingRoute( + onShowSnackbar = onShowSnackbar, + navigateToBack = navigateToBack, + ) + } +} diff --git a/feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/util/SettingUtil.kt b/feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/util/SettingUtil.kt new file mode 100644 index 00000000..bb84ea71 --- /dev/null +++ b/feature/setting/src/main/kotlin/com/chipichipi/dobedobe/feature/setting/util/SettingUtil.kt @@ -0,0 +1,15 @@ +package com.chipichipi.dobedobe.feature.setting.util + +import android.content.Context +import android.content.Intent +import android.net.Uri + +// TODO : 주소 확인 필요 +internal fun openPlayStore(context: Context) { + val intent = Intent( + Intent.ACTION_VIEW, + Uri.parse("https://play.google.com/store/apps/details?id=com.chipichipi.dobedobe"), + ) + + context.startActivity(intent) +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index eccd32cd..e77d4b78 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,6 +33,7 @@ robolectric = "4.14.1" # google protobuf = "4.29.2" protobufPlugin = "0.9.4" +permission = "0.37.0" # third-party coil = "2.7.0" @@ -95,6 +96,7 @@ androidx-test-truth = { module = "androidx.test.ext:truth", version.ref = "andro robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" } # google +accompanist-permission = { group = "com.google.accompanist", name = "accompanist-permissions", version.ref = "permission" } protobuf-kotlin-lite = { group = "com.google.protobuf", name = "protobuf-kotlin-lite", version.ref = "protobuf" } protobuf-protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protobuf" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 355bf0c6..9b0a4ac0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -30,6 +30,7 @@ include(":core:common") include(":core:database") include(":core:datastore") include(":core:datastore-proto") +include(":core:notifications") include(":feature:dashboard") include(":feature:goal")