@@ -148,6 +148,7 @@ private void OnOptionsChangedExternally()
148148 OnPropertyChanged ( nameof ( ShouldShowCircuitVisitToggle ) ) ;
149149 OnPropertyChanged ( nameof ( AllowCountUpDownToggle ) ) ;
150150 OnPropertyChanged ( nameof ( ShowUpDownButton ) ) ;
151+ OnPropertyChanged ( nameof ( ShowExportScheduleButton ) ) ;
151152
152153 // If OperatingMode or MidWeekOrWeekend changed, the entire talk
153154 // schedule needs rebuilding (different mode = different talk list).
@@ -164,6 +165,13 @@ private void OnOptionsChangedExternally()
164165 RefreshTalks ( ) ;
165166 }
166167
168+ // Directly refresh the timer output VM so display-mode, clock
169+ // format, and all visual settings update on the output window.
170+ // This bypasses the OptionsChanged event subscription (which
171+ // was unreliable) and ensures the refresh runs on the UI thread.
172+ var timerOutputViewModel = CommunityToolkit . Mvvm . DependencyInjection . Ioc . Default . GetService < TimerOutputViewModel > ( ) ;
173+ timerOutputViewModel ? . RefreshSettings ( ) ;
174+
167175 // Apply AlwaysOnTop + FullScreenMode + MonitorId to the output window
168176 ApplyWindowStateOptionsLive ( ) ;
169177 } ) ;
@@ -323,6 +331,8 @@ public string BellTooltip
323331 public bool IsManualMode => _optionsService . OperatingMode == OperatingMode . Manual ;
324332 public bool IsNotManualMode => ! IsManualMode ;
325333 public bool IsAutoMode => _optionsService . OperatingMode == OperatingMode . Automatic ;
334+ public bool IsFileBasedMode => _optionsService . OperatingMode == OperatingMode . ScheduleFile ;
335+ public bool ShowExportScheduleButton => _optionsService . GetOptions ( ) . ShowExportScheduleButton ;
326336
327337 // Circuit visit
328338 public bool IsCircuitVisit
@@ -579,6 +589,13 @@ public void RefreshTalks()
579589 OnPropertyChanged ( nameof ( IsManualMode ) ) ;
580590 OnPropertyChanged ( nameof ( IsNotManualMode ) ) ;
581591 OnPropertyChanged ( nameof ( IsAutoMode ) ) ;
592+ OnPropertyChanged ( nameof ( IsFileBasedMode ) ) ;
593+
594+ // Refresh the schedule-file picker (may have new templates)
595+ if ( IsFileBasedMode )
596+ {
597+ RefreshScheduleFiles ( ) ;
598+ }
582599
583600 // Refresh timer output settings (e.g., analog/digital clock switch)
584601 var timerOutputViewModel = CommunityToolkit . Mvvm . DependencyInjection . Ioc . Default . GetService < TimerOutputViewModel > ( ) ;
@@ -1222,6 +1239,89 @@ private void SkipTalk()
12221239
12231240 private bool CanSkipTalk ( ) => ! IsRunning && ! IsPaused && SelectedTalk != null ;
12241241
1242+ // --- File-based schedule picker + export ---
1243+
1244+ [ ObservableProperty ]
1245+ private ObservableCollection < string > _scheduleFiles = [ ] ;
1246+
1247+ [ ObservableProperty ]
1248+ private string ? _selectedScheduleFile ;
1249+
1250+ partial void OnSelectedScheduleFileChanged ( string ? value )
1251+ {
1252+ if ( value == null ) return ;
1253+ var options = _optionsService . GetOptions ( ) ;
1254+ options . SelectedScheduleFile = value ;
1255+ _optionsService . SaveOptions ( options ) ;
1256+ RefreshTalks ( ) ;
1257+ }
1258+
1259+ public void RefreshScheduleFiles ( )
1260+ {
1261+ var files = Utils . ScheduleExporter . GetAvailableTemplates ( ) ;
1262+
1263+ // First-use seeding: if there are no templates yet and we have
1264+ // talks loaded (from a previous Auto/Manual session), export
1265+ // the current schedule as a starting template so the user has
1266+ // something to work with immediately.
1267+ if ( files . Length == 0 && Talks . Count > 0 )
1268+ {
1269+ var seedPath = System . IO . Path . Combine (
1270+ Utils . FileUtils . GetScheduleTemplatesFolder ( ) , "default.xml" ) ;
1271+ Utils . ScheduleExporter . Export ( Talks , seedPath ) ;
1272+ files = Utils . ScheduleExporter . GetAvailableTemplates ( ) ;
1273+ }
1274+
1275+ ScheduleFiles . Clear ( ) ;
1276+ foreach ( var f in files )
1277+ {
1278+ ScheduleFiles . Add ( System . IO . Path . GetFileName ( f ) ) ;
1279+ }
1280+
1281+ var current = _optionsService . GetOptions ( ) . SelectedScheduleFile ;
1282+ if ( ! string . IsNullOrEmpty ( current ) && ScheduleFiles . Contains ( current ) )
1283+ {
1284+ SelectedScheduleFile = current ;
1285+ }
1286+ else if ( ScheduleFiles . Count > 0 )
1287+ {
1288+ SelectedScheduleFile = ScheduleFiles [ 0 ] ;
1289+ }
1290+
1291+ OnPropertyChanged ( nameof ( IsFileBasedMode ) ) ;
1292+ }
1293+
1294+ [ RelayCommand ]
1295+ private void ExportScheduleAsTemplate ( )
1296+ {
1297+ var folder = Utils . FileUtils . GetScheduleTemplatesFolder ( ) ;
1298+ var talks = Talks ;
1299+
1300+ if ( talks . Count == 0 )
1301+ {
1302+ StatusText = "No talks to export" ;
1303+ return ;
1304+ }
1305+
1306+ // Generate a unique filename based on the current mode/meeting type
1307+ var prefix = _optionsService . OperatingMode switch
1308+ {
1309+ OperatingMode . Automatic => _optionsService . MidWeekOrWeekend == MidWeekOrWeekend . MidWeek ? "midweek" : "weekend" ,
1310+ OperatingMode . Manual => "manual" ,
1311+ OperatingMode . ScheduleFile => "custom" ,
1312+ _ => "schedule"
1313+ } ;
1314+
1315+ var timestamp = System . DateTime . Now . ToString ( "yyyyMMdd-HHmmss" ) ;
1316+ var filename = $ "{ prefix } -{ timestamp } .xml";
1317+ var path = System . IO . Path . Combine ( folder , filename ) ;
1318+
1319+ Utils . ScheduleExporter . Export ( talks , path ) ;
1320+ StatusText = $ "Saved: { filename } ";
1321+
1322+ RefreshScheduleFiles ( ) ;
1323+ }
1324+
12251325 // Quick-set: user types minutes in manual mode and presses Enter.
12261326 [ ObservableProperty ]
12271327 private decimal _quickSetMinutes = 5 ;
0 commit comments