diff --git a/Marechai.App/App.xaml b/Marechai.App/App.xaml index 77a46865..115bc560 100644 --- a/Marechai.App/App.xaml +++ b/Marechai.App/App.xaml @@ -8,6 +8,7 @@ + diff --git a/Marechai.App/App.xaml.cs b/Marechai.App/App.xaml.cs index 70d711ea..8a6f71c5 100644 --- a/Marechai.App/App.xaml.cs +++ b/Marechai.App/App.xaml.cs @@ -22,6 +22,7 @@ using NewsViewModel = Marechai.App.Presentation.ViewModels.NewsViewModel; using PhotoDetailViewModel = Marechai.App.Presentation.ViewModels.PhotoDetailViewModel; using ProcessorDetailViewModel = Marechai.App.Presentation.ViewModels.ProcessorDetailViewModel; using ProcessorsListViewModel = Marechai.App.Presentation.ViewModels.ProcessorsListViewModel; +using SettingsViewModel = Marechai.App.Presentation.ViewModels.SettingsViewModel; using SoundSynthDetailViewModel = Marechai.App.Presentation.ViewModels.SoundSynthDetailViewModel; using SoundSynthsListViewModel = Marechai.App.Presentation.ViewModels.SoundSynthsListViewModel; @@ -114,6 +115,10 @@ public partial class App : Application .ConfigureServices((context, services) => { // Register application services + services + .AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -149,6 +154,7 @@ public partial class App : Application services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); }) .UseNavigation(RegisterRoutes)); @@ -181,6 +187,7 @@ public partial class App : Application new ViewMap(), new ViewMap(), new ViewMap(), + new ViewMap(), new DataViewMap()); routes.Register(new RouteMap("", @@ -197,7 +204,8 @@ public partial class App : Application true), new RouteMap("computers", views.FindByViewModel(), - Nested: + Nested + : [ new RouteMap("list-computers", views.FindByViewModel< @@ -208,7 +216,8 @@ public partial class App : Application ]), new RouteMap("consoles", views.FindByViewModel(), - Nested: + Nested + : [ new RouteMap("list-consoles", views.FindByViewModel< @@ -216,7 +225,8 @@ public partial class App : Application ]), new RouteMap("companies", views.FindByViewModel(), - Nested: + Nested + : [ new RouteMap("company-details", views.FindByViewModel< @@ -239,7 +249,8 @@ public partial class App : Application ProcessorDetailViewModel>()) ]), new RouteMap("sound-synths", - views.FindByViewModel(), + views.FindByViewModel< + SoundSynthsListViewModel>(), Nested: [ new RouteMap("sound-synth-details", @@ -249,6 +260,8 @@ public partial class App : Application views.FindByViewModel< MachineViewViewModel>()) ]), + new RouteMap("settings", + views.FindByViewModel()), new RouteMap("Second", views.FindByViewModel()) ]) diff --git a/Marechai.App/Marechai.App.csproj b/Marechai.App/Marechai.App.csproj index 00335748..5788ea25 100644 --- a/Marechai.App/Marechai.App.csproj +++ b/Marechai.App/Marechai.App.csproj @@ -35,6 +35,7 @@ Localization; Navigation; ThemeService; + Storage; SkiaRenderer; Svg; diff --git a/Marechai.App/Presentation/ViewModels/MainViewModel.cs b/Marechai.App/Presentation/ViewModels/MainViewModel.cs index 4ecdb5d5..bdc3912d 100644 --- a/Marechai.App/Presentation/ViewModels/MainViewModel.cs +++ b/Marechai.App/Presentation/ViewModels/MainViewModel.cs @@ -2,7 +2,9 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using System.Windows.Input; +using Marechai.App.Services; using Uno.Extensions.Navigation; +using Uno.Extensions.Toolkit; namespace Marechai.App.Presentation.ViewModels; @@ -25,7 +27,7 @@ public partial class MainViewModel : ObservableObject private bool _sidebarContentVisible = true; public MainViewModel(IStringLocalizer localizer, IOptions appInfo, INavigator navigator, - NewsViewModel newsViewModel) + NewsViewModel newsViewModel, IColorThemeService colorThemeService, IThemeService themeService) { _navigator = navigator; _localizer = localizer; @@ -36,6 +38,9 @@ public partial class MainViewModel : ObservableObject GoToSecond = new AsyncRelayCommand(GoToSecondView); + // Initialize color theme service with theme service + _ = InitializeThemeServicesAsync(colorThemeService, themeService); + // Initialize localized strings InitializeLocalizedStrings(); @@ -81,6 +86,22 @@ public partial class MainViewModel : ObservableObject public ICommand LoginLogoutCommand { get; } public ICommand ToggleSidebarCommand { get; } + private async Task InitializeThemeServicesAsync(IColorThemeService colorThemeService, IThemeService themeService) + { + try + { + // Wait for theme service to be ready + await themeService.InitializeAsync(); + + // Set the theme service reference and reapply the saved theme + colorThemeService.SetThemeService(themeService); + } + catch + { + // Silently fail - theme will work but without refresh on startup + } + } + private void InitializeLocalizedStrings() { LocalizedStrings = new Dictionary diff --git a/Marechai.App/Presentation/ViewModels/SettingsViewModel.cs b/Marechai.App/Presentation/ViewModels/SettingsViewModel.cs new file mode 100644 index 00000000..f5303109 --- /dev/null +++ b/Marechai.App/Presentation/ViewModels/SettingsViewModel.cs @@ -0,0 +1,154 @@ +using System.Collections.Generic; +using System.Linq; +using Uno.Extensions.Toolkit; +using Marechai.App.Services; + +namespace Marechai.App.Presentation.ViewModels; + +public partial class SettingsViewModel : ObservableObject +{ + private readonly IStringLocalizer _localizer; + private readonly IThemeService _themeService; + private readonly IColorThemeService _colorThemeService; + + [ObservableProperty] + private List _availableThemes = new(); + + [ObservableProperty] + private ThemeOption _selectedTheme; + + [ObservableProperty] + private List _availableColorThemes = new(); + + [ObservableProperty] + private ColorThemeOption _selectedColorTheme; + + public SettingsViewModel(IStringLocalizer localizer, IThemeService themeService, IColorThemeService colorThemeService) + { + _localizer = localizer; + _themeService = themeService; + _colorThemeService = colorThemeService; + Title = _localizer["Settings"]; + + // Initialize immediately to ensure UI is populated + InitializeOptions(); + + // Wait for theme service to initialize + _ = InitializeThemeServiceAsync(); + } + + private async System.Threading.Tasks.Task InitializeThemeServiceAsync() + { + try + { + await _themeService.InitializeAsync(); + + // Ensure the color theme service has a reference to the theme service + _colorThemeService.SetThemeService(_themeService); + } + catch + { + // Theme service might already be initialized + } + } + + public string Title { get; } + + private void InitializeOptions() + { + // Initialize Light/Dark/System Themes + AvailableThemes = new List + { + new() { Theme = AppTheme.Light, DisplayName = _localizer["LightTheme"] }, + new() { Theme = AppTheme.Dark, DisplayName = _localizer["DarkTheme"] }, + new() { Theme = AppTheme.System, DisplayName = _localizer["SystemTheme"] } + }; + + // Initialize Color Themes + AvailableColorThemes = new List + { + new() { ThemeName = "Default", DisplayName = _localizer["DefaultColorTheme"] }, + new() { ThemeName = "Windows311", DisplayName = _localizer["Windows311Theme"] } + }; + + // Try to load saved preferences + LoadSavedPreferences(); + } + + private async void LoadSavedPreferences() + { + try + { + // Load current theme from ThemeService + var currentTheme = _themeService.Theme; + SelectedTheme = AvailableThemes.FirstOrDefault(t => t.Theme == currentTheme) + ?? AvailableThemes.FirstOrDefault(t => t.Theme == AppTheme.System) + ?? AvailableThemes.First(); + + // Load current color theme + var currentColorTheme = _colorThemeService.CurrentColorTheme; + SelectedColorTheme = AvailableColorThemes.FirstOrDefault(t => t.ThemeName == currentColorTheme) + ?? AvailableColorThemes.First(); + } + catch + { + // If loading fails, use defaults + SelectedTheme = AvailableThemes.FirstOrDefault(t => t.Theme == AppTheme.System) + ?? AvailableThemes.First(); + SelectedColorTheme = AvailableColorThemes.First(); + } + } + + partial void OnSelectedThemeChanged(ThemeOption value) + { + if (value != null) + { + ApplyTheme(value); + } + } + + partial void OnSelectedColorThemeChanged(ColorThemeOption value) + { + if (value != null) + { + ApplyColorTheme(value); + } + } + + private async void ApplyTheme(ThemeOption theme) + { + try + { + // Apply theme immediately using ThemeService + await _themeService.SetThemeAsync(theme.Theme); + } + catch + { + // Silently fail + } + } + + private void ApplyColorTheme(ColorThemeOption colorTheme) + { + try + { + _colorThemeService.ApplyColorTheme(colorTheme.ThemeName); + } + catch + { + // Silently fail + } + } +} + +public class ThemeOption +{ + public AppTheme Theme { get; set; } + public string DisplayName { get; set; } = string.Empty; +} + +public class ColorThemeOption +{ + public string ThemeName { get; set; } = string.Empty; + public string DisplayName { get; set; } = string.Empty; +} diff --git a/Marechai.App/Presentation/Views/SettingsPage.xaml b/Marechai.App/Presentation/Views/SettingsPage.xaml new file mode 100644 index 00000000..190bf6bb --- /dev/null +++ b/Marechai.App/Presentation/Views/SettingsPage.xaml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Marechai.App/Presentation/Views/SettingsPage.xaml.cs b/Marechai.App/Presentation/Views/SettingsPage.xaml.cs new file mode 100644 index 00000000..fd90416e --- /dev/null +++ b/Marechai.App/Presentation/Views/SettingsPage.xaml.cs @@ -0,0 +1,14 @@ +using Marechai.App.Presentation.ViewModels; +using Microsoft.UI.Xaml.Controls; + +namespace Marechai.App.Presentation.Views; + +public sealed partial class SettingsPage : Page +{ + public SettingsPage() + { + InitializeComponent(); + } + + public SettingsViewModel? ViewModel => DataContext as SettingsViewModel; +} \ No newline at end of file diff --git a/Marechai.App/Services/ColorThemeService.cs b/Marechai.App/Services/ColorThemeService.cs new file mode 100644 index 00000000..d7a9a5b6 --- /dev/null +++ b/Marechai.App/Services/ColorThemeService.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Windows.Storage; +using Microsoft.UI.Xaml; +using Uno.Extensions.Toolkit; + +namespace Marechai.App.Services; + +public interface IColorThemeService +{ + string CurrentColorTheme { get; } + IReadOnlyList AvailableColorThemes { get; } + void ApplyColorTheme(string themeName); + void SetThemeService(IThemeService themeService); + void ReapplyCurrentTheme(); +} + +public class ColorThemeService : IColorThemeService +{ + private const string COLOR_THEME_KEY = "ColorTheme"; + private const string DEFAULT_THEME = "Default"; + private IThemeService _themeService; + + public ColorThemeService() + { + LoadSavedTheme(); + } + + public string CurrentColorTheme { get; private set; } = DEFAULT_THEME; + + public IReadOnlyList AvailableColorThemes => new List + { + DEFAULT_THEME, + "Windows311" + }; + + public void SetThemeService(IThemeService themeService) + { + _themeService = themeService; + + // Reapply the current theme now that we have the theme service + if(CurrentColorTheme != DEFAULT_THEME) ReapplyCurrentTheme(); + } + + public void ReapplyCurrentTheme() + { + // Force refresh of the current theme + ApplyColorTheme(CurrentColorTheme); + } + + public void ApplyColorTheme(string themeName) + { + if(!AvailableColorThemes.Contains(themeName)) return; + + CurrentColorTheme = themeName; + + // Save preference + try + { + ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings; + localSettings.Values[COLOR_THEME_KEY] = themeName; + } + catch + { + // Silently fail + } + + // Apply the theme by rebuilding merged dictionaries + Application app = Application.Current; + + if(app?.Resources == null) return; + + // Store the existing merged dictionaries (except color overrides) + var existingDictionaries = app.Resources.MergedDictionaries + .Where(d => d.Source?.OriginalString?.Contains("ColorPaletteOverride") != true) + .ToList(); + + // Clear all merged dictionaries + app.Resources.MergedDictionaries.Clear(); + + // Re-add the existing dictionaries + foreach(ResourceDictionary dict in existingDictionaries) app.Resources.MergedDictionaries.Add(dict); + + // Add the new color theme if not default + if(themeName != DEFAULT_THEME) + { + var newDictionary = new ResourceDictionary + { + Source = new Uri("ms-appx:///Styles/ColorPaletteOverride.xaml") + }; + + app.Resources.MergedDictionaries.Add(newDictionary); + } + + // Force UI refresh by toggling the theme temporarily + ForceThemeRefresh(); + } + + private void LoadSavedTheme() + { + try + { + ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings; + + if(localSettings.Values.ContainsKey(COLOR_THEME_KEY)) + { + var savedTheme = localSettings.Values[COLOR_THEME_KEY] as string; + + if(!string.IsNullOrEmpty(savedTheme) && AvailableColorThemes.Contains(savedTheme)) + { + CurrentColorTheme = savedTheme; + ApplyColorTheme(CurrentColorTheme); + } + } + } + catch + { + // If loading fails, use default theme + } + } + + private async void ForceThemeRefresh() + { + if(_themeService == null) return; + + try + { + // Get current theme + AppTheme currentTheme = _themeService.Theme; + + // Toggle to opposite theme briefly + AppTheme tempTheme = currentTheme == AppTheme.Light ? AppTheme.Dark : AppTheme.Light; + await _themeService.SetThemeAsync(tempTheme); + + // Small delay to ensure the change is applied + await Task.Delay(50); + + // Switch back to original theme + await _themeService.SetThemeAsync(currentTheme); + } + catch + { + // Silently fail + } + } +} \ No newline at end of file diff --git a/Marechai.App/Strings/en/Resources.resw b/Marechai.App/Strings/en/Resources.resw index 16dff704..ef94999f 100644 --- a/Marechai.App/Strings/en/Resources.resw +++ b/Marechai.App/Strings/en/Resources.resw @@ -166,4 +166,50 @@ Company was renamed on an unknown date to an unknown name. + + + Appearance + + + Select your preferred theme + + + Theme + + + Theme changes will be applied the next time you start the application. + + + About + + + Version 1.0 + + + Light + + + Dark + + + System + + + Default + + + Windows 3.11 + + + Brightness + + + Color Theme + + + Design System + + + Design system changes will be applied the next time you start the application. Color theme changes are applied immediately. + diff --git a/Marechai.App/Strings/es/Resources.resw b/Marechai.App/Strings/es/Resources.resw index 72018021..f6c89c59 100644 --- a/Marechai.App/Strings/es/Resources.resw +++ b/Marechai.App/Strings/es/Resources.resw @@ -166,4 +166,50 @@ La empresa fue renombrada en una fecha desconocida a un nombre desconocido. + + + Apariencia + + + Seleccione su tema preferido + + + Tema + + + Los cambios de tema se aplicarán la próxima vez que inicie la aplicación. + + + Acerca de + + + Versión 1.0 + + + Claro + + + Oscuro + + + Sistema + + + Predeterminado + + + Windows 3.11 + + + Brillo + + + Tema de Color + + + Sistema de Diseño + + + Los cambios del sistema de diseño se aplicarán la próxima vez que inicie la aplicación. Los cambios de tema de color se aplican inmediatamente. + diff --git a/Marechai.App/Strings/fr/Resources.resw b/Marechai.App/Strings/fr/Resources.resw index d5298761..781dabb3 100644 --- a/Marechai.App/Strings/fr/Resources.resw +++ b/Marechai.App/Strings/fr/Resources.resw @@ -166,4 +166,50 @@ L'entreprise a été renommée à une date inconnue en un nom inconnu. + + + Apparence + + + Sélectionnez votre thème préféré + + + Thème + + + Les modifications du thème seront appliquées au prochain démarrage de l'application. + + + À propos + + + Version 1.0 + + + Clair + + + Sombre + + + Système + + + Par défaut + + + Windows 3.11 + + + Luminosité + + + Thème de Couleur + + + Système de Conception + + + Les modifications du système de conception seront appliquées au prochain démarrage de l'application. Les modifications du thème de couleur sont appliquées immédiatement. + diff --git a/Marechai.App/Strings/pt-BR/Resources.resw b/Marechai.App/Strings/pt-BR/Resources.resw index f2f52baa..422db224 100644 --- a/Marechai.App/Strings/pt-BR/Resources.resw +++ b/Marechai.App/Strings/pt-BR/Resources.resw @@ -166,4 +166,50 @@ A empresa foi renomeada em uma data desconhecida para um nome desconhecido. + + + Aparência + + + Selecione o seu tema preferido + + + Tema + + + As alterações de tema serão aplicadas na próxima vez que você iniciar o aplicativo. + + + Sobre + + + Versão 1.0 + + + Claro + + + Escuro + + + Sistema + + + Padrão + + + Windows 3.11 + + + Brilho + + + Tema de Cor + + + Sistema de Design + + + As alterações do sistema de design serão aplicadas na próxima vez que você iniciar o aplicativo. As alterações de tema de cor são aplicadas imediatamente. + diff --git a/Marechai.App/Styles/ColorPaletteOverride.xaml b/Marechai.App/Styles/ColorPaletteOverride.xaml new file mode 100644 index 00000000..c1e92ec7 --- /dev/null +++ b/Marechai.App/Styles/ColorPaletteOverride.xaml @@ -0,0 +1,86 @@ + + + + + + + #008080 + #00A0A0 + #00C0C0 + #00E0E0 + #006060 + #004040 + #002020 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +