Skip to content

Commit 337e2a4

Browse files
committed
Redesign UI slightly
1 parent 8ab977d commit 337e2a4

35 files changed

+1048
-451
lines changed

src/Bible.Alarm/App.xaml.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
#nullable enable
22
using System.ComponentModel;
33
using Bible.Alarm.Common;
4+
using Bible.Alarm.Common.Messenger;
45
using Bible.Alarm.Services.UI;
56
using Bible.Alarm.Services.UI.Interfaces;
67
using Bible.Alarm.Views;
8+
using CommunityToolkit.Mvvm.Messaging;
79
using Serilog;
810

911
namespace Bible.Alarm;
@@ -149,5 +151,7 @@ private void OnRequestedThemeChanged(object? sender, AppThemeChangedEventArgs e)
149151
{
150152
UpdateThemeAwareColorResources();
151153
WindowSetupService.UpdateNavigationBarColors();
154+
// Notify ViewModels to update theme-aware bindings (e.g., day button colors)
155+
WeakReferenceMessenger.Default.Send(new ThemeChangedMessage());
152156
}
153157
}

src/Bible.Alarm/Common/Helpers/ServiceRegistrationHelper.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ private static void RegisterViewModels(IServiceCollection services)
307307
services.AddTransient<MusicSelectionContainerViewModel>();
308308
services.AddTransient<NumberOfTrackContainerViewModel>();
309309
services.AddTransient<ScheduleDetailsContainerViewModel>();
310+
services.AddTransient<AlarmSettingsContainerViewModel>();
310311

311312
// Register ScheduleListItem as transient for list items
312313
services.AddTransient<ScheduleListItemViewModel>();

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,12 @@ public class ShowProgressBarMessage
127127
/// </summary>
128128
public class HideProgressBarMessage
129129
{
130+
}
131+
132+
/// <summary>
133+
/// Message sent when the app theme changes (dark/light mode).
134+
/// ViewModels should listen to this and notify properties that use theme-aware converters.
135+
/// </summary>
136+
public class ThemeChangedMessage
137+
{
130138
}

src/Bible.Alarm/Common/ThemeColors.cs

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,74 @@ public static class Primary
105105
public static Color LightPurpleForDark => Color.FromArgb("#E8E0FF");
106106
}
107107

108-
// Day/Calendar Colors (used in converters)
108+
// Day/Calendar Colors (used in converters) - Theme-aware
109109
public static class Day
110110
{
111-
public static Color EnabledText => Colors.White;
112-
public static Color DisabledText => Color.FromArgb("#666666");
113-
// SlateBlue
114-
public static Color EnabledBackground => Color.FromArgb("#6A5ACD");
115-
public static Color DisabledBackground => Color.FromArgb("#C0C0C0");
116-
public static Color DefaultBackground => Color.FromArgb("#D0D0D0");
111+
// Text colors
112+
public static class EnabledText
113+
{
114+
public static Color Dark => Colors.White;
115+
public static Color Light => Colors.White;
116+
public static Color Get(AppTheme theme) => theme == AppTheme.Dark ? Dark : Light;
117+
}
118+
119+
public static class DisabledText
120+
{
121+
// Dark mode: lighter gray for better contrast on dark backgrounds
122+
public static Color Dark => Color.FromArgb("#B0B0B0");
123+
// Light mode: darker gray for better contrast on light backgrounds
124+
public static Color Light => Color.FromArgb("#666666");
125+
public static Color Get(AppTheme theme) => theme == AppTheme.Dark ? Dark : Light;
126+
}
127+
128+
// Background colors
129+
public static class EnabledBackground
130+
{
131+
// Schedule enabled + Day enabled: Primary color (prominent)
132+
public static Color Dark => Color.FromArgb("#9370DB"); // Lighter purple for dark mode
133+
public static Color Light => Color.FromArgb("#6A5ACD"); // SlateBlue for light mode
134+
public static Color Get(AppTheme theme) => theme == AppTheme.Dark ? Dark : Light;
135+
}
136+
137+
public static class DisabledBackground
138+
{
139+
// Schedule enabled + Day disabled: Neutral gray
140+
public static Color Dark => Color.FromArgb("#404040"); // Darker gray for dark mode
141+
public static Color Light => Color.FromArgb("#C0C0C0"); // Light gray for light mode
142+
public static Color Get(AppTheme theme) => theme == AppTheme.Dark ? Dark : Light;
143+
}
144+
145+
public static class DefaultBackground
146+
{
147+
// Schedule disabled + Day enabled: Muted primary to show day is selected but schedule is off
148+
public static Color Dark => Color.FromArgb("#4A3A6D"); // Muted purple for dark mode
149+
public static Color Light => Color.FromArgb("#D0D0D0"); // Light gray for light mode
150+
public static Color Get(AppTheme theme) => theme == AppTheme.Dark ? Dark : Light;
151+
}
152+
153+
public static class AlarmDisabledDayDisabledBackground
154+
{
155+
// Schedule disabled + Day disabled: More muted gray to distinguish from enabled schedule + disabled day
156+
public static Color Dark => Color.FromArgb("#2A2A2A"); // Darker, more muted gray for dark mode
157+
public static Color Light => Color.FromArgb("#E0E0E0"); // Lighter gray for light mode
158+
public static Color Get(AppTheme theme) => theme == AppTheme.Dark ? Dark : Light;
159+
}
160+
161+
// Legacy properties for backward compatibility (use Light theme as default)
162+
[Obsolete("Use EnabledText.Get(theme) instead")]
163+
public static Color EnabledTextLegacy => EnabledText.Light;
164+
165+
[Obsolete("Use DisabledText.Get(theme) instead")]
166+
public static Color DisabledTextLegacy => DisabledText.Light;
167+
168+
[Obsolete("Use EnabledBackground.Get(theme) instead")]
169+
public static Color EnabledBackgroundLegacy => EnabledBackground.Light;
170+
171+
[Obsolete("Use DisabledBackground.Get(theme) instead")]
172+
public static Color DisabledBackgroundLegacy => DisabledBackground.Light;
173+
174+
[Obsolete("Use DefaultBackground.Get(theme) instead")]
175+
public static Color DefaultBackgroundLegacy => DefaultBackground.Light;
117176
}
118177

119178
// Animation/Interaction Colors

src/Bible.Alarm/Common/ViewHelpers/Converters/DayBackgroundColorConverter.cs

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,35 @@
1+
using System.ComponentModel;
12
using System.Globalization;
23
using Bible.Alarm.Shared.Models.Enums;
34
using Bible.Alarm.ViewModels;
45

56
namespace Bible.Alarm.Common.ViewHelpers.Converters;
67

7-
public sealed class DayBackgroundColorConverter : IValueConverter, IMultiValueConverter
8+
public sealed class DayBackgroundColorConverter : IValueConverter, IMultiValueConverter, INotifyPropertyChanged
89
{
10+
public event PropertyChangedEventHandler? PropertyChanged;
11+
12+
public DayBackgroundColorConverter()
13+
{
14+
// Subscribe to theme changes when converter is instantiated
15+
if (Application.Current != null)
16+
{
17+
Application.Current.RequestedThemeChanged += OnRequestedThemeChanged;
18+
}
19+
}
20+
21+
private void OnRequestedThemeChanged(object? sender, AppThemeChangedEventArgs e)
22+
{
23+
// Notify that the converter output has changed, causing all bindings to re-evaluate
24+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(string.Empty));
25+
}
926
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
1027
{
28+
var theme = ThemeColors.GetCurrentTheme();
29+
1130
if (value is null)
1231
{
13-
return ThemeColors.Day.DefaultBackground;
32+
return ThemeColors.Day.DisabledBackground.Get(theme);
1433
}
1534

1635
DaysOfWeek daysOfWeek;
@@ -34,45 +53,70 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
3453
}
3554
else
3655
{
37-
return ThemeColors.Day.DefaultBackground;
56+
return ThemeColors.Day.DisabledBackground.Get(theme);
3857
}
3958

4059
var dayParameter = ParseDayParameter(parameter);
4160
var isDayEnabled = (daysOfWeek & dayParameter) == dayParameter;
4261

62+
// Four combinations (theme-aware):
4363
if (isEnabled)
4464
{
45-
// When alarm is enabled: dark background for enabled days, darker for disabled days
46-
return isDayEnabled ? ThemeColors.Day.EnabledBackground : ThemeColors.Day.DisabledBackground;
65+
// Schedule enabled
66+
return isDayEnabled
67+
? ThemeColors.Day.EnabledBackground.Get(theme)
68+
: ThemeColors.Day.DisabledBackground.Get(theme);
69+
}
70+
else
71+
{
72+
// Schedule disabled
73+
return isDayEnabled
74+
? ThemeColors.Day.DefaultBackground.Get(theme)
75+
: ThemeColors.Day.AlarmDisabledDayDisabledBackground.Get(theme);
4776
}
48-
49-
// When alarm is disabled: flip the colors - enabled days get blue, disabled days get gray
50-
return isDayEnabled ? ThemeColors.Day.EnabledBackground : ThemeColors.Day.DisabledBackground;
5177
}
5278

