diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml
index 966eb75..778b2b4 100644
--- a/composeApp/src/commonMain/composeResources/values/strings.xml
+++ b/composeApp/src/commonMain/composeResources/values/strings.xml
@@ -390,6 +390,11 @@
Warranty Expiring
Reminders for expiring warranties
+
+ Email Notifications
+ Task Completed Email
+ Receive email when a task is completed
+
Save
Cancel
diff --git a/composeApp/src/commonMain/kotlin/com/example/casera/models/Notification.kt b/composeApp/src/commonMain/kotlin/com/example/casera/models/Notification.kt
index bbb6964..cbc1937 100644
--- a/composeApp/src/commonMain/kotlin/com/example/casera/models/Notification.kt
+++ b/composeApp/src/commonMain/kotlin/com/example/casera/models/Notification.kt
@@ -40,7 +40,10 @@ data class NotificationPreference(
@SerialName("residence_shared")
val residenceShared: Boolean = true,
@SerialName("warranty_expiring")
- val warrantyExpiring: Boolean = true
+ val warrantyExpiring: Boolean = true,
+ // Email preferences
+ @SerialName("email_task_completed")
+ val emailTaskCompleted: Boolean = true
)
@Serializable
@@ -56,7 +59,10 @@ data class UpdateNotificationPreferencesRequest(
@SerialName("residence_shared")
val residenceShared: Boolean? = null,
@SerialName("warranty_expiring")
- val warrantyExpiring: Boolean? = null
+ val warrantyExpiring: Boolean? = null,
+ // Email preferences
+ @SerialName("email_task_completed")
+ val emailTaskCompleted: Boolean? = null
)
@Serializable
diff --git a/composeApp/src/commonMain/kotlin/com/example/casera/ui/screens/NotificationPreferencesScreen.kt b/composeApp/src/commonMain/kotlin/com/example/casera/ui/screens/NotificationPreferencesScreen.kt
index 88580d8..40ea484 100644
--- a/composeApp/src/commonMain/kotlin/com/example/casera/ui/screens/NotificationPreferencesScreen.kt
+++ b/composeApp/src/commonMain/kotlin/com/example/casera/ui/screens/NotificationPreferencesScreen.kt
@@ -35,6 +35,7 @@ fun NotificationPreferencesScreen(
var taskAssigned by remember { mutableStateOf(true) }
var residenceShared by remember { mutableStateOf(true) }
var warrantyExpiring by remember { mutableStateOf(true) }
+ var emailTaskCompleted by remember { mutableStateOf(true) }
// Load preferences on first render
LaunchedEffect(Unit) {
@@ -51,6 +52,7 @@ fun NotificationPreferencesScreen(
taskAssigned = prefs.taskAssigned
residenceShared = prefs.residenceShared
warrantyExpiring = prefs.warrantyExpiring
+ emailTaskCompleted = prefs.emailTaskCompleted
}
}
@@ -294,6 +296,36 @@ fun NotificationPreferencesScreen(
}
}
+ // Email Notifications Section
+ Text(
+ stringResource(Res.string.notifications_email_section),
+ style = MaterialTheme.typography.titleMedium,
+ fontWeight = FontWeight.SemiBold,
+ modifier = Modifier.padding(top = AppSpacing.md)
+ )
+
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ shape = RoundedCornerShape(AppRadius.md),
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.surfaceVariant
+ )
+ ) {
+ Column {
+ NotificationToggle(
+ title = stringResource(Res.string.notifications_email_task_completed),
+ description = stringResource(Res.string.notifications_email_task_completed_desc),
+ icon = Icons.Default.Email,
+ iconTint = MaterialTheme.colorScheme.primary,
+ checked = emailTaskCompleted,
+ onCheckedChange = {
+ emailTaskCompleted = it
+ viewModel.updatePreference(emailTaskCompleted = it)
+ }
+ )
+ }
+ }
+
Spacer(modifier = Modifier.height(AppSpacing.xl))
}
}
diff --git a/composeApp/src/commonMain/kotlin/com/example/casera/viewmodel/NotificationPreferencesViewModel.kt b/composeApp/src/commonMain/kotlin/com/example/casera/viewmodel/NotificationPreferencesViewModel.kt
index 4747830..11b005a 100644
--- a/composeApp/src/commonMain/kotlin/com/example/casera/viewmodel/NotificationPreferencesViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/example/casera/viewmodel/NotificationPreferencesViewModel.kt
@@ -36,7 +36,8 @@ class NotificationPreferencesViewModel : ViewModel() {
taskCompleted: Boolean? = null,
taskAssigned: Boolean? = null,
residenceShared: Boolean? = null,
- warrantyExpiring: Boolean? = null
+ warrantyExpiring: Boolean? = null,
+ emailTaskCompleted: Boolean? = null
) {
viewModelScope.launch {
_updateState.value = ApiResult.Loading
@@ -46,7 +47,8 @@ class NotificationPreferencesViewModel : ViewModel() {
taskCompleted = taskCompleted,
taskAssigned = taskAssigned,
residenceShared = residenceShared,
- warrantyExpiring = warrantyExpiring
+ warrantyExpiring = warrantyExpiring,
+ emailTaskCompleted = emailTaskCompleted
)
val result = APILayer.updateNotificationPreferences(request)
_updateState.value = when (result) {
diff --git a/iosApp/iosApp/Helpers/L10n.swift b/iosApp/iosApp/Helpers/L10n.swift
index de92c3d..cea95ec 100644
--- a/iosApp/iosApp/Helpers/L10n.swift
+++ b/iosApp/iosApp/Helpers/L10n.swift
@@ -540,6 +540,11 @@ enum L10n {
static var warrantyExpiring: String { String(localized: "profile_warranty_expiring") }
static var warrantyExpiringDescription: String { String(localized: "profile_warranty_expiring_description") }
static var otherNotifications: String { String(localized: "profile_other_notifications") }
+
+ // Email Notifications
+ static var emailNotifications: String { String(localized: "profile_email_notifications") }
+ static var emailTaskCompleted: String { String(localized: "profile_email_task_completed") }
+ static var emailTaskCompletedDescription: String { String(localized: "profile_email_task_completed_description") }
}
// MARK: - Settings
diff --git a/iosApp/iosApp/Localizable.xcstrings b/iosApp/iosApp/Localizable.xcstrings
index 428fefc..8d1acbb 100644
--- a/iosApp/iosApp/Localizable.xcstrings
+++ b/iosApp/iosApp/Localizable.xcstrings
@@ -17844,6 +17844,17 @@
}
}
},
+ "profile_email_notifications" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Email Notifications"
+ }
+ }
+ }
+ },
"profile_email_required_unique" : {
"extractionState" : "manual",
"localizations" : {
@@ -17909,6 +17920,28 @@
}
}
},
+ "profile_email_task_completed" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Task Completed Email"
+ }
+ }
+ }
+ },
+ "profile_email_task_completed_description" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Receive email when a task is completed"
+ }
+ }
+ }
+ },
"profile_first_name" : {
"extractionState" : "manual",
"localizations" : {
diff --git a/iosApp/iosApp/Profile/NotificationPreferencesView.swift b/iosApp/iosApp/Profile/NotificationPreferencesView.swift
index d3c6c7a..6f87c87 100644
--- a/iosApp/iosApp/Profile/NotificationPreferencesView.swift
+++ b/iosApp/iosApp/Profile/NotificationPreferencesView.swift
@@ -191,6 +191,31 @@ struct NotificationPreferencesView: View {
Text(L10n.Profile.otherNotifications)
}
.listRowBackground(Color.appBackgroundSecondary)
+
+ // Email Notifications
+ Section {
+ Toggle(isOn: $viewModel.emailTaskCompleted) {
+ Label {
+ VStack(alignment: .leading, spacing: 2) {
+ Text(L10n.Profile.emailTaskCompleted)
+ .foregroundColor(Color.appTextPrimary)
+ Text(L10n.Profile.emailTaskCompletedDescription)
+ .font(.caption)
+ .foregroundColor(Color.appTextSecondary)
+ }
+ } icon: {
+ Image(systemName: "envelope.fill")
+ .foregroundColor(Color.appPrimary)
+ }
+ }
+ .tint(Color.appPrimary)
+ .onChange(of: viewModel.emailTaskCompleted) { _, newValue in
+ viewModel.updatePreference(emailTaskCompleted: newValue)
+ }
+ } header: {
+ Text(L10n.Profile.emailNotifications)
+ }
+ .listRowBackground(Color.appBackgroundSecondary)
}
}
.listStyle(.plain)
@@ -223,6 +248,7 @@ class NotificationPreferencesViewModelWrapper: ObservableObject {
@Published var taskAssigned: Bool = true
@Published var residenceShared: Bool = true
@Published var warrantyExpiring: Bool = true
+ @Published var emailTaskCompleted: Bool = true
@Published var isLoading: Bool = false
@Published var errorMessage: String?
@@ -243,6 +269,7 @@ class NotificationPreferencesViewModelWrapper: ObservableObject {
self.taskAssigned = prefs.taskAssigned
self.residenceShared = prefs.residenceShared
self.warrantyExpiring = prefs.warrantyExpiring
+ self.emailTaskCompleted = prefs.emailTaskCompleted
self.isLoading = false
self.errorMessage = nil
} else if let error = result as? ApiResultError {
@@ -262,7 +289,8 @@ class NotificationPreferencesViewModelWrapper: ObservableObject {
taskCompleted: Bool? = nil,
taskAssigned: Bool? = nil,
residenceShared: Bool? = nil,
- warrantyExpiring: Bool? = nil
+ warrantyExpiring: Bool? = nil,
+ emailTaskCompleted: Bool? = nil
) {
isSaving = true
@@ -274,7 +302,8 @@ class NotificationPreferencesViewModelWrapper: ObservableObject {
taskCompleted: taskCompleted.map { KotlinBoolean(bool: $0) },
taskAssigned: taskAssigned.map { KotlinBoolean(bool: $0) },
residenceShared: residenceShared.map { KotlinBoolean(bool: $0) },
- warrantyExpiring: warrantyExpiring.map { KotlinBoolean(bool: $0) }
+ warrantyExpiring: warrantyExpiring.map { KotlinBoolean(bool: $0) },
+ emailTaskCompleted: emailTaskCompleted.map { KotlinBoolean(bool: $0) }
)
let result = try await APILayer.shared.updateNotificationPreferences(request: request)