Skip to content

Commit 2fb14be

Browse files
committed
Fix various UI bugs
1 parent 6f4281b commit 2fb14be

32 files changed

+259
-175
lines changed

src/Bible.Alarm/App.xaml.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,6 @@ private void UpdateThemeAwareColorResources()
138138
Resources["PrimaryTextColor"] = ThemeColors.PrimaryText.Get(theme);
139139
Resources["SelectedItemBackgroundColor"] = ThemeColors.SelectedItemBackground.Get(theme);
140140
Resources["DisabledTextColor"] = ThemeColors.DisabledText.Get(theme);
141-
Resources["PrimaryColor"] = ThemeColors.Primary.Get(theme);
142-
Resources["PrimaryLightColor"] = ThemeColors.PrimaryLight.Get(theme);
143141
}
144142
catch (Exception ex)
145143
{

src/Bible.Alarm/Common/Messenger/Messages.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,19 @@ public class SeekBackwardButtonPressedMessage
112112
public class DestroyMediaElementMessage
113113
{
114114
}
115+
116+
/// <summary>
117+
/// Message sent to show the progress bar on the home page.
118+
/// Used when next/prev buttons are clicked to indicate background activity.
119+
/// </summary>
120+
public class ShowProgressBarMessage
121+
{
122+
}
123+
124+
/// <summary>
125+
/// Message sent to hide the progress bar on the home page.
126+
/// Used when subtitle is updated after track change.
127+
/// </summary>
128+
public class HideProgressBarMessage
129+
{
130+
}

src/Bible.Alarm/Common/ThemeColors.cs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -103,21 +103,6 @@ public static class Primary
103103
public static Color SlateBlue => Color.FromArgb("#6A5ACD");
104104
public static Color LightPurple => Color.FromArgb("#9370DB");
105105
public static Color LightPurpleForDark => Color.FromArgb("#E8E0FF");
106-
107-
// Theme-aware primary color - lighter in dark mode for better contrast
108-
public static Color Dark => Color.FromArgb("#B8A0F0"); // Lighter purple for dark mode
109-
public static Color Light => Color.FromArgb("#6A5ACD"); // Original slate blue for light mode
110-
111-
public static Color Get(AppTheme theme) => theme == AppTheme.Dark ? Dark : Light;
112-
}
113-
114-
public static class PrimaryLight
115-
{
116-
// Theme-aware primary light color - used for highlights and lighter accents
117-
public static Color Dark => Color.FromArgb("#D0C0F8"); // Even lighter purple for dark mode highlights
118-
public static Color Light => Color.FromArgb("#9370DB"); // Original light purple for light mode
119-
120-
public static Color Get(AppTheme theme) => theme == AppTheme.Dark ? Dark : Light;
121106
}
122107

123108
// Day/Calendar Colors (used in converters)

src/Bible.Alarm/Services/UI/NavigationServiceHelpers/HomeNavigationHandler.cs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -98,18 +98,12 @@ private async Task PopToExistingHomeAsync(INavigation navigation, Home existingH
9898
}
9999
}
100100

101-
// Check if playback is active and hide home page if so
102-
// This prevents visual flash when app starts cold from Android Auto while playing
103-
if (ShouldHideHomePageOnStart())
104-
{
105-
existingHome.Opacity = 0.0;
106-
Logger.Information("Existing home page opacity set to 0.0 after pop (playback active)");
107-
}
108-
else
109-
{
110-
// Ensure home page is visible if playback is not active
111-
existingHome.Opacity = 1.0;
112-
}
101+
// NOTE: Do NOT change opacity when navigating back to an existing Home page.
102+
// The Home page's opacity is managed by AlarmModalService based on playback state.
103+
// Changing it here causes bugs:
104+
// - If playback is active: opacity = 0 makes Home blank when user returns from Schedule page
105+
// - The AlarmModalService will show/hide Home appropriately when playback starts/stops
106+
Logger.Information("Navigated back to existing home page (opacity unchanged)");
113107
}
114108

115109
private async Task PopToRootAndPushNewHomeAsync(INavigation navigation)

src/Bible.Alarm/ViewModels/HomeViewModel.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,17 @@
1212
using Bible.Alarm.Stores.Actions.Schedule;
1313
using Bible.Alarm.ViewModels.General;
1414
using Bible.Alarm.ViewModels.HomeViewModelHelpers;
15+
using Bible.Alarm.Common.Messenger;
1516
using CommunityToolkit.Mvvm.ComponentModel;
1617
using CommunityToolkit.Mvvm.Input;
18+
using CommunityToolkit.Mvvm.Messaging;
1719
using Fluxor;
1820
using Serilog;
1921
using IDispatcher = Fluxor.IDispatcher;
2022

2123
namespace Bible.Alarm.ViewModels;
2224