5379
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
5480
{
81+
var theme = ThemeColors.GetCurrentTheme();
82+
5583
if (values == null || values.Length < 2)
5684
{
57-
return ThemeColors.Day.DefaultBackground;
85+
return ThemeColors.Day.DisabledBackground.Get(theme);
5886
}
5987

6088
if (values[0] is not DaysOfWeek daysOfWeek)
6189
{
62-
return ThemeColors.Day.DefaultBackground;
90+
return ThemeColors.Day.DisabledBackground.Get(theme);
6391
}
6492

6593
if (values[1] is not bool isEnabled)
6694
{
67-
return ThemeColors.Day.DefaultBackground;
95+
return ThemeColors.Day.DisabledBackground.Get(theme);
6896
}
6997

7098
var dayParameter = ParseDayParameter(parameter);
7199
var isDayEnabled = (daysOfWeek & dayParameter) == dayParameter;
72100

73-
// When alarm is enabled: dark background for enabled days, darker for disabled days
74-
// When alarm is disabled: same colors - enabled days get blue, disabled days get gray
75-
return isDayEnabled ? ThemeColors.Day.EnabledBackground : ThemeColors.Day.DisabledBackground;
101+
// Four combinations (theme-aware):
102+
// 1. Schedule enabled + Day enabled: Primary color background (prominent)
103+
// 2. Schedule enabled + Day disabled: Neutral gray background
104+
// 3. Schedule disabled + Day enabled: Muted primary background (shows day selected but schedule off)
105+
// 4. Schedule disabled + Day disabled: More muted gray background (distinct from case 2)
106+
if (isEnabled)
107+
{
108+
// Schedule enabled
109+
return isDayEnabled
110+
? ThemeColors.Day.EnabledBackground.Get(theme)
111+
: ThemeColors.Day.DisabledBackground.Get(theme);
112+
}
113+
else
114+
{
115+
// Schedule disabled
116+
return isDayEnabled
117+
? ThemeColors.Day.DefaultBackground.Get(theme)
118+
: ThemeColors.Day.AlarmDisabledDayDisabledBackground.Get(theme);
119+
}
76120
}
77121

78122
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotImplementedException();

src/Bible.Alarm/Common/ViewHelpers/Converters/DayColorConverter.cs

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
1+
using System.ComponentModel;
12
using System.Globalization;
23
using Bible.Alarm.Shared.Models.Enums;
34
using Bible.Alarm.ViewModels;
45

56
namespace Bible.Alarm.Common.ViewHelpers.Converters;
67

