mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-05-09 11:51:59 -04:00
Use a 3-way theme switcher instead of a 2-way switcher (#1233)
This commit is contained in:
parent
9e7ad4d85c
commit
7a69c87b56
12 changed files with 106 additions and 116 deletions
|
@ -5,7 +5,7 @@
|
|||
<PackageReference Include="CSharpier.MsBuild" Version="0.28.2" PrivateAssets="all" />
|
||||
<PackageReference Include="Gress" Version="2.1.1" />
|
||||
<PackageReference Include="JsonExtensions" Version="1.2.0" />
|
||||
<PackageReference Include="Polly" Version="8.3.1" />
|
||||
<PackageReference Include="Polly" Version="8.4.0" />
|
||||
<PackageReference Include="RazorBlade" Version="0.6.0" />
|
||||
<PackageReference Include="Superpower" Version="3.0.0" />
|
||||
<PackageReference Include="WebMarkupMin.Core" Version="2.16.0" />
|
||||
|
|
|
@ -7,13 +7,18 @@
|
|||
xmlns:materialAssists="clr-namespace:Material.Styles.Assists;assembly=Material.Styles"
|
||||
xmlns:materialControls="clr-namespace:Material.Styles.Controls;assembly=Material.Styles"
|
||||
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:materialStyles="clr-namespace:Material.Styles.Themes;assembly=Material.Styles">
|
||||
xmlns:materialStyles="clr-namespace:Material.Styles.Themes;assembly=Material.Styles"
|
||||
ActualThemeVariantChanged="Application_OnActualThemeVariantChanged">
|
||||
<Application.DataTemplates>
|
||||
<framework:ViewManager />
|
||||
</Application.DataTemplates>
|
||||
|
||||
<Application.Styles>
|
||||
<materialStyles:MaterialTheme />
|
||||
<!-- This theme is used as a stub to pre-load default resources, the actual colors are set through code -->
|
||||
<materialStyles:MaterialTheme
|
||||
BaseTheme="Light"
|
||||
PrimaryColor="Grey"
|
||||
SecondaryColor="DeepOrange" />
|
||||
<materialIcons:MaterialIconStyles />
|
||||
<dialogHostAvalonia:DialogHostStyles />
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ using Avalonia.Media;
|
|||
using Avalonia.Platform;
|
||||
using DiscordChatExporter.Gui.Framework;
|
||||
using DiscordChatExporter.Gui.Services;
|
||||
using DiscordChatExporter.Gui.Utils;
|
||||
using DiscordChatExporter.Gui.Utils.Extensions;
|
||||
using DiscordChatExporter.Gui.ViewModels;
|
||||
using DiscordChatExporter.Gui.ViewModels.Components;
|
||||
using DiscordChatExporter.Gui.ViewModels.Dialogs;
|
||||
|
@ -16,11 +18,14 @@ using Microsoft.Extensions.DependencyInjection;
|
|||
|
||||
namespace DiscordChatExporter.Gui;
|
||||
|
||||
public partial class App : Application, IDisposable
|
||||
public class App : Application, IDisposable
|
||||
{
|
||||
private readonly ServiceProvider _services;
|
||||
private readonly SettingsService _settingsService;
|
||||
private readonly MainViewModel _mainViewModel;
|
||||
|
||||
private readonly DisposableCollector _eventRoot = new();
|
||||
|
||||
public App()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
@ -43,17 +48,62 @@ public partial class App : Application, IDisposable
|
|||
services.AddTransient<SettingsViewModel>();
|
||||
|
||||
_services = services.BuildServiceProvider(true);
|
||||
_settingsService = _services.GetRequiredService<SettingsService>();
|
||||
_mainViewModel = _services.GetRequiredService<ViewModelManager>().CreateMainViewModel();
|
||||
|
||||
// Re-initialize the theme when the user changes it
|
||||
_eventRoot.Add(
|
||||
_settingsService.WatchProperty(
|
||||
o => o.Theme,
|
||||
() =>
|
||||
{
|
||||
RequestedThemeVariant = _settingsService.Theme switch
|
||||
{
|
||||
ThemeVariant.System => Avalonia.Styling.ThemeVariant.Default,
|
||||
ThemeVariant.Light => Avalonia.Styling.ThemeVariant.Light,
|
||||
ThemeVariant.Dark => Avalonia.Styling.ThemeVariant.Dark,
|
||||
_
|
||||
=> throw new InvalidOperationException(
|
||||
$"Unknown theme '{_settingsService.Theme}'."
|
||||
)
|
||||
};
|
||||
|
||||
InitializeTheme();
|
||||
},
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
// Increase maximum concurrent connections
|
||||
ServicePointManager.DefaultConnectionLimit = 20;
|
||||
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void InitializeTheme()
|
||||
{
|
||||
var actualTheme = RequestedThemeVariant?.Key switch
|
||||
{
|
||||
"Light" => PlatformThemeVariant.Light,
|
||||
"Dark" => PlatformThemeVariant.Dark,
|
||||
_ => PlatformSettings?.GetColorValues().ThemeVariant
|
||||
};
|
||||
|
||||
this.LocateMaterialTheme<MaterialThemeBase>().CurrentTheme = actualTheme switch
|
||||
{
|
||||
PlatformThemeVariant.Light
|
||||
=> Theme.Create(Theme.Light, Color.Parse("#343838"), Color.Parse("#F9A825")),
|
||||
PlatformThemeVariant.Dark
|
||||
=> Theme.Create(Theme.Dark, Color.Parse("#E8E8E8"), Color.Parse("#F9A825")),
|
||||
_ => throw new InvalidOperationException($"Unknown theme '{actualTheme}'.")
|
||||
};
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
|
@ -61,50 +111,20 @@ public partial class App : Application, IDisposable
|
|||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
|
||||
// Set custom theme colors
|
||||
SetDefaultTheme();
|
||||
// Set up custom theme colors
|
||||
InitializeTheme();
|
||||
|
||||
// Load settings
|
||||
_settingsService.Load();
|
||||
}
|
||||
|
||||
public void Dispose() => _services.Dispose();
|
||||
}
|
||||
private void Application_OnActualThemeVariantChanged(object? sender, EventArgs args) =>
|
||||
// Re-initialize the theme when the system theme changes
|
||||
InitializeTheme();
|
||||
|
||||
public partial class App
|
||||
{
|
||||
public static void SetLightTheme()
|
||||
public void Dispose()
|
||||
{
|
||||
if (Current is null)
|
||||
return;
|
||||
|
||||
Current.LocateMaterialTheme<MaterialThemeBase>().CurrentTheme = Theme.Create(
|
||||
Theme.Light,
|
||||
Color.Parse("#343838"),
|
||||
Color.Parse("#F9A825")
|
||||
);
|
||||
}
|
||||
|
||||
public static void SetDarkTheme()
|
||||
{
|
||||
if (Current is null)
|
||||
return;
|
||||
|
||||
Current.LocateMaterialTheme<MaterialThemeBase>().CurrentTheme = Theme.Create(
|
||||
Theme.Dark,
|
||||
Color.Parse("#E8E8E8"),
|
||||
Color.Parse("#F9A825")
|
||||
);
|
||||
}
|
||||
|
||||
public static void SetDefaultTheme()
|
||||
{
|
||||
if (Current is null)
|
||||
return;
|
||||
|
||||
var isDarkModeEnabledByDefault =
|
||||
Current.PlatformSettings?.GetColorValues().ThemeVariant == PlatformThemeVariant.Dark;
|
||||
|
||||
if (isDarkModeEnabledByDefault)
|
||||
SetDarkTheme();
|
||||
else
|
||||
SetLightTheme();
|
||||
_eventRoot.Dispose();
|
||||
_services.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ public class LocaleToDisplayNameStringConverter : IValueConverter
|
|||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) =>
|
||||
value is string locale && !string.IsNullOrWhiteSpace(locale)
|
||||
? CultureInfo.GetCultureInfo(locale).DisplayName
|
||||
: "System default";
|
||||
: "System";
|
||||
|
||||
public object ConvertBack(
|
||||
object? value,
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<PackageReference Include="DialogHost.Avalonia" Version="0.7.7" />
|
||||
<PackageReference Include="DotnetRuntimeBootstrapper" Version="2.5.4" PrivateAssets="all" />
|
||||
<PackageReference Include="Gress" Version="2.1.1" />
|
||||
<PackageReference Include="Material.Avalonia" Version="3.5.0" />
|
||||
<PackageReference Include="Material.Avalonia" Version="3.6.0" />
|
||||
<PackageReference Include="Material.Icons.Avalonia" Version="2.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Onova" Version="2.6.11" />
|
||||
|
|
8
DiscordChatExporter.Gui/Framework/ThemeVariant.cs
Normal file
8
DiscordChatExporter.Gui/Framework/ThemeVariant.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace DiscordChatExporter.Gui.Framework;
|
||||
|
||||
public enum ThemeVariant
|
||||
{
|
||||
System,
|
||||
Light,
|
||||
Dark
|
||||
}
|
|
@ -1,12 +1,10 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Avalonia;
|
||||
using Avalonia.Platform;
|
||||
using Cogwheel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using DiscordChatExporter.Core.Exporting;
|
||||
using DiscordChatExporter.Gui.Framework;
|
||||
using DiscordChatExporter.Gui.Models;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace DiscordChatExporter.Gui.Services;
|
||||
|
||||
|
@ -18,10 +16,10 @@ public partial class SettingsService()
|
|||
private bool _isUkraineSupportMessageEnabled = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isAutoUpdateEnabled = true;
|
||||
private ThemeVariant _theme;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isDarkModeEnabled;
|
||||
private bool _isAutoUpdateEnabled = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isTokenPersisted = true;
|
||||
|
@ -62,17 +60,6 @@ public partial class SettingsService()
|
|||
[ObservableProperty]
|
||||
private string? _lastAssetsDirPath;
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
|
||||
// Reset the dark mode setting separately because its default value is evaluated dynamically
|
||||
// and cannot be set by the field initializer.
|
||||
IsDarkModeEnabled =
|
||||
Application.Current?.PlatformSettings?.GetColorValues().ThemeVariant
|
||||
== PlatformThemeVariant.Dark;
|
||||
}
|
||||
|
||||
public override void Save()
|
||||
{
|
||||
// Clear the token if it's not supposed to be persisted
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using DiscordChatExporter.Core.Utils.Extensions;
|
||||
using DiscordChatExporter.Gui.Framework;
|
||||
using DiscordChatExporter.Gui.Models;
|
||||
|
@ -23,18 +22,20 @@ public class SettingsViewModel : DialogViewModelBase
|
|||
_eventRoot.Add(_settingsService.WatchAllProperties(OnAllPropertiesChanged));
|
||||
}
|
||||
|
||||
public IReadOnlyList<ThemeVariant> AvailableThemes { get; } = Enum.GetValues<ThemeVariant>();
|
||||
|
||||
public ThemeVariant Theme
|
||||
{
|
||||
get => _settingsService.Theme;
|
||||
set => _settingsService.Theme = value;
|
||||
}
|
||||
|
||||
public bool IsAutoUpdateEnabled
|
||||
{
|
||||
get => _settingsService.IsAutoUpdateEnabled;
|
||||
set => _settingsService.IsAutoUpdateEnabled = value;
|
||||
}
|
||||
|
||||
public bool IsDarkModeEnabled
|
||||
{
|
||||
get => _settingsService.IsDarkModeEnabled;
|
||||
set => _settingsService.IsDarkModeEnabled = value;
|
||||
}
|
||||
|
||||
public bool IsTokenPersisted
|
||||
{
|
||||
get => _settingsService.IsTokenPersisted;
|
||||
|
|
|
@ -79,18 +79,6 @@ public partial class MainViewModel(
|
|||
[RelayCommand]
|
||||
private async Task InitializeAsync()
|
||||
{
|
||||
// Reset settings (needed to resolve the default dark mode setting)
|
||||
settingsService.Reset();
|
||||
|
||||
// Load settings
|
||||
settingsService.Load();
|
||||
|
||||
// Set the correct theme
|
||||
if (settingsService.IsDarkModeEnabled)
|
||||
App.SetDarkTheme();
|
||||
else
|
||||
App.SetLightTheme();
|
||||
|
||||
await ShowUkraineSupportMessageAsync();
|
||||
await CheckForUpdatesAsync();
|
||||
}
|
||||
|
|
|
@ -24,6 +24,19 @@
|
|||
BorderThickness="0,1">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<!-- Theme -->
|
||||
<DockPanel
|
||||
Margin="16,8"
|
||||
LastChildFill="False"
|
||||
ToolTip.Tip="Preferred user interface theme">
|
||||
<TextBlock DockPanel.Dock="Left" Text="Theme" />
|
||||
<ComboBox
|
||||
Width="150"
|
||||
DockPanel.Dock="Right"
|
||||
ItemsSource="{Binding AvailableThemes}"
|
||||
SelectedItem="{Binding Theme}" />
|
||||
</DockPanel>
|
||||
|
||||
<!-- Auto-updates -->
|
||||
<DockPanel
|
||||
Margin="16,8"
|
||||
|
@ -35,19 +48,6 @@
|
|||
<ToggleSwitch DockPanel.Dock="Right" IsChecked="{Binding IsAutoUpdateEnabled}" />
|
||||
</DockPanel>
|
||||
|
||||
<!-- Dark mode -->
|
||||
<DockPanel
|
||||
Margin="16,8"
|
||||
LastChildFill="False"
|
||||
ToolTip.Tip="Use darker colors in the UI">
|
||||
<TextBlock DockPanel.Dock="Left" Text="Dark mode" />
|
||||
<ToggleSwitch
|
||||
x:Name="DarkModeToggleSwitch"
|
||||
DockPanel.Dock="Right"
|
||||
IsChecked="{Binding IsDarkModeEnabled}"
|
||||
IsCheckedChanged="DarkModeToggleSwitch_OnIsCheckedChanged" />
|
||||
</DockPanel>
|
||||
|
||||
<!-- Persist token -->
|
||||
<DockPanel
|
||||
Margin="16,8"
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System.Windows;
|
||||
using Avalonia.Interactivity;
|
||||
using DiscordChatExporter.Gui.Framework;
|
||||
using DiscordChatExporter.Gui.Framework;
|
||||
using DiscordChatExporter.Gui.ViewModels.Dialogs;
|
||||
|
||||
namespace DiscordChatExporter.Gui.Views.Dialogs;
|
||||
|
@ -8,20 +6,4 @@ namespace DiscordChatExporter.Gui.Views.Dialogs;
|
|||
public partial class SettingsView : UserControl<SettingsViewModel>
|
||||
{
|
||||
public SettingsView() => InitializeComponent();
|
||||
|
||||
private void DarkModeToggleSwitch_OnIsCheckedChanged(object? sender, RoutedEventArgs args)
|
||||
{
|
||||
if (DarkModeToggleSwitch.IsChecked is true)
|
||||
{
|
||||
App.SetDarkTheme();
|
||||
}
|
||||
else if (DarkModeToggleSwitch.IsChecked is false)
|
||||
{
|
||||
App.SetLightTheme();
|
||||
}
|
||||
else
|
||||
{
|
||||
App.SetDefaultTheme();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue