Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
b104bc3212
commit
07f75bd766
@ -2386,6 +2386,10 @@
|
|||||||
"comment" : "A hint that appears when a user taps on a day cell to explain that it will navigate to more details about that day.",
|
"comment" : "A hint that appears when a user taps on a day cell to explain that it will navigate to more details about that day.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"Test Notification" : {
|
||||||
|
"comment" : "Title of a test notification used to verify notification delivery.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Thank someone" : {
|
"Thank someone" : {
|
||||||
"comment" : "Habit title for a ritual preset focused on gratitude practice.",
|
"comment" : "Habit title for a ritual preset focused on gratitude practice.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -2409,6 +2413,10 @@
|
|||||||
"comment" : "A description displayed when a day has no habit completion data.",
|
"comment" : "A description displayed when a day has no habit completion data.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"This is a test notification to verify delivery." : {
|
||||||
|
"comment" : "Title and body of a test notification used to verify notification delivery.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"This ritual is not currently active" : {
|
"This ritual is not currently active" : {
|
||||||
"comment" : "A message displayed when a user views a past ritual and it is not currently active.",
|
"comment" : "A message displayed when a user views a past ritual and it is not currently active.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -2506,6 +2514,10 @@
|
|||||||
"comment" : "Accessibility label for a trend direction indicating an increase.",
|
"comment" : "Accessibility label for a trend direction indicating an increase.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"Trigger Test Notification (5s)" : {
|
||||||
|
"comment" : "Title of a settings option that triggers a test local notification.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Try a different search term" : {
|
"Try a different search term" : {
|
||||||
"comment" : "A description text displayed below the \"No icons found\" message in the icon picker sheet.",
|
"comment" : "A description text displayed below the \"No icons found\" message in the icon picker sheet.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
|
|||||||
@ -48,7 +48,7 @@ enum ReminderSlot: String, CaseIterable {
|
|||||||
/// Only schedules reminders for time slots that have active rituals.
|
/// Only schedules reminders for time slots that have active rituals.
|
||||||
@MainActor
|
@MainActor
|
||||||
@Observable
|
@Observable
|
||||||
final class ReminderScheduler {
|
final class ReminderScheduler: NSObject, UNUserNotificationCenterDelegate {
|
||||||
|
|
||||||
private(set) var authorizationStatus: UNAuthorizationStatus = .notDetermined
|
private(set) var authorizationStatus: UNAuthorizationStatus = .notDetermined
|
||||||
private(set) var scheduledSlots: Set<ReminderSlot> = []
|
private(set) var scheduledSlots: Set<ReminderSlot> = []
|
||||||
@ -69,10 +69,33 @@ final class ReminderScheduler {
|
|||||||
/// The rituals to base reminders on - set this from RitualStore
|
/// The rituals to base reminders on - set this from RitualStore
|
||||||
private var activeRituals: [Ritual] = []
|
private var activeRituals: [Ritual] = []
|
||||||
|
|
||||||
init() {
|
override init() {
|
||||||
|
super.init()
|
||||||
|
UNUserNotificationCenter.current().delegate = self
|
||||||
Task { await refreshAuthorizationStatus() }
|
Task { await refreshAuthorizationStatus() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - UNUserNotificationCenterDelegate
|
||||||
|
|
||||||
|
func userNotificationCenter(
|
||||||
|
_ center: UNUserNotificationCenter,
|
||||||
|
willPresent notification: UNNotification,
|
||||||
|
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
|
||||||
|
) {
|
||||||
|
// Show the notification even when the app is in the foreground
|
||||||
|
completionHandler([.banner, .list, .sound, .badge])
|
||||||
|
}
|
||||||
|
|
||||||
|
func userNotificationCenter(
|
||||||
|
_ center: UNUserNotificationCenter,
|
||||||
|
didReceive response: UNNotificationResponse,
|
||||||
|
withCompletionHandler completionHandler: @escaping () -> Void
|
||||||
|
) {
|
||||||
|
// Clear badge when user interacts with notification
|
||||||
|
clearBadge()
|
||||||
|
completionHandler()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Public API
|
// MARK: - Public API
|
||||||
|
|
||||||
/// Updates the scheduled reminders based on the current active rituals.
|
/// Updates the scheduled reminders based on the current active rituals.
|
||||||
@ -116,6 +139,28 @@ final class ReminderScheduler {
|
|||||||
UNUserNotificationCenter.current().setBadgeCount(0) { _ in }
|
UNUserNotificationCenter.current().setBadgeCount(0) { _ in }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Schedules a test notification to appear in 5 seconds.
|
||||||
|
func scheduleTestNotification() {
|
||||||
|
let content = UNMutableNotificationContent()
|
||||||
|
content.title = String(localized: "Test Notification")
|
||||||
|
content.body = String(localized: "This is a test notification to verify delivery.")
|
||||||
|
content.sound = .default
|
||||||
|
content.badge = 1
|
||||||
|
|
||||||
|
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
|
||||||
|
let request = UNNotificationRequest(
|
||||||
|
identifier: "rituals.test.notification",
|
||||||
|
content: content,
|
||||||
|
trigger: trigger
|
||||||
|
)
|
||||||
|
|
||||||
|
UNUserNotificationCenter.current().add(request) { error in
|
||||||
|
if let error = error {
|
||||||
|
print("Failed to schedule test notification: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Refreshes authorization status and reschedules if enabled.
|
/// Refreshes authorization status and reschedules if enabled.
|
||||||
func refreshStatus() async {
|
func refreshStatus() async {
|
||||||
await refreshAuthorizationStatus()
|
await refreshAuthorizationStatus()
|
||||||
|
|||||||
@ -72,6 +72,7 @@ struct RootView: View {
|
|||||||
.background(AppSurface.primary.ignoresSafeArea())
|
.background(AppSurface.primary.ignoresSafeArea())
|
||||||
.onChange(of: scenePhase) { _, newPhase in
|
.onChange(of: scenePhase) { _, newPhase in
|
||||||
if newPhase == .active {
|
if newPhase == .active {
|
||||||
|
store.reminderScheduler.clearBadge()
|
||||||
refreshCurrentTab()
|
refreshCurrentTab()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -154,6 +154,14 @@ struct SettingsView: View {
|
|||||||
) {
|
) {
|
||||||
ritualStore.clearAllCompletions()
|
ritualStore.clearAllCompletions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsRow(
|
||||||
|
systemImage: "bell.badge",
|
||||||
|
title: String(localized: "Trigger Test Notification (5s)"),
|
||||||
|
iconColor: AppStatus.info
|
||||||
|
) {
|
||||||
|
ritualStore.reminderScheduler.scheduleTestNotification()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -6,6 +6,96 @@
|
|||||||
"platform" : "ios",
|
"platform" : "ios",
|
||||||
"size" : "1024x1024"
|
"size" : "1024x1024"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "20x20",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "20x20",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "29x29",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "29x29",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "40x40",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "40x40",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "60x60",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "60x60",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "20x20",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "20x20",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "29x29",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "29x29",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "40x40",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "40x40",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "76x76",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "76x76",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"size" : "83.5x83.5",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"size" : "1024x1024",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"appearances" : [
|
"appearances" : [
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user