Skip to content

Commit

Permalink
Setting 기능 추가 (#21)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
nohjunh authored Jan 26, 2025
1 parent b4f9ec3 commit f0b9a9a
Show file tree
Hide file tree
Showing 12 changed files with 405 additions and 28 deletions.
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
@@ -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 @@ -43,6 +43,7 @@ import org.koin.androidx.compose.koinViewModel
@Composable
internal fun DashboardRoute(
onShowSnackbar: suspend (String, String?) -> Boolean,
navigateToSetting: () -> Unit,
modifier: Modifier = Modifier,
viewModel: DashboardViewModel = koinViewModel(),
) {
Expand All @@ -54,6 +55,7 @@ internal fun DashboardRoute(
uiState = uiState,
setGoalNotificationEnabled = viewModel::setGoalNotificationEnabled,
disableSystemNotificationDialog = viewModel::disableSystemNotificationDialog,
navigateToSetting = navigateToSetting,
)
}

Expand All @@ -64,6 +66,7 @@ private fun DashboardScreen(
uiState: DashboardUiState,
setGoalNotificationEnabled: (Boolean) -> Unit,
disableSystemNotificationDialog: () -> Unit,
navigateToSetting: () -> Unit,
modifier: Modifier = Modifier,
) {
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
Expand All @@ -90,7 +93,7 @@ private fun DashboardScreen(
// TODO: 기능 추가 필요
DashboardTopAppBar(
onEditClick = {},
onSettingClick = {},
navigateToSetting = navigateToSetting,
)
},
) { innerPadding ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -43,7 +43,7 @@ internal fun DashboardTopAppBar(
}
// TODO: 아이콘 교체 필요
IconButton(
onClick = onSettingClick,
onClick = navigateToSetting,
) {
Icon(
Icons.AutoMirrored.Filled.AltRoute,
Expand All @@ -65,7 +65,7 @@ private fun DashboardTopAppBarPreview() {
DobeDobeTheme {
DashboardTopAppBar(
onEditClick = {},
onSettingClick = {},
navigateToSetting = {},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ fun NavController.navigateToDashboard(

fun NavGraphBuilder.dashboardScreen(
onShowSnackbar: suspend (String, String?) -> Boolean,
navigateToSetting: () -> Unit,
) {
composable<DashboardRoute> {
DashboardRoute(
onShowSnackbar = onShowSnackbar,
navigateToSetting = navigateToSetting,
)
}
}
1 change: 1 addition & 0 deletions feature/setting/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ android {

dependencies {
implementation(projects.core.data)
implementation(projects.core.notifications)
}
Original file line number Diff line number Diff line change
@@ -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.checkSystemNotificationEnabled
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 isGoalNotificationChecked by viewModel.isGoalNotificationChecked.collectAsStateWithLifecycle()

SettingScreen(
modifier = modifier,
isGoalNotificationChecked = isGoalNotificationChecked,
navigateToBack = navigateToBack,
onNotificationToggled = viewModel::setGoalNotificationChecked,
)
}

@Composable
private fun SettingScreen(
isGoalNotificationChecked: Boolean,
navigateToBack: () -> Unit,
onNotificationToggled: (Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
Scaffold(
modifier = modifier,
topBar = {
SettingTopAppBar(
navigateToBack = navigateToBack,
)
},
) { innerPadding ->
SettingBody(
isGoalNotificationChecked = isGoalNotificationChecked,
onNotificationToggled = onNotificationToggled,
modifier = Modifier
.fillMaxSize()
.padding(innerPadding),
)
}

GoalNotificationEffect(
onNotificationToggled = onNotificationToggled,
)
}

@Composable
private fun SettingBody(
isGoalNotificationChecked: 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 = isGoalNotificationChecked,
onCheckedChange = { checked ->
NotificationUtil.handleNotificationToggle(
context = context,
checked = 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(checkSystemNotificationEnabled(context))
}

LifecycleResumeEffect(Unit) {
val updatedSystemNotificationEnabled = checkSystemNotificationEnabled(context)

if (systemNotificationEnabled != updatedSystemNotificationEnabled) {
systemNotificationEnabled = updatedSystemNotificationEnabled
if (updatedSystemNotificationEnabled) {
onNotificationToggled(true)
}
}
onPauseOrDispose { }
}
}
Original file line number Diff line number Diff line change
@@ -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 isGoalNotificationChecked = userRepository.userData
.map { it.isGoalNotificationChecked }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = false,
)

fun setGoalNotificationChecked(checked: Boolean) {
viewModelScope.launch {
userRepository.setGoalNotificationChecked(checked)
}
}
}
Loading

0 comments on commit f0b9a9a

Please sign in to comment.