fix(settings): apply a theme as soon as it is selected and restore it on cancel
PLAN 5.6 asks for the theme to take effect immediately, but it was applied only on OK. Selecting a theme now previews it; OK saves it, and cancel, Escape or closing the window restores the theme the dialog opened with.
This commit is contained in:
1 parent
1ecb48316d
commit
4a88033695
5 files changed
+109
-7
No files matched your search
@@ -0,0 +1,63 @@
|
||||
using YKanBan.Storage.AppData;
|
||||
using YKanBan.ViewModels.Dialogs;
|
||||
|
||||
namespace YKanBan.Tests.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the settings dialog: the theme is previewed as soon as it is
|
||||
/// selected, saved on accept and restored on cancel.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class SettingsDialogViewModelTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void OpeningTheDialogAppliesNothing()
|
||||
{
|
||||
var applied = new List<ThemeOption>();
|
||||
|
||||
_ = new SettingsDialogViewModel(new AppConfig(), AppConfig.DefaultLanguage, applied.Add);
|
||||
|
||||
Assert.AreEqual(0, applied.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SelectingAThemeAppliesItImmediatelyWithoutSavingIt()
|
||||
{
|
||||
var config = new AppConfig();
|
||||
var applied = new List<ThemeOption>();
|
||||
var dialog = new SettingsDialogViewModel(config, AppConfig.DefaultLanguage, applied.Add);
|
||||
|
||||
dialog.SelectedTheme = dialog.Themes.Single(option => option.Value.Equals(ThemeOption.Dark));
|
||||
|
||||
CollectionAssert.AreEqual(new[] { ThemeOption.Dark }, applied);
|
||||
Assert.AreEqual(ThemeOption.FollowSystem, config.Theme);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AcceptSavesThePreviewedTheme()
|
||||
{
|
||||
var config = new AppConfig();
|
||||
var applied = new List<ThemeOption>();
|
||||
var dialog = new SettingsDialogViewModel(config, AppConfig.DefaultLanguage, applied.Add);
|
||||
dialog.SelectedTheme = dialog.Themes.Single(option => option.Value.Equals(ThemeOption.Light));
|
||||
|
||||
dialog.Accept();
|
||||
|
||||
Assert.AreEqual(ThemeOption.Light, config.Theme);
|
||||
CollectionAssert.AreEqual(new[] { ThemeOption.Light }, applied);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CancelRestoresTheThemeTheDialogOpenedWith()
|
||||
{
|
||||
var config = new AppConfig { Theme = ThemeOption.Light };
|
||||
var applied = new List<ThemeOption>();
|
||||
var dialog = new SettingsDialogViewModel(config, AppConfig.DefaultLanguage, applied.Add);
|
||||
dialog.SelectedTheme = dialog.Themes.Single(option => option.Value.Equals(ThemeOption.Dark));
|
||||
|
||||
dialog.Cancel();
|
||||
|
||||
Assert.AreEqual(ThemeOption.Light, config.Theme);
|
||||
CollectionAssert.AreEqual(new[] { ThemeOption.Dark, ThemeOption.Light }, applied);
|
||||
}
|
||||
}
|
||||
@@ -5,23 +5,27 @@ namespace YKanBan.ViewModels.Dialogs;
|
||||
|
||||
/// <summary>
|
||||
/// The settings dialog: language (restart to apply), theme and the four
|
||||
/// confirmation-dialog toggles. Nothing is applied until the user accepts;
|
||||
/// cancelling or closing the window discards the changes.
|
||||
/// confirmation-dialog toggles. A theme selection is previewed immediately;
|
||||
/// everything else waits until the user accepts. Cancelling or closing the
|
||||
/// window discards the changes and restores the theme the dialog opened with.
|
||||
/// </summary>
|
||||
public sealed partial class SettingsDialogViewModel : ViewModelBase
|
||||
{
|
||||
private readonly AppConfig _config;
|
||||
private readonly string _startupLanguage;
|
||||
private readonly Action<ThemeOption> _applyTheme;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the dialog from the live configuration.
|
||||
/// </summary>
|
||||
/// <param name="config">The live application configuration.</param>
|
||||
/// <param name="startupLanguage">The language applied at startup, for the restart notice.</param>
|
||||
public SettingsDialogViewModel(AppConfig config, string startupLanguage)
|
||||
/// <param name="applyTheme">Applies a theme to the running app; defaults to <see cref="App.ApplyTheme"/>.</param>
|
||||
public SettingsDialogViewModel(AppConfig config, string startupLanguage, Action<ThemeOption>? applyTheme = null)
|
||||
{
|
||||
_config = config;
|
||||
_startupLanguage = startupLanguage;
|
||||
_applyTheme = applyTheme ?? App.ApplyTheme;
|
||||
|
||||
Languages =
|
||||
[
|
||||
@@ -63,7 +67,8 @@ public sealed partial class SettingsDialogViewModel : ViewModelBase
|
||||
private OptionItem? selectedLanguage;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected theme; applied when the dialog is accepted.
|
||||
/// Gets or sets the selected theme; applied to the running app as soon as it
|
||||
/// changes, and saved to the configuration when the dialog is accepted.
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private OptionItem? selectedTheme;
|
||||
@@ -110,7 +115,6 @@ public sealed partial class SettingsDialogViewModel : ViewModelBase
|
||||
if (SelectedTheme is not null)
|
||||
{
|
||||
_config.Theme = (ThemeOption)SelectedTheme.Value;
|
||||
App.ApplyTheme(_config.Theme);
|
||||
}
|
||||
|
||||
_config.Confirmations.DeleteCard = ConfirmDeleteCard;
|
||||
@@ -119,6 +123,23 @@ public sealed partial class SettingsDialogViewModel : ViewModelBase
|
||||
_config.Confirmations.DiscardEdit = ConfirmDiscardEdit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discards the working values and restores the theme the dialog opened with.
|
||||
/// </summary>
|
||||
public void Cancel() => _applyTheme(_config.Theme);
|
||||
|
||||
/// <summary>
|
||||
/// Previews a theme selection on the running app.
|
||||
/// </summary>
|
||||
/// <param name="value">The new selection.</param>
|
||||
partial void OnSelectedThemeChanged(OptionItem? value)
|
||||
{
|
||||
if (value is not null)
|
||||
{
|
||||
_applyTheme((ThemeOption)value.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the restart notice after a language selection change.
|
||||
/// </summary>
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace YKanBan.Views.Dialogs;
|
||||
|
||||
public partial class SettingsDialog : Window
|
||||
{
|
||||
private bool _accepted;
|
||||
|
||||
public SettingsDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -15,9 +17,24 @@ public partial class SettingsDialog : Window
|
||||
private void OnOkClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
(DataContext as SettingsDialogViewModel)?.Accept();
|
||||
_accepted = true;
|
||||
Close(true);
|
||||
}
|
||||
|
||||
/// <summary>Closes without applying anything.</summary>
|
||||
private void OnCancelClick(object? sender, RoutedEventArgs e) => Close(false);
|
||||
|
||||
/// <summary>
|
||||
/// Restores the previewed theme on every close that did not accept,
|
||||
/// including the title-bar close button and Escape.
|
||||
/// </summary>
|
||||
/// <param name="e">The event arguments.</param>
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
if (!_accepted)
|
||||
{
|
||||
(DataContext as SettingsDialogViewModel)?.Cancel();
|
||||
}
|
||||
base.OnClosed(e);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -30,7 +30,8 @@
|
||||
|
||||
### A3 主题三态切换
|
||||
|
||||
- [ ] 设置 → 主题 亮色 / 暗色 / 跟随系统 依次切换:**即时生效**,无重启提示
|
||||
- [ ] 设置 → 主题 亮色 / 暗色 / 跟随系统 依次切换:下拉框一选中就**即时生效**(无需点确认),无重启提示
|
||||
- [ ] 切换主题后点取消、按 Esc 或点标题栏关闭:恢复为打开设置时的主题;重启后仍是原主题
|
||||
- [ ] 每种主题下检查:主窗口、看板列与卡片、标签 tab、全部模态、全部错误页颜色完整切换,无亮底暗字/暗底亮字残留
|
||||
- [ ] 标签徽章在两种主题下均显示原色(§5.9 例外),文字颜色保持可读
|
||||
- [ ] 跟随系统:程序运行中切换 Windows 深浅色 → 程序跟随变化
|
||||
|
||||
+1
-1
@@ -214,7 +214,7 @@ card_tags: (card_id, tag_id) 复合主键,双向 ON DELETE CASCADE
|
||||
入口 = 顶部显示区域右侧"设置"按钮,承载各类全局设置:
|
||||
|
||||
- 语言选择(英文 / 简体中文),切换后显示"重启后生效"提示(提示文本本身为资源键)
|
||||
- 主题选择(亮色 / 暗色 / 跟随系统),切换后**即时生效**,无重启提示(与语言项的行为差异)
|
||||
- 主题选择(亮色 / 暗色 / 跟随系统),下拉框选中即**即时生效**(预览),无重启提示(与语言项的行为差异);点确认才写入配置,取消或关闭设置模态则恢复打开时的主题
|
||||
- 四类确认开关(见 §5.5)
|
||||
|
||||
### 5.7 关于模态
|
||||
|
||||
Reference in new issue
Block a user