23-
public sealed class HomeViewModel : ObservableObject, IDisposable
25+
public sealed class HomeViewModel : ObservableObject, IDisposable, IRecipient<ShowProgressBarMessage>, IRecipient<HideProgressBarMessage>
2426
{
2527
private readonly ILogger logger;
2628
private readonly IServiceScopeFactory scopeFactory;
@@ -195,6 +197,10 @@ public HomeViewModel(
195197

196198
// Start tracking bootstrap ready state
197199
bootstrapReadyManager.StartTracking();
200+
201+
// Register for progress bar messages
202+
WeakReferenceMessenger.Default.Register<ShowProgressBarMessage>(this);
203+
WeakReferenceMessenger.Default.Register<HideProgressBarMessage>(this);
198204
}
199205

200206

@@ -373,9 +379,29 @@ private void OnStateChanged(object? sender, EventArgs e)
373379
});
374380
}
375381

382+
public void Receive(ShowProgressBarMessage message)
383+
{
384+
MainThread.BeginInvokeOnMainThread(() =>
385+
{
386+
// Show progress bar temporarily (doesn't affect normal visibility logic)
387+
progressBarManager.ShowTemporarily();
388+
});
389+
}
390+
391+
public void Receive(HideProgressBarMessage message)
392+
{
393+
MainThread.BeginInvokeOnMainThread(async () =>
394+
{
395+
// Hide progress bar temporarily (doesn't affect normal visibility logic)
396+
await progressBarManager.HideTemporarilyAsync();
397+
});
398+
}
399+
376400
public void Dispose()
377401
{
378402
state.StateChanged -= OnStateChanged;
403+
WeakReferenceMessenger.Default.Unregister<ShowProgressBarMessage>(this);
404+
WeakReferenceMessenger.Default.Unregister<HideProgressBarMessage>(this);
379405
bootstrapReadyManager.Dispose();
380406
progressBarManager.Dispose();
381407
scheduleViewModelManager.DisposeAll();

src/Bible.Alarm/ViewModels/HomeViewModelHelpers/CommandHandler.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ public ICommand CreateAddScheduleCommand()
5555

5656
public ICommand CreateViewScheduleCommand()
5757
{
58-
// Use AsyncRelayCommandOptions.AllowConcurrentExecutions to ensure taps aren't ignored
59-
// while a previous command is still completing
58+
// Do NOT use AllowConcurrentExecutions - this prevents double-tap from creating
59+
// multiple navigations that race with each other and corrupt ScheduleNavigationContext
6060
return new AsyncRelayCommand<ScheduleListItemViewModel>(async x =>
6161
{
6262
if (x == null || x.Schedule == null)
@@ -70,7 +70,7 @@ public ICommand CreateViewScheduleCommand()
7070
}
7171

7272
await showOverlayAndNavigateAsync(x);
73-
}, AsyncRelayCommandOptions.AllowConcurrentExecutions);
73+
});
7474
}
7575
}
7676

src/Bible.Alarm/ViewModels/HomeViewModelHelpers/ProgressBarManager.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ public void UpdateVisibility(bool isBusy, int? schedulesCount)
4949
ProgressBarOpacity = shouldShow ? 1.0 : 0.0;
5050
}
5151

52+
/// <summary>
53+
/// Temporarily shows the progress bar without affecting the shouldShowProgressBar flag.
54+
/// Used for showing progress during next/prev track operations.
55+
/// </summary>
56+
public void ShowTemporarily()
57+
{
58+
ProgressBarOpacity = 1.0;
59+
}
60+
5261
public async Task FadeOutAsync()
5362
{
5463
// Prevent showing progress bar again
@@ -75,6 +84,33 @@ public async Task FadeOutAsync()
7584
ProgressBarOpacity = 0.0;
7685
}
7786

87+
/// <summary>
88+
/// Hides the progress bar with fade animation without affecting the shouldShowProgressBar flag.
89+
/// Used for hiding progress after next/prev track operations complete.
90+
/// </summary>
91+
public async Task HideTemporarilyAsync()
92+
{
93+
// If already hidden, don't re-animate (would cause a flash)
94+
if (progressBarOpacity == 0)
95+
{
96+
return;
97+
}
98+
99+
const int fadeSteps = 10;
100+
const int fadeDurationMs = 200;
101+
const double stepDelay = fadeDurationMs / (double)fadeSteps;
102+
103+
// Fade from current opacity to 0 (not from 1.0)
104+
var startOpacity = progressBarOpacity;
105+
for (int i = fadeSteps; i >= 0; i--)
106+
{
107+
ProgressBarOpacity = startOpacity * i / fadeSteps;
108+
await Task.Delay((int)stepDelay);
109+
}
110+
111+
ProgressBarOpacity = 0.0;
112+
}
113+
78114
public void Reset()
79115
{
80116
shouldShowProgressBar = true;

src/Bible.Alarm/ViewModels/Schedule/NumberOfTrackContainerViewModel.cs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,8 @@ private void OnStateChanged(object? sender, EventArgs e)
231231
// Update label text properties
232232
OnPropertyChanged(nameof(HasSectionStructure));
233233
OnPropertyChanged(nameof(TrackLabelText));
234+
OnPropertyChanged(nameof(TracksLabelText));
235+
OnPropertyChanged(nameof(SelectedTracksText));
234236
OnPropertyChanged(nameof(ModalHeaderText));
235237
OnPropertyChanged(nameof(RestartLabelText));
236238

@@ -275,7 +277,7 @@ public NumberOfTracksListViewItemModel? CurrentNumberOfTracks
275277
{
276278
// Notify that the Text property (computed from CurrentNumberOfTracks) has changed
277279
OnPropertyChanged(nameof(CurrentNumberOfTracksText));
278-
OnPropertyChanged(nameof(TracksLabelText));
280+
OnPropertyChanged(nameof(SelectedTracksText));
279281
}
280282
}
281283
}
@@ -307,17 +309,30 @@ public bool HasSectionStructure
307309
public string TrackLabelText => HasSectionStructure ? "Chapters to play each time" : "Episodes to play each time";
308310

