Skip to content

Commit

Permalink
Notification 기반 잡기 (#20)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
nohjunh authored Jan 26, 2025
1 parent 6de7c5e commit 539ec97
Show file tree
Hide file tree
Showing 31 changed files with 777 additions and 45 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ interface UserRepository {
val userData: Flow<UserData>

suspend fun completeOnBoarding(): Result<Unit>

suspend fun setGoalNotificationEnabled(enabled: Boolean): Result<Unit>

suspend fun disableSystemNotificationDialog(): Result<Unit>
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,16 @@ internal class UserRepositoryImpl(
userPreferencesDataSource.completeOnBoarding()
}
}

override suspend fun setGoalNotificationEnabled(enabled: Boolean): Result<Unit> {
return runCatching {
userPreferencesDataSource.setGoalNotificationEnabled(enabled)
}
}

override suspend fun disableSystemNotificationDialog(): Result<Unit> {
return runCatching {
userPreferencesDataSource.disableSystemNotificationDialog()
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Original file line number Diff line number Diff line change
@@ -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")
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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 = {},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ package com.chipichipi.dobedobe.core.model

data class UserData(
val isOnboardingCompleted: Boolean,
val isGoalNotificationEnabled: Boolean,
val isSystemNotificationDialogDisabled: Boolean,
)
1 change: 1 addition & 0 deletions core/notifications/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
15 changes: 15 additions & 0 deletions core/notifications/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
4 changes: 4 additions & 0 deletions core/notifications/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
</manifest>
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading

0 comments on commit 539ec97

Please sign in to comment.