Add Daily Digest notification preferences with custom time support
- Add dailyDigest and dailyDigestHour fields to Kotlin NotificationPreference model - Update NotificationPreferencesViewModel to support new fields - Add Daily Summary toggle with time picker to Android NotificationPreferencesScreen - Add Daily Summary toggle with time picker to iOS NotificationPreferencesView - Add localized strings for Daily Summary in all 10 languages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,8 @@ data class NotificationPreference(
|
||||
val residenceShared: Boolean = true,
|
||||
@SerialName("warranty_expiring")
|
||||
val warrantyExpiring: Boolean = true,
|
||||
@SerialName("daily_digest")
|
||||
val dailyDigest: Boolean = true,
|
||||
// Email preferences
|
||||
@SerialName("email_task_completed")
|
||||
val emailTaskCompleted: Boolean = true,
|
||||
@@ -50,7 +52,9 @@ data class NotificationPreference(
|
||||
@SerialName("task_overdue_hour")
|
||||
val taskOverdueHour: Int? = null,
|
||||
@SerialName("warranty_expiring_hour")
|
||||
val warrantyExpiringHour: Int? = null
|
||||
val warrantyExpiringHour: Int? = null,
|
||||
@SerialName("daily_digest_hour")
|
||||
val dailyDigestHour: Int? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@@ -67,6 +71,8 @@ data class UpdateNotificationPreferencesRequest(
|
||||
val residenceShared: Boolean? = null,
|
||||
@SerialName("warranty_expiring")
|
||||
val warrantyExpiring: Boolean? = null,
|
||||
@SerialName("daily_digest")
|
||||
val dailyDigest: Boolean? = null,
|
||||
// Email preferences
|
||||
@SerialName("email_task_completed")
|
||||
val emailTaskCompleted: Boolean? = null,
|
||||
@@ -76,7 +82,9 @@ data class UpdateNotificationPreferencesRequest(
|
||||
@SerialName("task_overdue_hour")
|
||||
val taskOverdueHour: Int? = null,
|
||||
@SerialName("warranty_expiring_hour")
|
||||
val warrantyExpiringHour: Int? = null
|
||||
val warrantyExpiringHour: Int? = null,
|
||||
@SerialName("daily_digest_hour")
|
||||
val dailyDigestHour: Int? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
||||
@@ -37,20 +37,24 @@ fun NotificationPreferencesScreen(
|
||||
var taskAssigned by remember { mutableStateOf(true) }
|
||||
var residenceShared by remember { mutableStateOf(true) }
|
||||
var warrantyExpiring by remember { mutableStateOf(true) }
|
||||
var dailyDigest by remember { mutableStateOf(true) }
|
||||
var emailTaskCompleted by remember { mutableStateOf(true) }
|
||||
|
||||
// Custom notification times (local hours)
|
||||
var taskDueSoonHour by remember { mutableStateOf<Int?>(null) }
|
||||
var taskOverdueHour by remember { mutableStateOf<Int?>(null) }
|
||||
var warrantyExpiringHour by remember { mutableStateOf<Int?>(null) }
|
||||
var dailyDigestHour by remember { mutableStateOf<Int?>(null) }
|
||||
|
||||
// Time picker dialog states
|
||||
var showTaskDueSoonTimePicker by remember { mutableStateOf(false) }
|
||||
var showTaskOverdueTimePicker by remember { mutableStateOf(false) }
|
||||
var showDailyDigestTimePicker by remember { mutableStateOf(false) }
|
||||
|
||||
// Default local hours when user first enables custom time
|
||||
val defaultTaskDueSoonLocalHour = 14 // 2 PM local
|
||||
val defaultTaskOverdueLocalHour = 9 // 9 AM local
|
||||
val defaultDailyDigestLocalHour = 8 // 8 AM local
|
||||
|
||||
// Load preferences on first render
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -67,6 +71,7 @@ fun NotificationPreferencesScreen(
|
||||
taskAssigned = prefs.taskAssigned
|
||||
residenceShared = prefs.residenceShared
|
||||
warrantyExpiring = prefs.warrantyExpiring
|
||||
dailyDigest = prefs.dailyDigest
|
||||
emailTaskCompleted = prefs.emailTaskCompleted
|
||||
|
||||
// Load custom notification times (convert from UTC to local)
|
||||
@@ -79,6 +84,9 @@ fun NotificationPreferencesScreen(
|
||||
prefs.warrantyExpiringHour?.let { utcHour ->
|
||||
warrantyExpiringHour = DateUtils.utcHourToLocal(utcHour)
|
||||
}
|
||||
prefs.dailyDigestHour?.let { utcHour ->
|
||||
dailyDigestHour = DateUtils.utcHourToLocal(utcHour)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,9 +382,54 @@ fun NotificationPreferencesScreen(
|
||||
viewModel.updatePreference(warrantyExpiring = it)
|
||||
}
|
||||
)
|
||||
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(horizontal = AppSpacing.lg),
|
||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)
|
||||
)
|
||||
|
||||
NotificationToggle(
|
||||
title = stringResource(Res.string.notifications_daily_digest),
|
||||
description = stringResource(Res.string.notifications_daily_digest_desc),
|
||||
icon = Icons.Default.Summarize,
|
||||
iconTint = MaterialTheme.colorScheme.secondary,
|
||||
checked = dailyDigest,
|
||||
onCheckedChange = {
|
||||
dailyDigest = it
|
||||
viewModel.updatePreference(dailyDigest = it)
|
||||
}
|
||||
)
|
||||
|
||||
// Time picker for Daily Digest
|
||||
if (dailyDigest) {
|
||||
NotificationTimePickerRow(
|
||||
currentHour = dailyDigestHour,
|
||||
onSetCustomTime = {
|
||||
val localHour = defaultDailyDigestLocalHour
|
||||
dailyDigestHour = localHour
|
||||
val utcHour = DateUtils.localHourToUtc(localHour)
|
||||
viewModel.updatePreference(dailyDigestHour = utcHour)
|
||||
},
|
||||
onChangeTime = { showDailyDigestTimePicker = true }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Daily Digest time picker dialog
|
||||
if (showDailyDigestTimePicker) {
|
||||
HourPickerDialog(
|
||||
currentHour = dailyDigestHour ?: defaultDailyDigestLocalHour,
|
||||
onHourSelected = { hour ->
|
||||
dailyDigestHour = hour
|
||||
val utcHour = DateUtils.localHourToUtc(hour)
|
||||
viewModel.updatePreference(dailyDigestHour = utcHour)
|
||||
showDailyDigestTimePicker = false
|
||||
},
|
||||
onDismiss = { showDailyDigestTimePicker = false }
|
||||
)
|
||||
}
|
||||
|
||||
// Email Notifications Section
|
||||
Text(
|
||||
stringResource(Res.string.notifications_email_section),
|
||||
|
||||
@@ -37,10 +37,12 @@ class NotificationPreferencesViewModel : ViewModel() {
|
||||
taskAssigned: Boolean? = null,
|
||||
residenceShared: Boolean? = null,
|
||||
warrantyExpiring: Boolean? = null,
|
||||
dailyDigest: Boolean? = null,
|
||||
emailTaskCompleted: Boolean? = null,
|
||||
taskDueSoonHour: Int? = null,
|
||||
taskOverdueHour: Int? = null,
|
||||
warrantyExpiringHour: Int? = null
|
||||
warrantyExpiringHour: Int? = null,
|
||||
dailyDigestHour: Int? = null
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
_updateState.value = ApiResult.Loading
|
||||
@@ -51,10 +53,12 @@ class NotificationPreferencesViewModel : ViewModel() {
|
||||
taskAssigned = taskAssigned,
|
||||
residenceShared = residenceShared,
|
||||
warrantyExpiring = warrantyExpiring,
|
||||
dailyDigest = dailyDigest,
|
||||
emailTaskCompleted = emailTaskCompleted,
|
||||
taskDueSoonHour = taskDueSoonHour,
|
||||
taskOverdueHour = taskOverdueHour,
|
||||
warrantyExpiringHour = warrantyExpiringHour
|
||||
warrantyExpiringHour = warrantyExpiringHour,
|
||||
dailyDigestHour = dailyDigestHour
|
||||
)
|
||||
val result = APILayer.updateNotificationPreferences(request)
|
||||
_updateState.value = when (result) {
|
||||
|
||||
Reference in New Issue
Block a user