1+ #nullable enable
2+
13using Bible . Alarm . Common . Interfaces . UI ;
24using Bible . Alarm . Models . Schedule ;
35using Bible . Alarm . Platforms . Windows . Helpers ;
46using Bible . Alarm . Platforms . Windows . Services . Handlers ;
5- // Removed UWP toast notification APIs - using WinUI 3 alternatives
7+ using System . Linq ;
8+ using Windows . ApplicationModel ;
9+ using Windows . Data . Xml . Dom ;
10+ using Windows . UI . Notifications ;
611
712namespace Bible . Alarm . Platforms . Windows . Services . UI
813{
@@ -18,37 +23,235 @@ public async Task ShowNotificationAsync(int scheduleId)
1823 public Task ScheduleNotificationAsync ( AlarmSchedule schedule ,
1924 string title , string body )
2025 {
21- // For WinUI 3 desktop apps, we can't use UWP toast notifications
22- // This functionality would need to be implemented using alternative approaches
23- // such as Windows Task Scheduler, Windows Notifications API, or a custom solution
24- // For now, we'll return a completed task without scheduling
25- // TODO: Implement proper notification scheduling for WinUI 3 desktop apps
26+ try
27+ {
28+ var scheduleId = schedule . Id ;
29+ var time = schedule . NextFireDate ( ) ;
30+
31+ if ( time <= DateTimeOffset . Now )
32+ {
33+ Serilog . Log . Warning ( "Cannot schedule notification for schedule {ScheduleId}: time {Time} is in the past" , scheduleId , time ) ;
34+ return Task . CompletedTask ;
35+ }
36+
37+ Serilog . Log . Information ( "Scheduling notification for schedule {ScheduleId} at {Time}" , scheduleId , time ) ;
38+
39+ var notifier = GetToastNotifier ( ) ;
40+ if ( notifier == null )
41+ {
42+ Serilog . Log . Error ( "Failed to create toast notifier for schedule {ScheduleId}. App may not be properly registered for notifications." , scheduleId ) ;
43+ return Task . CompletedTask ;
44+ }
45+
46+ var toast = CreateScheduledToast ( scheduleId , title , body , time ) ;
47+ notifier . AddToSchedule ( toast ) ;
48+
49+ if ( IsNotificationScheduled ( notifier , scheduleId ) )
50+ {
51+ Serilog . Log . Information ( "Successfully scheduled notification for schedule {ScheduleId} at {Time}" , scheduleId , time ) ;
52+ }
53+ else
54+ {
55+ Serilog . Log . Warning ( "Notification may not have been scheduled for schedule {ScheduleId}. Check Windows notification settings." , scheduleId ) ;
56+ }
57+ }
58+ catch ( Exception ex )
59+ {
60+ Serilog . Log . Error ( ex , "Error scheduling notification for schedule {ScheduleId}" , schedule . Id ) ;
61+ }
62+
2663 return Task . CompletedTask ;
2764 }
2865
2966 public Task RemoveAsync ( int scheduleId )
3067 {
31- // For WinUI 3 desktop apps, we can't use UWP toast notifications
32- // This functionality would need to be implemented using alternative approaches
33- // For now, we'll return a completed task without removing
34- // TODO: Implement proper notification removal for WinUI 3 desktop apps
68+ try
69+ {
70+ var notifier = GetToastNotifier ( ) ;
71+ if ( notifier == null ) return Task . CompletedTask ;
72+
73+ var toRemove = FindScheduledToast ( notifier , scheduleId ) ;
74+ if ( toRemove != null )
75+ {
76+ notifier . RemoveFromSchedule ( toRemove ) ;
77+ }
78+ }
79+ catch ( Exception ex )
80+ {
81+ Serilog . Log . Error ( ex , "Error removing notification for schedule {ScheduleId}" , scheduleId ) ;
82+ }
83+
3584 return Task . CompletedTask ;
3685 }
3786
3887 public Task < bool > IsScheduledAsync ( int scheduleId )
3988 {
40- // For WinUI 3 desktop apps, we can't use UWP toast notifications
41- // This functionality would need to be implemented using alternative approaches
42- // For now, we'll return false
43- // TODO: Implement proper notification checking for WinUI 3 desktop apps
44- return Task . FromResult ( false ) ;
89+ try
90+ {
91+ var notifier = GetToastNotifier ( ) ;
92+ if ( notifier == null ) return Task . FromResult ( false ) ;
93+
94+ return Task . FromResult ( IsNotificationScheduled ( notifier , scheduleId ) ) ;
95+ }
96+ catch ( Exception ex )
97+ {
98+ Serilog . Log . Error ( ex , "Error checking if notification is scheduled for schedule {ScheduleId}" , scheduleId ) ;
99+ return Task . FromResult ( false ) ;
100+ }
45101 }
46102
47103 public Task < bool > CanScheduleAsync ( )
48104 {
49105 return Task . FromResult ( WindowsBootstrapHelper . IsBackgroundTaskEnabled ) ;
50106 }
51107
108+ private static ScheduledToastNotification CreateScheduledToast ( int scheduleId , string title , string body , DateTimeOffset time )
109+ {
110+ var toastXml = CreateToastXml ( title , body , scheduleId ) ;
111+ return new ScheduledToastNotification ( toastXml , time )
112+ {
113+ Id = scheduleId . ToString ( )
114+ } ;
115+ }
116+
117+ private static XmlDocument CreateToastXml ( string title , string body , int scheduleId )
118+ {
119+ var toastXml = ToastNotificationManager . GetTemplateContent ( ToastTemplateType . ToastText02 ) ;
120+
121+ var textElements = toastXml . GetElementsByTagName ( "text" ) ;
122+ if ( textElements . Length > 0 )
123+ {
124+ textElements [ 0 ] . AppendChild ( toastXml . CreateTextNode ( title ) ) ;
125+ }
126+
127+ if ( textElements . Length > 1 )
128+ {
129+ textElements [ 1 ] . AppendChild ( toastXml . CreateTextNode ( body ) ) ;
130+ }
131+
132+ var toastNode = toastXml . SelectSingleNode ( "/toast" ) ;
133+ if ( toastNode ? . Attributes != null )
134+ {
135+ var launchAttribute = toastXml . CreateAttribute ( "launch" ) ;
136+ launchAttribute . Value = scheduleId . ToString ( ) ;
137+ toastNode . Attributes . SetNamedItem ( launchAttribute ) ;
138+ }
139+
140+ var audioNode = toastXml . CreateElement ( "audio" ) ;
141+ audioNode . SetAttribute ( "src" , "ms-winsoundevent:Notification.Default" ) ;
142+ toastNode ? . AppendChild ( audioNode ) ;
143+
144+ return toastXml ;
145+ }
146+
147+ private static bool IsNotificationScheduled ( ToastNotifier notifier , int scheduleId )
148+ {
149+ var scheduledToasts = notifier . GetScheduledToastNotifications ( ) ;
150+ return scheduledToasts . Any ( t => t . Id == scheduleId . ToString ( ) ) ;
151+ }
152+
153+ private static ScheduledToastNotification ? FindScheduledToast ( ToastNotifier notifier , int scheduleId )
154+ {
155+ var scheduledToasts = notifier . GetScheduledToastNotifications ( ) ;
156+ return scheduledToasts . FirstOrDefault ( t => t . Id == scheduleId . ToString ( ) ) ;
157+ }
158+
159+ private static ToastNotifier ? GetToastNotifier ( )
160+ {
161+ var notifier = TryCreateNotifierWithoutParameters ( ) ;
162+ if ( notifier != null ) return notifier ;
163+
164+ notifier = TryCreateNotifierWithAumid ( ) ;
165+ if ( notifier != null ) return notifier ;
166+
167+ Serilog . Log . Error (
168+ "Unable to create toast notifier. Scheduled notifications will not work. " +
169+ "This is common in debug mode. Try running the app from an installed package instead of Visual Studio." ) ;
170+
171+ return null ;
172+ }
173+
174+ private static ToastNotifier ? TryCreateNotifierWithoutParameters ( )
175+ {
176+ try
177+ {
178+ Serilog . Log . Debug ( "Attempting to create toast notifier without parameters..." ) ;
179+ var notifier = ToastNotificationManager . CreateToastNotifier ( ) ;
180+ if ( notifier != null )
181+ {
182+ Serilog . Log . Debug ( "Successfully created toast notifier without parameters" ) ;
183+ return notifier ;
184+ }
185+ Serilog . Log . Warning ( "ToastNotificationManager.CreateToastNotifier() returned null" ) ;
186+ }
187+ catch ( System . Runtime . InteropServices . COMException ex ) when ( ex . HResult == unchecked ( ( int ) 0x80070490 ) )
188+ {
189+ Serilog . Log . Warning ( "Failed to create toast notifier without parameters (0x80070490). Trying with AUMID..." ) ;
190+ }
191+ catch ( Exception ex )
192+ {
193+ Serilog . Log . Warning ( ex , "Exception creating toast notifier without parameters. HResult: 0x{HR:X8}" , ex . HResult ) ;
194+ }
195+ return null ;
196+ }
197+
198+ private static ToastNotifier ? TryCreateNotifierWithAumid ( )
199+ {
200+ try
201+ {
202+ var package = Package . Current ;
203+ var packageId = package . Id ;
204+
205+ var aumidFormats = new [ ]
206+ {
207+ $ "{ packageId . FamilyName } !App",
208+ packageId . FamilyName ,
209+ packageId . Name ,
210+ } ;
211+
212+ foreach ( var aumid in aumidFormats )
213+ {
214+ var notifier = TryCreateNotifierWithAumid ( aumid ) ;
215+ if ( notifier != null ) return notifier ;
216+ }
217+
218+ Serilog . Log . Warning (
219+ "Failed to create toast notifier with any AUMID format. " +
220+ "Package: {PackageName}, FamilyName: {FamilyName}, Publisher: {Publisher}" ,
221+ packageId . Name , packageId . FamilyName , packageId . Publisher ) ;
222+ }
223+ catch ( InvalidOperationException )
224+ {
225+ Serilog . Log . Warning (
226+ "Package.Current is not available. This is expected in debug mode or unpackaged WinUI 3 apps. " +
227+ "Scheduled notifications require the app to be properly packaged and installed." ) ;
228+ }
229+ catch ( Exception ex )
230+ {
231+ Serilog . Log . Error ( ex , "Exception while trying to create toast notifier with AUMID" ) ;
232+ }
233+ return null ;
234+ }
235+
236+ private static ToastNotifier ? TryCreateNotifierWithAumid ( string aumid )
237+ {
238+ try
239+ {
240+ Serilog . Log . Debug ( "Trying to create toast notifier with AUMID: {AUMID}" , aumid ) ;
241+ var notifier = ToastNotificationManager . CreateToastNotifier ( aumid ) ;
242+ if ( notifier != null )
243+ {
244+ Serilog . Log . Information ( "Successfully created toast notifier with AUMID: {AUMID}" , aumid ) ;
245+ return notifier ;
246+ }
247+ }
248+ catch ( Exception ex )
249+ {
250+ Serilog . Log . Debug ( ex , "Failed to create toast notifier with AUMID '{AUMID}'. HResult: 0x{HR:X8}" , aumid , ex . HResult ) ;
251+ }
252+ return null ;
253+ }
254+
52255 public void Dispose ( )
53256 {
54257 }
0 commit comments