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:
@@ -399,6 +399,8 @@
|
|||||||
<string name="notifications_property_shared_desc">When someone shares a property with you</string>
|
<string name="notifications_property_shared_desc">When someone shares a property with you</string>
|
||||||
<string name="notifications_warranty_expiring">Warranty Expiring</string>
|
<string name="notifications_warranty_expiring">Warranty Expiring</string>
|
||||||
<string name="notifications_warranty_expiring_desc">Reminders for expiring warranties</string>
|
<string name="notifications_warranty_expiring_desc">Reminders for expiring warranties</string>
|
||||||
|
<string name="notifications_daily_digest">Daily Summary</string>
|
||||||
|
<string name="notifications_daily_digest_desc">Daily overview of tasks due and overdue</string>
|
||||||
|
|
||||||
<!-- Email Notifications -->
|
<!-- Email Notifications -->
|
||||||
<string name="notifications_email_section">Email Notifications</string>
|
<string name="notifications_email_section">Email Notifications</string>
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ data class NotificationPreference(
|
|||||||
val residenceShared: Boolean = true,
|
val residenceShared: Boolean = true,
|
||||||
@SerialName("warranty_expiring")
|
@SerialName("warranty_expiring")
|
||||||
val warrantyExpiring: Boolean = true,
|
val warrantyExpiring: Boolean = true,
|
||||||
|
@SerialName("daily_digest")
|
||||||
|
val dailyDigest: Boolean = true,
|
||||||
// Email preferences
|
// Email preferences
|
||||||
@SerialName("email_task_completed")
|
@SerialName("email_task_completed")
|
||||||
val emailTaskCompleted: Boolean = true,
|
val emailTaskCompleted: Boolean = true,
|
||||||
@@ -50,7 +52,9 @@ data class NotificationPreference(
|
|||||||
@SerialName("task_overdue_hour")
|
@SerialName("task_overdue_hour")
|
||||||
val taskOverdueHour: Int? = null,
|
val taskOverdueHour: Int? = null,
|
||||||
@SerialName("warranty_expiring_hour")
|
@SerialName("warranty_expiring_hour")
|
||||||
val warrantyExpiringHour: Int? = null
|
val warrantyExpiringHour: Int? = null,
|
||||||
|
@SerialName("daily_digest_hour")
|
||||||
|
val dailyDigestHour: Int? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -67,6 +71,8 @@ data class UpdateNotificationPreferencesRequest(
|
|||||||
val residenceShared: Boolean? = null,
|
val residenceShared: Boolean? = null,
|
||||||
@SerialName("warranty_expiring")
|
@SerialName("warranty_expiring")
|
||||||
val warrantyExpiring: Boolean? = null,
|
val warrantyExpiring: Boolean? = null,
|
||||||
|
@SerialName("daily_digest")
|
||||||
|
val dailyDigest: Boolean? = null,
|
||||||
// Email preferences
|
// Email preferences
|
||||||
@SerialName("email_task_completed")
|
@SerialName("email_task_completed")
|
||||||
val emailTaskCompleted: Boolean? = null,
|
val emailTaskCompleted: Boolean? = null,
|
||||||
@@ -76,7 +82,9 @@ data class UpdateNotificationPreferencesRequest(
|
|||||||
@SerialName("task_overdue_hour")
|
@SerialName("task_overdue_hour")
|
||||||
val taskOverdueHour: Int? = null,
|
val taskOverdueHour: Int? = null,
|
||||||
@SerialName("warranty_expiring_hour")
|
@SerialName("warranty_expiring_hour")
|
||||||
val warrantyExpiringHour: Int? = null
|
val warrantyExpiringHour: Int? = null,
|
||||||
|
@SerialName("daily_digest_hour")
|
||||||
|
val dailyDigestHour: Int? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|||||||
@@ -37,20 +37,24 @@ fun NotificationPreferencesScreen(
|
|||||||
var taskAssigned by remember { mutableStateOf(true) }
|
var taskAssigned by remember { mutableStateOf(true) }
|
||||||
var residenceShared by remember { mutableStateOf(true) }
|
var residenceShared by remember { mutableStateOf(true) }
|
||||||
var warrantyExpiring by remember { mutableStateOf(true) }
|
var warrantyExpiring by remember { mutableStateOf(true) }
|
||||||
|
var dailyDigest by remember { mutableStateOf(true) }
|
||||||
var emailTaskCompleted by remember { mutableStateOf(true) }
|
var emailTaskCompleted by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
// Custom notification times (local hours)
|
// Custom notification times (local hours)
|
||||||
var taskDueSoonHour by remember { mutableStateOf<Int?>(null) }
|
var taskDueSoonHour by remember { mutableStateOf<Int?>(null) }
|
||||||
var taskOverdueHour by remember { mutableStateOf<Int?>(null) }
|
var taskOverdueHour by remember { mutableStateOf<Int?>(null) }
|
||||||
var warrantyExpiringHour by remember { mutableStateOf<Int?>(null) }
|
var warrantyExpiringHour by remember { mutableStateOf<Int?>(null) }
|
||||||
|
var dailyDigestHour by remember { mutableStateOf<Int?>(null) }
|
||||||
|
|
||||||
// Time picker dialog states
|
// Time picker dialog states
|
||||||
var showTaskDueSoonTimePicker by remember { mutableStateOf(false) }
|
var showTaskDueSoonTimePicker by remember { mutableStateOf(false) }
|
||||||
var showTaskOverdueTimePicker 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
|
// Default local hours when user first enables custom time
|
||||||
val defaultTaskDueSoonLocalHour = 14 // 2 PM local
|
val defaultTaskDueSoonLocalHour = 14 // 2 PM local
|
||||||
val defaultTaskOverdueLocalHour = 9 // 9 AM local
|
val defaultTaskOverdueLocalHour = 9 // 9 AM local
|
||||||
|
val defaultDailyDigestLocalHour = 8 // 8 AM local
|
||||||
|
|
||||||
// Load preferences on first render
|
// Load preferences on first render
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
@@ -67,6 +71,7 @@ fun NotificationPreferencesScreen(
|
|||||||
taskAssigned = prefs.taskAssigned
|
taskAssigned = prefs.taskAssigned
|
||||||
residenceShared = prefs.residenceShared
|
residenceShared = prefs.residenceShared
|
||||||
warrantyExpiring = prefs.warrantyExpiring
|
warrantyExpiring = prefs.warrantyExpiring
|
||||||
|
dailyDigest = prefs.dailyDigest
|
||||||
emailTaskCompleted = prefs.emailTaskCompleted
|
emailTaskCompleted = prefs.emailTaskCompleted
|
||||||
|
|
||||||
// Load custom notification times (convert from UTC to local)
|
// Load custom notification times (convert from UTC to local)
|
||||||
@@ -79,6 +84,9 @@ fun NotificationPreferencesScreen(
|
|||||||
prefs.warrantyExpiringHour?.let { utcHour ->
|
prefs.warrantyExpiringHour?.let { utcHour ->
|
||||||
warrantyExpiringHour = DateUtils.utcHourToLocal(utcHour)
|
warrantyExpiringHour = DateUtils.utcHourToLocal(utcHour)
|
||||||
}
|
}
|
||||||
|
prefs.dailyDigestHour?.let { utcHour ->
|
||||||
|
dailyDigestHour = DateUtils.utcHourToLocal(utcHour)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,9 +382,54 @@ fun NotificationPreferencesScreen(
|
|||||||
viewModel.updatePreference(warrantyExpiring = it)
|
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
|
// Email Notifications Section
|
||||||
Text(
|
Text(
|
||||||
stringResource(Res.string.notifications_email_section),
|
stringResource(Res.string.notifications_email_section),
|
||||||
|
|||||||
@@ -37,10 +37,12 @@ class NotificationPreferencesViewModel : ViewModel() {
|
|||||||
taskAssigned: Boolean? = null,
|
taskAssigned: Boolean? = null,
|
||||||
residenceShared: Boolean? = null,
|
residenceShared: Boolean? = null,
|
||||||
warrantyExpiring: Boolean? = null,
|
warrantyExpiring: Boolean? = null,
|
||||||
|
dailyDigest: Boolean? = null,
|
||||||
emailTaskCompleted: Boolean? = null,
|
emailTaskCompleted: Boolean? = null,
|
||||||
taskDueSoonHour: Int? = null,
|
taskDueSoonHour: Int? = null,
|
||||||
taskOverdueHour: Int? = null,
|
taskOverdueHour: Int? = null,
|
||||||
warrantyExpiringHour: Int? = null
|
warrantyExpiringHour: Int? = null,
|
||||||
|
dailyDigestHour: Int? = null
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_updateState.value = ApiResult.Loading
|
_updateState.value = ApiResult.Loading
|
||||||
@@ -51,10 +53,12 @@ class NotificationPreferencesViewModel : ViewModel() {
|
|||||||
taskAssigned = taskAssigned,
|
taskAssigned = taskAssigned,
|
||||||
residenceShared = residenceShared,
|
residenceShared = residenceShared,
|
||||||
warrantyExpiring = warrantyExpiring,
|
warrantyExpiring = warrantyExpiring,
|
||||||
|
dailyDigest = dailyDigest,
|
||||||
emailTaskCompleted = emailTaskCompleted,
|
emailTaskCompleted = emailTaskCompleted,
|
||||||
taskDueSoonHour = taskDueSoonHour,
|
taskDueSoonHour = taskDueSoonHour,
|
||||||
taskOverdueHour = taskOverdueHour,
|
taskOverdueHour = taskOverdueHour,
|
||||||
warrantyExpiringHour = warrantyExpiringHour
|
warrantyExpiringHour = warrantyExpiringHour,
|
||||||
|
dailyDigestHour = dailyDigestHour
|
||||||
)
|
)
|
||||||
val result = APILayer.updateNotificationPreferences(request)
|
val result = APILayer.updateNotificationPreferences(request)
|
||||||
_updateState.value = when (result) {
|
_updateState.value = when (result) {
|
||||||
|
|||||||
@@ -547,6 +547,8 @@ enum L10n {
|
|||||||
static var propertySharedDescription: String { String(localized: "profile_property_shared_description") }
|
static var propertySharedDescription: String { String(localized: "profile_property_shared_description") }
|
||||||
static var warrantyExpiring: String { String(localized: "profile_warranty_expiring") }
|
static var warrantyExpiring: String { String(localized: "profile_warranty_expiring") }
|
||||||
static var warrantyExpiringDescription: String { String(localized: "profile_warranty_expiring_description") }
|
static var warrantyExpiringDescription: String { String(localized: "profile_warranty_expiring_description") }
|
||||||
|
static var dailyDigest: String { String(localized: "profile_daily_digest") }
|
||||||
|
static var dailyDigestDescription: String { String(localized: "profile_daily_digest_description") }
|
||||||
static var otherNotifications: String { String(localized: "profile_other_notifications") }
|
static var otherNotifications: String { String(localized: "profile_other_notifications") }
|
||||||
|
|
||||||
// Email Notifications
|
// Email Notifications
|
||||||
|
|||||||
@@ -20543,6 +20543,136 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"profile_daily_digest" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"de" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Tägliche Zusammenfassung"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Daily Summary"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Resumen diario"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Résumé quotidien"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"it" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Riepilogo giornaliero"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ja" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "デイリーサマリー"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ko" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "일일 요약"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Dagelijkse samenvatting"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pt" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Resumo diário"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "每日摘要"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profile_daily_digest_description" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"de" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Tägliche Übersicht über fällige und überfällige Aufgaben"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Daily overview of tasks due and overdue"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Resumen diario de tareas pendientes y vencidas"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Aperçu quotidien des tâches à faire et en retard"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"it" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Panoramica giornaliera delle attività in scadenza e scadute"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ja" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "期限が近いタスクと期限切れのタスクの毎日の概要"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ko" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "마감 예정 및 지연된 작업의 일일 개요"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Dagelijks overzicht van taken die binnenkort verlopen en achterstallig zijn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pt" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Resumo diário de tarefas a vencer e atrasadas"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "即将到期和逾期任务的每日概览"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"properties_add_button" : {
|
"properties_add_button" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|||||||
@@ -217,6 +217,40 @@ struct NotificationPreferencesView: View {
|
|||||||
.onChange(of: viewModel.warrantyExpiring) { _, newValue in
|
.onChange(of: viewModel.warrantyExpiring) { _, newValue in
|
||||||
viewModel.updatePreference(warrantyExpiring: newValue)
|
viewModel.updatePreference(warrantyExpiring: newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Toggle(isOn: $viewModel.dailyDigest) {
|
||||||
|
Label {
|
||||||
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
Text(L10n.Profile.dailyDigest)
|
||||||
|
.foregroundColor(Color.appTextPrimary)
|
||||||
|
Text(L10n.Profile.dailyDigestDescription)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(Color.appTextSecondary)
|
||||||
|
}
|
||||||
|
} icon: {
|
||||||
|
Image(systemName: "list.bullet.clipboard.fill")
|
||||||
|
.foregroundColor(Color.appSecondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tint(Color.appPrimary)
|
||||||
|
.onChange(of: viewModel.dailyDigest) { _, newValue in
|
||||||
|
viewModel.updatePreference(dailyDigest: newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time picker for Daily Digest
|
||||||
|
if viewModel.dailyDigest {
|
||||||
|
NotificationTimePickerRow(
|
||||||
|
isEnabled: $viewModel.dailyDigestTimeEnabled,
|
||||||
|
selectedHour: $viewModel.dailyDigestHour,
|
||||||
|
onEnableCustomTime: {
|
||||||
|
viewModel.enableCustomTime(for: .dailyDigest)
|
||||||
|
},
|
||||||
|
onTimeChange: { hour in
|
||||||
|
viewModel.updateCustomTime(hour, for: .dailyDigest)
|
||||||
|
},
|
||||||
|
formatHour: viewModel.formatHour
|
||||||
|
)
|
||||||
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text(L10n.Profile.otherNotifications)
|
Text(L10n.Profile.otherNotifications)
|
||||||
}
|
}
|
||||||
@@ -278,17 +312,20 @@ class NotificationPreferencesViewModelWrapper: ObservableObject {
|
|||||||
@Published var taskAssigned: Bool = true
|
@Published var taskAssigned: Bool = true
|
||||||
@Published var residenceShared: Bool = true
|
@Published var residenceShared: Bool = true
|
||||||
@Published var warrantyExpiring: Bool = true
|
@Published var warrantyExpiring: Bool = true
|
||||||
|
@Published var dailyDigest: Bool = true
|
||||||
@Published var emailTaskCompleted: Bool = true
|
@Published var emailTaskCompleted: Bool = true
|
||||||
|
|
||||||
// Custom notification times (local hours, 0-23)
|
// Custom notification times (local hours, 0-23)
|
||||||
@Published var taskDueSoonHour: Int? = nil
|
@Published var taskDueSoonHour: Int? = nil
|
||||||
@Published var taskOverdueHour: Int? = nil
|
@Published var taskOverdueHour: Int? = nil
|
||||||
@Published var warrantyExpiringHour: Int? = nil
|
@Published var warrantyExpiringHour: Int? = nil
|
||||||
|
@Published var dailyDigestHour: Int? = nil
|
||||||
|
|
||||||
// Track if user has enabled custom times
|
// Track if user has enabled custom times
|
||||||
@Published var taskDueSoonTimeEnabled: Bool = false
|
@Published var taskDueSoonTimeEnabled: Bool = false
|
||||||
@Published var taskOverdueTimeEnabled: Bool = false
|
@Published var taskOverdueTimeEnabled: Bool = false
|
||||||
@Published var warrantyExpiringTimeEnabled: Bool = false
|
@Published var warrantyExpiringTimeEnabled: Bool = false
|
||||||
|
@Published var dailyDigestTimeEnabled: Bool = false
|
||||||
|
|
||||||
@Published var isLoading: Bool = false
|
@Published var isLoading: Bool = false
|
||||||
@Published var errorMessage: String?
|
@Published var errorMessage: String?
|
||||||
@@ -298,6 +335,7 @@ class NotificationPreferencesViewModelWrapper: ObservableObject {
|
|||||||
private let defaultTaskDueSoonLocalHour = 14 // 2 PM local
|
private let defaultTaskDueSoonLocalHour = 14 // 2 PM local
|
||||||
private let defaultTaskOverdueLocalHour = 9 // 9 AM local
|
private let defaultTaskOverdueLocalHour = 9 // 9 AM local
|
||||||
private let defaultWarrantyExpiringLocalHour = 10 // 10 AM local
|
private let defaultWarrantyExpiringLocalHour = 10 // 10 AM local
|
||||||
|
private let defaultDailyDigestLocalHour = 8 // 8 AM local
|
||||||
|
|
||||||
func loadPreferences() {
|
func loadPreferences() {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
@@ -314,6 +352,7 @@ class NotificationPreferencesViewModelWrapper: ObservableObject {
|
|||||||
self.taskAssigned = prefs.taskAssigned
|
self.taskAssigned = prefs.taskAssigned
|
||||||
self.residenceShared = prefs.residenceShared
|
self.residenceShared = prefs.residenceShared
|
||||||
self.warrantyExpiring = prefs.warrantyExpiring
|
self.warrantyExpiring = prefs.warrantyExpiring
|
||||||
|
self.dailyDigest = prefs.dailyDigest
|
||||||
self.emailTaskCompleted = prefs.emailTaskCompleted
|
self.emailTaskCompleted = prefs.emailTaskCompleted
|
||||||
|
|
||||||
// Load custom notification times (convert from UTC to local)
|
// Load custom notification times (convert from UTC to local)
|
||||||
@@ -329,6 +368,10 @@ class NotificationPreferencesViewModelWrapper: ObservableObject {
|
|||||||
self.warrantyExpiringHour = DateUtils.utcHourToLocal(Int(utcHour))
|
self.warrantyExpiringHour = DateUtils.utcHourToLocal(Int(utcHour))
|
||||||
self.warrantyExpiringTimeEnabled = true
|
self.warrantyExpiringTimeEnabled = true
|
||||||
}
|
}
|
||||||
|
if let utcHour = prefs.dailyDigestHour?.intValue {
|
||||||
|
self.dailyDigestHour = DateUtils.utcHourToLocal(Int(utcHour))
|
||||||
|
self.dailyDigestTimeEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
self.errorMessage = nil
|
self.errorMessage = nil
|
||||||
@@ -350,10 +393,12 @@ class NotificationPreferencesViewModelWrapper: ObservableObject {
|
|||||||
taskAssigned: Bool? = nil,
|
taskAssigned: Bool? = nil,
|
||||||
residenceShared: Bool? = nil,
|
residenceShared: Bool? = nil,
|
||||||
warrantyExpiring: Bool? = nil,
|
warrantyExpiring: Bool? = nil,
|
||||||
|
dailyDigest: Bool? = nil,
|
||||||
emailTaskCompleted: Bool? = nil,
|
emailTaskCompleted: Bool? = nil,
|
||||||
taskDueSoonHour: Int? = nil,
|
taskDueSoonHour: Int? = nil,
|
||||||
taskOverdueHour: Int? = nil,
|
taskOverdueHour: Int? = nil,
|
||||||
warrantyExpiringHour: Int? = nil
|
warrantyExpiringHour: Int? = nil,
|
||||||
|
dailyDigestHour: Int? = nil
|
||||||
) {
|
) {
|
||||||
isSaving = true
|
isSaving = true
|
||||||
|
|
||||||
@@ -363,6 +408,7 @@ class NotificationPreferencesViewModelWrapper: ObservableObject {
|
|||||||
let taskDueSoonUtc = taskDueSoonHour.map { DateUtils.localHourToUtc($0) }
|
let taskDueSoonUtc = taskDueSoonHour.map { DateUtils.localHourToUtc($0) }
|
||||||
let taskOverdueUtc = taskOverdueHour.map { DateUtils.localHourToUtc($0) }
|
let taskOverdueUtc = taskOverdueHour.map { DateUtils.localHourToUtc($0) }
|
||||||
let warrantyExpiringUtc = warrantyExpiringHour.map { DateUtils.localHourToUtc($0) }
|
let warrantyExpiringUtc = warrantyExpiringHour.map { DateUtils.localHourToUtc($0) }
|
||||||
|
let dailyDigestUtc = dailyDigestHour.map { DateUtils.localHourToUtc($0) }
|
||||||
|
|
||||||
let request = UpdateNotificationPreferencesRequest(
|
let request = UpdateNotificationPreferencesRequest(
|
||||||
taskDueSoon: taskDueSoon.map { KotlinBoolean(bool: $0) },
|
taskDueSoon: taskDueSoon.map { KotlinBoolean(bool: $0) },
|
||||||
@@ -371,10 +417,12 @@ class NotificationPreferencesViewModelWrapper: ObservableObject {
|
|||||||
taskAssigned: taskAssigned.map { KotlinBoolean(bool: $0) },
|
taskAssigned: taskAssigned.map { KotlinBoolean(bool: $0) },
|
||||||
residenceShared: residenceShared.map { KotlinBoolean(bool: $0) },
|
residenceShared: residenceShared.map { KotlinBoolean(bool: $0) },
|
||||||
warrantyExpiring: warrantyExpiring.map { KotlinBoolean(bool: $0) },
|
warrantyExpiring: warrantyExpiring.map { KotlinBoolean(bool: $0) },
|
||||||
|
dailyDigest: dailyDigest.map { KotlinBoolean(bool: $0) },
|
||||||
emailTaskCompleted: emailTaskCompleted.map { KotlinBoolean(bool: $0) },
|
emailTaskCompleted: emailTaskCompleted.map { KotlinBoolean(bool: $0) },
|
||||||
taskDueSoonHour: taskDueSoonUtc.map { KotlinInt(int: Int32($0)) },
|
taskDueSoonHour: taskDueSoonUtc.map { KotlinInt(int: Int32($0)) },
|
||||||
taskOverdueHour: taskOverdueUtc.map { KotlinInt(int: Int32($0)) },
|
taskOverdueHour: taskOverdueUtc.map { KotlinInt(int: Int32($0)) },
|
||||||
warrantyExpiringHour: warrantyExpiringUtc.map { KotlinInt(int: Int32($0)) }
|
warrantyExpiringHour: warrantyExpiringUtc.map { KotlinInt(int: Int32($0)) },
|
||||||
|
dailyDigestHour: dailyDigestUtc.map { KotlinInt(int: Int32($0)) }
|
||||||
)
|
)
|
||||||
let result = try await APILayer.shared.updateNotificationPreferences(request: request)
|
let result = try await APILayer.shared.updateNotificationPreferences(request: request)
|
||||||
|
|
||||||
@@ -406,6 +454,10 @@ class NotificationPreferencesViewModelWrapper: ObservableObject {
|
|||||||
warrantyExpiringHour = defaultWarrantyExpiringLocalHour
|
warrantyExpiringHour = defaultWarrantyExpiringLocalHour
|
||||||
warrantyExpiringTimeEnabled = true
|
warrantyExpiringTimeEnabled = true
|
||||||
updatePreference(warrantyExpiringHour: defaultWarrantyExpiringLocalHour)
|
updatePreference(warrantyExpiringHour: defaultWarrantyExpiringLocalHour)
|
||||||
|
case .dailyDigest:
|
||||||
|
dailyDigestHour = defaultDailyDigestLocalHour
|
||||||
|
dailyDigestTimeEnabled = true
|
||||||
|
updatePreference(dailyDigestHour: defaultDailyDigestLocalHour)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,6 +473,9 @@ class NotificationPreferencesViewModelWrapper: ObservableObject {
|
|||||||
case .warrantyExpiring:
|
case .warrantyExpiring:
|
||||||
warrantyExpiringHour = hour
|
warrantyExpiringHour = hour
|
||||||
updatePreference(warrantyExpiringHour: hour)
|
updatePreference(warrantyExpiringHour: hour)
|
||||||
|
case .dailyDigest:
|
||||||
|
dailyDigestHour = hour
|
||||||
|
updatePreference(dailyDigestHour: hour)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,6 +483,7 @@ class NotificationPreferencesViewModelWrapper: ObservableObject {
|
|||||||
case taskDueSoon
|
case taskDueSoon
|
||||||
case taskOverdue
|
case taskOverdue
|
||||||
case warrantyExpiring
|
case warrantyExpiring
|
||||||
|
case dailyDigest
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format hour to display string
|
// Format hour to display string
|
||||||
|
|||||||
Reference in New Issue
Block a user