309311
/// <summary>
310-
/// Gets the combined label text showing the number with "chapters or episodes" format.
311-
/// Returns format like "3 chapters or episodes" (not bold).
312+
/// Gets the static label text for the tracks selection row.
313+
/// Returns "Number of chapters to play" for sectioned publications (Bible),
314+
/// or "Number of episodes to play" for non-sectioned publications (dramas).
312315
/// </summary>
313-
public string TracksLabelText
316+
public string TracksLabelText => HasSectionStructure ? "Number of chapters to play" : "Number of episodes to play";
317+
318+
/// <summary>
319+
/// Gets the dynamic selected value text showing the number with proper singular/plural.
320+
/// Returns format like "3 Chapters", "1 Chapter", "3 Episodes", or "1 Episode".
321+
/// </summary>
322+
public string SelectedTracksText
314323
{
315324
get
316325
{
317326
var number = CurrentNumberOfTracks?.Value ?? 0;
318327
if (number == 0)
319-
return "chapters or episodes";
320-
return $"{number} chapters or episodes";
328+
{
329+
return HasSectionStructure ? "Chapters" : "Episodes";
330+
}
331+
332+
var unitSingular = HasSectionStructure ? "Chapter" : "Episode";
333+
var unitPlural = HasSectionStructure ? "Chapters" : "Episodes";
334+
var selectedUnit = number == 1 ? unitSingular : unitPlural;
335+
return $"{number} {selectedUnit}";
321336
}
322337
}
323338

src/Bible.Alarm/ViewModels/ScheduleListItemViewModel.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ public bool IsEnabled
222222
OnPropertyChanged();
223223
if (!propertyManager.IsInitializing && Schedule != null)
224224
{
225+
// Show progress bar to indicate background activity
226+
WeakReferenceMessenger.Default.Send(new ShowProgressBarMessage());
225227
_ = HandleIsEnabledChanged(value);
226228
}
227229
}
@@ -251,6 +253,9 @@ await MainThread.InvokeOnMainThreadAsync(() =>
251253
propertyManager.IsEnabled = !attemptedValue;
252254
OnPropertyChanged(nameof(IsEnabled));
253255
OnPropertyChanged(nameof(This));
256+
257+
// Hide progress bar when operation fails and is reverted
258+
WeakReferenceMessenger.Default.Send(new HideProgressBarMessage());
254259
});
255260
}
256261

@@ -497,6 +502,9 @@ private void NotifyPropertyChanges(ScheduleListItemStateHandler.ScheduleChangeIn
497502
if (changeInfo.IsEnabledChanged)
498503
{
499504
OnPropertyChanged(nameof(IsEnabled));
505+
506+
// Hide progress bar when IsEnabled is updated (indicates toggle operation is complete)
507+
WeakReferenceMessenger.Default.Send(new HideProgressBarMessage());
500508
}
501509
if (changeInfo.NameChanged)
502510
{
@@ -521,6 +529,9 @@ private void NotifyPropertyChanges(ScheduleListItemStateHandler.ScheduleChangeIn
521529
ScheduleId);
522530
OnPropertyChanged(nameof(SubTitle));
523531
OnPropertyChanged(nameof(Language));
532+
533+
// Hide progress bar when subtitle is updated (indicates track change is complete)
534+
WeakReferenceMessenger.Default.Send(new HideProgressBarMessage());
524535
}
525536
});
526537
}

src/Bible.Alarm/ViewModels/ScheduleListItemViewModelHelpers/ScheduleListItemCommandHandler.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ public ICommand CreatePreviousCommand(AlarmSchedule? schedule)
7070
}
7171

7272
logger.Information("PreviousCommand: Moving to previous track for schedule {ScheduleId}", schedule.Id);
73+
74+
// Show progress bar to indicate background activity
75+
WeakReferenceMessenger.Default.Send(new ShowProgressBarMessage());
76+
7377
try
7478
{
7579
// Run database operations off UI thread
@@ -124,6 +128,10 @@ public ICommand CreateNextCommand(AlarmSchedule? schedule)
124128
}
125129

126130
logger.Information("NextCommand: Moving to next track for schedule {ScheduleId}", schedule.Id);
131+
132+
// Show progress bar to indicate background activity
133+
WeakReferenceMessenger.Default.Send(new ShowProgressBarMessage());
134+
127135
try
128136
{
129137
// Run database operations off UI thread

0 commit comments

Comments
 (0)