7-
public sealed class DayColorConverter : IValueConverter
8+
public sealed class DayColorConverter : IValueConverter, IMultiValueConverter, INotifyPropertyChanged
89
{
10+
public event PropertyChangedEventHandler? PropertyChanged;
11+
12+
public DayColorConverter()
13+
{
14+
// Subscribe to theme changes when converter is instantiated
15+
if (Application.Current != null)
16+
{
17+
Application.Current.RequestedThemeChanged += OnRequestedThemeChanged;
18+
}
19+
}
20+
21+
private void OnRequestedThemeChanged(object? sender, AppThemeChangedEventArgs e)
22+
{
23+
// Notify that the converter output has changed, causing all bindings to re-evaluate
24+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(string.Empty));
25+
}
926
private static DaysOfWeek ParseDayParameter(object parameter)
1027
{
1128
return parameter switch
@@ -26,10 +43,14 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
2643

2744
var dayParameter = ParseDayParameter(parameter);
2845

46+
var theme = ThemeColors.GetCurrentTheme();
47+
2948
if (value is DaysOfWeek)
3049
{
31-
var isEnabled = ((DaysOfWeek)value & dayParameter) == dayParameter;
32-
return isEnabled ? ThemeColors.Day.EnabledText : ThemeColors.Day.DisabledText;
50+
var isDayEnabled = ((DaysOfWeek)value & dayParameter) == dayParameter;
51+
return isDayEnabled
52+
? ThemeColors.Day.EnabledText.Get(theme)
53+
: ThemeColors.Day.DisabledText.Get(theme);
3354
}
3455
else
3556
{
@@ -40,19 +61,62 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
4061
return Colors.White;
4162
}
4263

43-
var isEnabled = (schedule.DaysOfWeek & dayParameter) == dayParameter;
64+
var isDayEnabled = (schedule.DaysOfWeek & dayParameter) == dayParameter;
4465

4566
if (schedule.IsEnabled)
4667
{
47-
// When alarm is enabled: light text on dark background for enabled days, darker text for disabled days
48-
return isEnabled ? ThemeColors.Day.EnabledText : ThemeColors.Day.DisabledText;
68+
// Schedule enabled
69+
return isDayEnabled
70+
? ThemeColors.Day.EnabledText.Get(theme)
71+
: ThemeColors.Day.DisabledText.Get(theme);
4972
}
5073

51-
// When alarm is disabled: flip the colors - enabled days get white text (on blue), disabled days get darker text (on gray)
52-
// White for enabled days, darker text for disabled days
53-
return isEnabled ? ThemeColors.Day.EnabledText : ThemeColors.Day.DisabledText;
74+
// Schedule disabled - all days use disabled text color
75+
return ThemeColors.Day.DisabledText.Get(theme);
76+
}
77+
}
78+
79+
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
80+
{
81+
if (values == null || values.Length < 2)
82+
{
83+
return Colors.White;
84+
}
85+
86+
if (values[0] is not DaysOfWeek daysOfWeek)
87+
{
88+
return Colors.White;
89+
}
90+
91+
if (values[1] is not bool isEnabled)
92+
{
93+
return Colors.White;
94+
}
95+
96+
var dayParameter = ParseDayParameter(parameter);
97+
var isDayEnabled = (daysOfWeek & dayParameter) == dayParameter;
98+
var theme = ThemeColors.GetCurrentTheme();
99+
100+
// Four combinations (theme-aware):
101+
// 1. Schedule enabled + Day enabled: White text on primary background (high contrast)
102+
// 2. Schedule enabled + Day disabled: Disabled text on gray background
103+
// 3. Schedule disabled + Day enabled: Disabled text on muted background
104+
// 4. Schedule disabled + Day disabled: Disabled text on gray background
105+
if (isEnabled)
106+
{
107+
// Schedule enabled
108+
return isDayEnabled
109+
? ThemeColors.Day.EnabledText.Get(theme)
110+
: ThemeColors.Day.DisabledText.Get(theme);
111+
}
112+
else
113+
{
114+
// Schedule disabled - all days use disabled text color (theme-aware)
115+
return ThemeColors.Day.DisabledText.Get(theme);
54116
}
55117
}
56118

119+
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotImplementedException();
120+
57121
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
58122
}

src/Bible.Alarm/Services/Schedule/Interfaces/IScheduleContainerService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ public interface IScheduleContainerService
77
{
88
Task InitializeContainersAsync(
99
IServiceProvider serviceProvider,
10-
Action<BiblePublicationSelectionContainerViewModel, MusicSelectionContainerViewModel, NumberOfTrackContainerViewModel, ScheduleDetailsContainerViewModel> onContainersReady);
10+
Action<BiblePublicationSelectionContainerViewModel, MusicSelectionContainerViewModel, NumberOfTrackContainerViewModel, ScheduleDetailsContainerViewModel, AlarmSettingsContainerViewModel> onContainersReady);
1111
}
1212

src/Bible.Alarm/Services/Schedule/ScheduleContainerService.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public ScheduleContainerService(ILogger logger)
1616

1717
public async Task InitializeContainersAsync(
1818
IServiceProvider serviceProvider,
19-
Action<BiblePublicationSelectionContainerViewModel, MusicSelectionContainerViewModel, NumberOfTrackContainerViewModel, ScheduleDetailsContainerViewModel> onContainersReady)
19+
Action<BiblePublicationSelectionContainerViewModel, MusicSelectionContainerViewModel, NumberOfTrackContainerViewModel, ScheduleDetailsContainerViewModel, AlarmSettingsContainerViewModel> onContainersReady)
2020
{
2121
try
2222
{
@@ -34,13 +34,17 @@ public async Task InitializeContainersAsync(
3434
var scheduleDetails = await Task.Run(() =>
3535
serviceProvider.GetRequiredService<ScheduleDetailsContainerViewModel>());
3636

37+
var alarmSettings = await Task.Run(() =>
38+
serviceProvider.GetRequiredService<AlarmSettingsContainerViewModel>());
39+
3740
await MainThread.InvokeOnMainThreadAsync(() =>
3841
{
3942
onContainersReady(
4043
bibleSelection,
4144
musicSelection,
4245
numberOfTrack,
43-
scheduleDetails);
46+
scheduleDetails,
47+
alarmSettings);
4448
});
4549
}
4650
catch (Exception ex)

0 commit comments

Comments
 (0)