首先我加了CI课程表json的读取,然后把天气时钟这个老问题也修了。
This commit is contained in:
lincube
2026-03-22 04:57:19 +08:00
parent 1a7dde34d0
commit c3db5af923
19 changed files with 646 additions and 67 deletions

View File

@@ -38,6 +38,27 @@
"settings.wallpaper.title": "Wallpaper", "settings.wallpaper.title": "Wallpaper",
"settings.wallpaper.description": "Pick an image or video to apply as the app window wallpaper immediately.", "settings.wallpaper.description": "Pick an image or video to apply as the app window wallpaper immediately.",
"settings.wallpaper.current_label": "Current Wallpaper", "settings.wallpaper.current_label": "Current Wallpaper",
"settings.wallpaper.type_label": "Wallpaper Type",
"settings.wallpaper.type.image": "Image",
"settings.wallpaper.type.solid_color": "Solid Color",
"settings.wallpaper.type.system": "System Wallpaper",
"settings.wallpaper.system.label": "System Wallpaper",
"settings.wallpaper.system.unavailable": "Unable to read system wallpaper",
"settings.wallpaper.refresh_interval": "Refresh Interval",
"settings.wallpaper.refresh_now": "Refresh Now",
"settings.wallpaper.refresh.30s": "30 seconds",
"settings.wallpaper.refresh.1m": "1 minute",
"settings.wallpaper.refresh.5m": "5 minutes",
"settings.wallpaper.refresh.10m": "10 minutes",
"settings.wallpaper.refresh.15m": "15 minutes",
"settings.wallpaper.refresh.30m": "30 minutes",
"settings.wallpaper.refresh.1h": "1 hour",
"settings.wallpaper.refresh.2h": "2 hours",
"settings.wallpaper.refresh.4h": "4 hours",
"settings.wallpaper.refresh.8h": "8 hours",
"settings.wallpaper.refresh.12h": "12 hours",
"settings.wallpaper.refresh.24h": "24 hours",
"settings.wallpaper.color_label": "Wallpaper Color",
"settings.wallpaper.placement_label": "Placement", "settings.wallpaper.placement_label": "Placement",
"settings.wallpaper.placement_desc": "Adjust how the image fills the desktop.", "settings.wallpaper.placement_desc": "Adjust how the image fills the desktop.",
"settings.wallpaper.pick_button": "Browse Files", "settings.wallpaper.pick_button": "Browse Files",
@@ -217,7 +238,14 @@
"schedule.settings.unnamed": "Unnamed Schedule", "schedule.settings.unnamed": "Unnamed Schedule",
"schedule.settings.delete": "Delete", "schedule.settings.delete": "Delete",
"schedule.settings.picker_title": "Select ClassIsland schedule file", "schedule.settings.picker_title": "Select ClassIsland schedule file",
"schedule.settings.picker_file_type": "ClassIsland CSES schedule", "schedule.settings.picker_file_type.all": "ClassIsland Schedule Files",
"schedule.settings.picker_file_type.json": "ClassIsland Profile (JSON)",
"schedule.settings.picker_file_type.cses": "CSES Schedule (YAML)",
"schedule.settings.semester.title": "Semester Settings",
"schedule.settings.semester.start_date": "Semester Start Date",
"schedule.settings.semester.week_cycle": "Week Cycle",
"schedule.settings.semester.week_cycle_desc": "Set the week rotation cycle for multi-week schedules (e.g., 2 for odd/even weeks).",
"schedule.settings.semester.week_cycle_format": "{0}-week rotation",
"worldclock.settings.title": "World Clock Settings", "worldclock.settings.title": "World Clock Settings",
"worldclock.settings.desc": "Choose a time zone for each of the four clocks.", "worldclock.settings.desc": "Choose a time zone for each of the four clocks.",
"worldclock.settings.clock_1": "Clock 1", "worldclock.settings.clock_1": "Clock 1",

View File

@@ -41,6 +41,23 @@
"settings.wallpaper.type_label": "壁纸类型", "settings.wallpaper.type_label": "壁纸类型",
"settings.wallpaper.type.image": "图片", "settings.wallpaper.type.image": "图片",
"settings.wallpaper.type.solid_color": "纯色", "settings.wallpaper.type.solid_color": "纯色",
"settings.wallpaper.type.system": "系统壁纸",
"settings.wallpaper.system.label": "系统壁纸",
"settings.wallpaper.system.unavailable": "无法读取系统壁纸",
"settings.wallpaper.refresh_interval": "刷新频率",
"settings.wallpaper.refresh_now": "立即刷新",
"settings.wallpaper.refresh.30s": "30 秒",
"settings.wallpaper.refresh.1m": "1 分钟",
"settings.wallpaper.refresh.5m": "5 分钟",
"settings.wallpaper.refresh.10m": "10 分钟",
"settings.wallpaper.refresh.15m": "15 分钟",
"settings.wallpaper.refresh.30m": "30 分钟",
"settings.wallpaper.refresh.1h": "1 小时",
"settings.wallpaper.refresh.2h": "2 小时",
"settings.wallpaper.refresh.4h": "4 小时",
"settings.wallpaper.refresh.8h": "8 小时",
"settings.wallpaper.refresh.12h": "12 小时",
"settings.wallpaper.refresh.24h": "24 小时",
"settings.wallpaper.color_label": "壁纸颜色", "settings.wallpaper.color_label": "壁纸颜色",
"settings.wallpaper.custom_color_tooltip": "自定义颜色", "settings.wallpaper.custom_color_tooltip": "自定义颜色",
"settings.wallpaper.custom_color_apply": "应用", "settings.wallpaper.custom_color_apply": "应用",
@@ -216,7 +233,14 @@
"schedule.settings.unnamed": "未命名课表", "schedule.settings.unnamed": "未命名课表",
"schedule.settings.delete": "删除", "schedule.settings.delete": "删除",
"schedule.settings.picker_title": "选择 ClassIsland 课表文件", "schedule.settings.picker_title": "选择 ClassIsland 课表文件",
"schedule.settings.picker_file_type": "ClassIsland CSES 课表", "schedule.settings.picker_file_type.all": "ClassIsland 课表文件",
"schedule.settings.picker_file_type.json": "ClassIsland 档案 (JSON)",
"schedule.settings.picker_file_type.cses": "CSES 课表 (YAML)",
"schedule.settings.semester.title": "学期设置",
"schedule.settings.semester.start_date": "学期开始日期",
"schedule.settings.semester.week_cycle": "周循环",
"schedule.settings.semester.week_cycle_desc": "设置多周课表轮换周期,用于计算当前是第几周。",
"schedule.settings.semester.week_cycle_format": "{0} 周轮换",
"worldclock.settings.title": "世界时钟设置", "worldclock.settings.title": "世界时钟设置",
"worldclock.settings.desc": "分别为四个时钟选择时区。", "worldclock.settings.desc": "分别为四个时钟选择时区。",
"worldclock.settings.clock_1": "时钟 1", "worldclock.settings.clock_1": "时钟 1",

View File

@@ -33,6 +33,8 @@ public sealed class AppSettingsSnapshot
public string WallpaperPlacement { get; set; } = "Fill"; public string WallpaperPlacement { get; set; } = "Fill";
public int SystemWallpaperRefreshIntervalSeconds { get; set; } = 300;
public int SettingsTabIndex { get; set; } = 0; public int SettingsTabIndex { get; set; } = 0;
public string? SettingsTabTag { get; set; } public string? SettingsTabTag { get; set; }

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace LanMountainDesktop.Models; namespace LanMountainDesktop.Models;
@@ -12,6 +13,10 @@ public sealed class ComponentSettingsSnapshot
public string ActiveImportedClassScheduleId { get; set; } = string.Empty; public string ActiveImportedClassScheduleId { get; set; } = string.Empty;
public DateOnly? SemesterStartDate { get; set; }
public int SemesterWeekCycle { get; set; } = 1;
public bool StudyEnvironmentShowDisplayDb { get; set; } = true; public bool StudyEnvironmentShowDisplayDb { get; set; } = true;
public bool StudyEnvironmentShowDbfs { get; set; } public bool StudyEnvironmentShowDbfs { get; set; }

View File

@@ -12,7 +12,7 @@ namespace LanMountainDesktop.Services;
public interface IClassIslandScheduleDataService public interface IClassIslandScheduleDataService
{ {
ClassIslandScheduleReadResult Load(string? inputPath = null, string? profileFileName = null); ClassIslandScheduleReadResult Load(string? inputPath = null, string? profileFileName = null, DateOnly? semesterStartDate = null, int semesterWeekCycle = 1);
bool TryResolveClassPlanForDate( bool TryResolveClassPlanForDate(
ClassIslandScheduleSnapshot snapshot, ClassIslandScheduleSnapshot snapshot,
@@ -43,7 +43,7 @@ public sealed class ClassIslandScheduleDataService : IClassIslandScheduleDataSer
.IgnoreUnmatchedProperties() .IgnoreUnmatchedProperties()
.Build(); .Build();
public ClassIslandScheduleReadResult Load(string? inputPath = null, string? profileFileName = null) public ClassIslandScheduleReadResult Load(string? inputPath = null, string? profileFileName = null, DateOnly? semesterStartDate = null, int semesterWeekCycle = 1)
{ {
var warnings = new List<string>(); var warnings = new List<string>();
try try
@@ -73,11 +73,11 @@ public sealed class ClassIslandScheduleDataService : IClassIslandScheduleDataSer
ClassIslandScheduleSnapshot snapshot; ClassIslandScheduleSnapshot snapshot;
if (source.SourceKind == ScheduleSourceKind.Cses) if (source.SourceKind == ScheduleSourceKind.Cses)
{ {
snapshot = ParseCsesSnapshot(source); snapshot = ParseCsesSnapshot(source, semesterStartDate, semesterWeekCycle);
} }
else else
{ {
var cycleRule = ParseCycleRule(source.SettingsPath, warnings); var cycleRule = ParseCycleRule(source.SettingsPath, warnings, semesterStartDate, semesterWeekCycle);
var profileJson = ReadJson(source.ProfilePath); var profileJson = ReadJson(source.ProfilePath);
snapshot = ParseProfileSnapshot(profileJson.RootElement, source, cycleRule); snapshot = ParseProfileSnapshot(profileJson.RootElement, source, cycleRule);
} }
@@ -412,22 +412,50 @@ public sealed class ClassIslandScheduleDataService : IClassIslandScheduleDataSer
return null; return null;
} }
private static ClassIslandScheduleCycleRule ParseCycleRule(string? settingsPath, List<string> warnings) private static ClassIslandScheduleCycleRule ParseCycleRule(
string? settingsPath,
List<string> warnings,
DateOnly? semesterStartDate = null,
int semesterWeekCycle = 1)
{ {
if (string.IsNullOrWhiteSpace(settingsPath) || !File.Exists(settingsPath)) DateOnly? singleWeekStartDate = semesterStartDate;
int maxCycle = semesterWeekCycle > 1 ? semesterWeekCycle : 4;
var offsetList = new List<int> { -1, -1, 0, 0, 0, 0, 0, 0 };
if (!string.IsNullOrWhiteSpace(settingsPath) && File.Exists(settingsPath))
{ {
warnings.Add("ClassIsland Settings.json not found, using default cycle rule."); using var json = ReadJson(settingsPath);
return new ClassIslandScheduleCycleRule(null, 4, new List<int> { -1, -1, 0, 0, 0 }); var root = json.RootElement;
if (!singleWeekStartDate.HasValue)
{
singleWeekStartDate = TryReadDateOnly(root, "SingleWeekStartTime");
}
if (semesterWeekCycle <= 1)
{
maxCycle = TryReadInt(root, "MultiWeekRotationMaxCycle", 4);
}
var settingsOffsetList = ReadIntList(root, "MultiWeekRotationOffset");
if (settingsOffsetList.Count >= 2)
{
offsetList = settingsOffsetList;
}
}
else
{
warnings.Add("ClassIsland Settings.json not found, using semester settings from component.");
} }
using var json = ReadJson(settingsPath); if (maxCycle < 2)
var root = json.RootElement;
var singleWeekStartDate = TryReadDateOnly(root, "SingleWeekStartTime");
var maxCycle = TryReadInt(root, "MultiWeekRotationMaxCycle", 4);
var offsetList = ReadIntList(root, "MultiWeekRotationOffset");
if (offsetList.Count < 2)
{ {
offsetList = new List<int> { -1, -1, 0, 0, 0 }; maxCycle = 2;
}
while (offsetList.Count <= maxCycle)
{
offsetList.Add(0);
} }
return new ClassIslandScheduleCycleRule( return new ClassIslandScheduleCycleRule(
@@ -469,7 +497,10 @@ public sealed class ClassIslandScheduleDataService : IClassIslandScheduleDataSer
ClassPlanGroups: groups); ClassPlanGroups: groups);
} }
private static ClassIslandScheduleSnapshot ParseCsesSnapshot(ResolvedSource source) private static ClassIslandScheduleSnapshot ParseCsesSnapshot(
ResolvedSource source,
DateOnly? semesterStartDate = null,
int semesterWeekCycle = 1)
{ {
var yaml = File.ReadAllText(source.ProfilePath); var yaml = File.ReadAllText(source.ProfilePath);
var csesProfile = CsesDeserializer.Deserialize<CsesProfileDto>(yaml) ?? new CsesProfileDto(); var csesProfile = CsesDeserializer.Deserialize<CsesProfileDto>(yaml) ?? new CsesProfileDto();
@@ -600,12 +631,19 @@ public sealed class ClassIslandScheduleDataService : IClassIslandScheduleDataSer
[GlobalClassPlanGroupId] = new ClassIslandClassPlanGroup(GlobalClassPlanGroupId, "Global", IsGlobal: true) [GlobalClassPlanGroupId] = new ClassIslandClassPlanGroup(GlobalClassPlanGroupId, "Global", IsGlobal: true)
}; };
var maxCycle = semesterWeekCycle > 1 ? semesterWeekCycle : 4;
var offsetList = new List<int> { -1, -1, 0, 0, 0, 0, 0, 0 };
while (offsetList.Count <= maxCycle)
{
offsetList.Add(0);
}
return new ClassIslandScheduleSnapshot( return new ClassIslandScheduleSnapshot(
SourceRootPath: source.SourceRootPath, SourceRootPath: source.SourceRootPath,
ProfilePath: source.ProfilePath, ProfilePath: source.ProfilePath,
ProfileFileName: source.ProfileFileName, ProfileFileName: source.ProfileFileName,
LoadedAt: DateTimeOffset.Now, LoadedAt: DateTimeOffset.Now,
CycleRule: new ClassIslandScheduleCycleRule(null, 4, new List<int> { -1, -1, 0, 0, 0 }), CycleRule: new ClassIslandScheduleCycleRule(semesterStartDate, Math.Clamp(maxCycle, 2, 32), offsetList),
SelectedClassPlanGroupId: DefaultClassPlanGroupId, SelectedClassPlanGroupId: DefaultClassPlanGroupId,
TempClassPlanGroupId: null, TempClassPlanGroupId: null,
IsTempClassPlanGroupEnabled: false, IsTempClassPlanGroupEnabled: false,

View File

@@ -16,7 +16,13 @@ public enum WallpaperMediaType
} }
public sealed record GridSettingsState(int ShortSideCells, string SpacingPreset, int EdgeInsetPercent); public sealed record GridSettingsState(int ShortSideCells, string SpacingPreset, int EdgeInsetPercent);
public sealed record WallpaperSettingsState(string? WallpaperPath, string Type, string? Color, string Placement, string? CustomColor = null); public sealed record WallpaperSettingsState(
string? WallpaperPath,
string Type,
string? Color,
string Placement,
string? CustomColor = null,
int SystemWallpaperRefreshIntervalSeconds = 300);
public sealed record ThemeAppearanceSettingsState( public sealed record ThemeAppearanceSettingsState(
bool IsNightMode, bool IsNightMode,
string? ThemeColor, string? ThemeColor,

View File

@@ -101,7 +101,9 @@ internal sealed class WallpaperSettingsService : IWallpaperSettingsService
: snapshot.WallpaperPath, : snapshot.WallpaperPath,
normalizedType, normalizedType,
snapshot.WallpaperColor, snapshot.WallpaperColor,
snapshot.WallpaperPlacement); snapshot.WallpaperPlacement,
CustomColor: null,
SystemWallpaperRefreshIntervalSeconds: NormalizeRefreshInterval(snapshot.SystemWallpaperRefreshIntervalSeconds));
} }
public void Save(WallpaperSettingsState state) public void Save(WallpaperSettingsState state)
@@ -128,6 +130,7 @@ internal sealed class WallpaperSettingsService : IWallpaperSettingsService
snapshot.WallpaperPlacement = string.IsNullOrWhiteSpace(state.Placement) snapshot.WallpaperPlacement = string.IsNullOrWhiteSpace(state.Placement)
? "Fill" ? "Fill"
: state.Placement.Trim(); : state.Placement.Trim();
snapshot.SystemWallpaperRefreshIntervalSeconds = NormalizeRefreshInterval(state.SystemWallpaperRefreshIntervalSeconds);
_settingsService.SaveSnapshot( _settingsService.SaveSnapshot(
SettingsScope.App, SettingsScope.App,
snapshot, snapshot,
@@ -136,9 +139,21 @@ internal sealed class WallpaperSettingsService : IWallpaperSettingsService
nameof(AppSettingsSnapshot.WallpaperPath), nameof(AppSettingsSnapshot.WallpaperPath),
nameof(AppSettingsSnapshot.WallpaperType), nameof(AppSettingsSnapshot.WallpaperType),
nameof(AppSettingsSnapshot.WallpaperColor), nameof(AppSettingsSnapshot.WallpaperColor),
nameof(AppSettingsSnapshot.WallpaperPlacement) nameof(AppSettingsSnapshot.WallpaperPlacement),
nameof(AppSettingsSnapshot.SystemWallpaperRefreshIntervalSeconds)
]); ]);
} }
private static int NormalizeRefreshInterval(int seconds)
{
return seconds switch
{
<= 0 => 300,
< 30 => 30,
> 86400 => 86400,
_ => seconds
};
}
} }
internal sealed class WallpaperMediaService : IWallpaperMediaService internal sealed class WallpaperMediaService : IWallpaperMediaService

View File

@@ -0,0 +1,65 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using Avalonia.Media.Imaging;
using Microsoft.Win32;
namespace LanMountainDesktop.Services;
public interface ISystemWallpaperProvider
{
bool IsSupported { get; }
string? GetWallpaperPath();
event EventHandler? WallpaperChanged;
}
internal sealed class SystemWallpaperProvider : ISystemWallpaperProvider, IDisposable
{
public bool IsSupported => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public event EventHandler? WallpaperChanged;
public string? GetWallpaperPath()
{
if (!IsSupported)
{
return null;
}
try
{
using var key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Desktop");
var wallpaperPath = key?.GetValue("Wallpaper") as string;
if (string.IsNullOrWhiteSpace(wallpaperPath))
{
return null;
}
if (!File.Exists(wallpaperPath))
{
return null;
}
return wallpaperPath;
}
catch
{
return null;
}
}
public void Dispose()
{
}
}
public static class HostSystemWallpaperProvider
{
private static ISystemWallpaperProvider? _instance;
public static ISystemWallpaperProvider GetOrCreate()
{
return _instance ??= new SystemWallpaperProvider();
}
}

View File

@@ -5,13 +5,13 @@ using Avalonia.Media.Imaging;
namespace LanMountainDesktop.Services; namespace LanMountainDesktop.Services;
internal static class WallpaperImageBrushFactory public static class WallpaperImageBrushFactory
{ {
internal const string Fill = "Fill"; public const string Fill = "Fill";
internal const string Fit = "Fit"; public const string Fit = "Fit";
internal const string StretchMode = "Stretch"; public const string StretchMode = "Stretch";
internal const string Center = "Center"; public const string Center = "Center";
internal const string Tile = "Tile"; public const string Tile = "Tile";
public static string NormalizePlacement(string? placement) public static string NormalizePlacement(string? placement)
{ {

View File

@@ -1,7 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
@@ -15,6 +16,7 @@ namespace LanMountainDesktop.ViewModels;
public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
{ {
private readonly ISettingsFacadeService _settingsFacade; private readonly ISettingsFacadeService _settingsFacade;
private readonly ISystemWallpaperProvider _systemWallpaperProvider;
private readonly LocalizationService _localizationService = new(); private readonly LocalizationService _localizationService = new();
private readonly string _languageCode; private readonly string _languageCode;
private bool _isInitializing; private bool _isInitializing;
@@ -22,9 +24,11 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
public WallpaperSettingsPageViewModel(ISettingsFacadeService settingsFacade) public WallpaperSettingsPageViewModel(ISettingsFacadeService settingsFacade)
{ {
_settingsFacade = settingsFacade; _settingsFacade = settingsFacade;
_systemWallpaperProvider = HostSystemWallpaperProvider.GetOrCreate();
_languageCode = _localizationService.NormalizeLanguageCode(_settingsFacade.Region.Get().LanguageCode); _languageCode = _localizationService.NormalizeLanguageCode(_settingsFacade.Region.Get().LanguageCode);
WallpaperPlacements = CreateWallpaperPlacements(); WallpaperPlacements = CreateWallpaperPlacements();
WallpaperTypes = CreateWallpaperTypes(); WallpaperTypes = CreateWallpaperTypes();
RefreshIntervals = CreateRefreshIntervals();
PresetColors = CreatePresetColors(); PresetColors = CreatePresetColors();
RefreshLocalizedText(); RefreshLocalizedText();
@@ -35,8 +39,11 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
public IReadOnlyList<SelectionOption> WallpaperPlacements { get; } public IReadOnlyList<SelectionOption> WallpaperPlacements { get; }
public IReadOnlyList<SelectionOption> WallpaperTypes { get; } public IReadOnlyList<SelectionOption> WallpaperTypes { get; }
public IReadOnlyList<SelectionOption> RefreshIntervals { get; }
public IReadOnlyList<string> PresetColors { get; } public IReadOnlyList<string> PresetColors { get; }
public bool IsSystemWallpaperSupported => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
[ObservableProperty] [ObservableProperty]
private string _wallpaperPath = string.Empty; private string _wallpaperPath = string.Empty;
@@ -49,6 +56,9 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
[ObservableProperty] [ObservableProperty]
private SelectionOption _selectedWallpaperPlacement = null!; private SelectionOption _selectedWallpaperPlacement = null!;
[ObservableProperty]
private SelectionOption _selectedRefreshInterval = null!;
[ObservableProperty] [ObservableProperty]
private string _wallpaperHeader = string.Empty; private string _wallpaperHeader = string.Empty;
@@ -73,6 +83,18 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
[ObservableProperty] [ObservableProperty]
private string _filePickerTitle = string.Empty; private string _filePickerTitle = string.Empty;
[ObservableProperty]
private string _systemWallpaperLabel = string.Empty;
[ObservableProperty]
private string _refreshIntervalLabel = string.Empty;
[ObservableProperty]
private string _refreshButtonTooltip = string.Empty;
[ObservableProperty]
private string _systemWallpaperStatus = string.Empty;
[ObservableProperty] [ObservableProperty]
private bool _isImageOrVideo; private bool _isImageOrVideo;
@@ -82,13 +104,15 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
[ObservableProperty] [ObservableProperty]
private bool _isImage; private bool _isImage;
[ObservableProperty]
private bool _isSystemWallpaper;
[ObservableProperty] [ObservableProperty]
private Bitmap? _previewImage; private Bitmap? _previewImage;
[ObservableProperty] [ObservableProperty]
private IBrush? _previewBrush; private IBrush? _previewBrush;
// 自定义颜色持久化
[ObservableProperty] [ObservableProperty]
private Color _customColor = Colors.White; private Color _customColor = Colors.White;
@@ -110,7 +134,11 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
string.Equals(option.Value, wallpaperPlacement, StringComparison.OrdinalIgnoreCase)) string.Equals(option.Value, wallpaperPlacement, StringComparison.OrdinalIgnoreCase))
?? WallpaperPlacements[0]; ?? WallpaperPlacements[0];
// 加载自定义颜色 var refreshIntervalSeconds = wallpaper.SystemWallpaperRefreshIntervalSeconds;
SelectedRefreshInterval = RefreshIntervals.FirstOrDefault(option =>
GetIntervalSeconds(option.Value) == refreshIntervalSeconds)
?? RefreshIntervals[2];
if (!string.IsNullOrWhiteSpace(wallpaper.CustomColor) && Color.TryParse(wallpaper.CustomColor, out var customColor)) if (!string.IsNullOrWhiteSpace(wallpaper.CustomColor) && Color.TryParse(wallpaper.CustomColor, out var customColor))
{ {
CustomColor = customColor; CustomColor = customColor;
@@ -119,6 +147,7 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
UpdateVisibility(); UpdateVisibility();
UpdatePreviewFromCurrentSelection(); UpdatePreviewFromCurrentSelection();
UpdateSystemWallpaperStatus();
} }
partial void OnSelectedWallpaperTypeChanged(SelectionOption value) partial void OnSelectedWallpaperTypeChanged(SelectionOption value)
@@ -132,8 +161,9 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
private void UpdateVisibility() private void UpdateVisibility()
{ {
IsImage = SelectedWallpaperType?.Value == "Image"; IsImage = SelectedWallpaperType?.Value == "Image";
IsImageOrVideo = IsImage; IsImageOrVideo = IsImage || SelectedWallpaperType?.Value == "SystemWallpaper";
IsSolidColor = SelectedWallpaperType?.Value == "SolidColor"; IsSolidColor = SelectedWallpaperType?.Value == "SolidColor";
IsSystemWallpaper = SelectedWallpaperType?.Value == "SystemWallpaper";
} }
partial void OnSelectedColorChanged(string? value) partial void OnSelectedColorChanged(string? value)
@@ -145,13 +175,18 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
partial void OnCustomColorChanged(Color value) partial void OnCustomColorChanged(Color value)
{ {
CustomColorBrush = new SolidColorBrush(value); CustomColorBrush = new SolidColorBrush(value);
// 将自定义颜色应用到壁纸
var colorHex = $"#{value.A:X2}{value.R:X2}{value.G:X2}{value.B:X2}"; var colorHex = $"#{value.A:X2}{value.R:X2}{value.G:X2}{value.B:X2}";
SelectedColor = colorHex; SelectedColor = colorHex;
if (_isInitializing) return; if (_isInitializing) return;
SaveWallpaper(); SaveWallpaper();
} }
partial void OnSelectedRefreshIntervalChanged(SelectionOption value)
{
if (_isInitializing) return;
SaveWallpaper();
}
public async Task ImportWallpaperAsync(string sourcePath) public async Task ImportWallpaperAsync(string sourcePath)
{ {
var importedPath = await _settingsFacade.WallpaperMedia.ImportAssetAsync(sourcePath); var importedPath = await _settingsFacade.WallpaperMedia.ImportAssetAsync(sourcePath);
@@ -170,6 +205,12 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
private void UpdatePreviewFromCurrentSelection() private void UpdatePreviewFromCurrentSelection()
{ {
if (IsSystemWallpaper)
{
UpdateSystemWallpaperPreview();
return;
}
if (!IsImage) if (!IsImage)
{ {
ClearPreviewImage(); ClearPreviewImage();
@@ -180,10 +221,24 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
UpdatePreviewImage(WallpaperPath); UpdatePreviewImage(WallpaperPath);
} }
private void UpdatePreviewImage(string path) private void UpdateSystemWallpaperPreview()
{
var systemPath = _systemWallpaperProvider.GetWallpaperPath();
if (string.IsNullOrWhiteSpace(systemPath))
{
ClearPreviewImage();
SystemWallpaperStatus = L("settings.wallpaper.system.unavailable", "Unable to read system wallpaper");
return;
}
SystemWallpaperStatus = systemPath;
UpdatePreviewImage(systemPath);
}
private void UpdatePreviewImage(string? path)
{ {
var previousPreview = PreviewImage; var previousPreview = PreviewImage;
if (string.IsNullOrWhiteSpace(path) || !System.IO.File.Exists(path)) if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
{ {
previousPreview?.Dispose(); previousPreview?.Dispose();
PreviewImage = null; PreviewImage = null;
@@ -193,7 +248,7 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
try try
{ {
using var stream = System.IO.File.OpenRead(path); using var stream = File.OpenRead(path);
var bitmap = new Bitmap(stream); var bitmap = new Bitmap(stream);
PreviewImage = bitmap; PreviewImage = bitmap;
PreviewBrush = WallpaperImageBrushFactory.Create(bitmap, SelectedWallpaperPlacement?.Value); PreviewBrush = WallpaperImageBrushFactory.Create(bitmap, SelectedWallpaperPlacement?.Value);
@@ -215,9 +270,21 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
previousPreview?.Dispose(); previousPreview?.Dispose();
} }
private void UpdateSystemWallpaperStatus()
{
if (!IsSystemWallpaper) return;
UpdateSystemWallpaperPreview();
}
[RelayCommand]
private void RefreshSystemWallpaper()
{
UpdateSystemWallpaperPreview();
}
partial void OnSelectedWallpaperPlacementChanged(SelectionOption value) partial void OnSelectedWallpaperPlacementChanged(SelectionOption value)
{ {
if (IsImage && PreviewImage is not null) if ((IsImage || IsSystemWallpaper) && PreviewImage is not null)
{ {
PreviewBrush = WallpaperImageBrushFactory.Create(PreviewImage, value?.Value); PreviewBrush = WallpaperImageBrushFactory.Create(PreviewImage, value?.Value);
} }
@@ -236,16 +303,46 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
{ {
var selectedType = SelectedWallpaperType?.Value ?? "Image"; var selectedType = SelectedWallpaperType?.Value ?? "Image";
var selectedPlacement = SelectedWallpaperPlacement?.Value ?? WallpaperImageBrushFactory.Fill; var selectedPlacement = SelectedWallpaperPlacement?.Value ?? WallpaperImageBrushFactory.Fill;
var normalizedPath = SelectedWallpaperType?.Value == "SolidColor" || string.IsNullOrWhiteSpace(WallpaperPath) var refreshIntervalSeconds = GetIntervalSeconds(SelectedRefreshInterval?.Value);
? null
: WallpaperPath; string? normalizedPath;
if (selectedType == "SolidColor" || selectedType == "SystemWallpaper")
{
normalizedPath = null;
}
else
{
normalizedPath = string.IsNullOrWhiteSpace(WallpaperPath) ? null : WallpaperPath;
}
var customColorHex = $"#{CustomColor.A:X2}{CustomColor.R:X2}{CustomColor.G:X2}{CustomColor.B:X2}"; var customColorHex = $"#{CustomColor.A:X2}{CustomColor.R:X2}{CustomColor.G:X2}{CustomColor.B:X2}";
_settingsFacade.Wallpaper.Save(new WallpaperSettingsState( _settingsFacade.Wallpaper.Save(new WallpaperSettingsState(
normalizedPath, normalizedPath,
selectedType, selectedType,
SelectedColor, SelectedColor,
selectedPlacement, selectedPlacement,
customColorHex)); customColorHex,
refreshIntervalSeconds));
}
private static int GetIntervalSeconds(string? value)
{
return value switch
{
"30s" => 30,
"1m" => 60,
"5m" => 300,
"10m" => 600,
"15m" => 900,
"30m" => 1800,
"1h" => 3600,
"2h" => 7200,
"4h" => 14400,
"8h" => 28800,
"12h" => 43200,
"24h" => 86400,
_ => 300
};
} }
private IReadOnlyList<SelectionOption> CreateWallpaperPlacements() private IReadOnlyList<SelectionOption> CreateWallpaperPlacements()
@@ -262,10 +359,36 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
private IReadOnlyList<SelectionOption> CreateWallpaperTypes() private IReadOnlyList<SelectionOption> CreateWallpaperTypes()
{ {
return var types = new List<SelectionOption>
[ {
new SelectionOption("Image", L("settings.wallpaper.type.image", "Image")), new SelectionOption("Image", L("settings.wallpaper.type.image", "Image")),
new SelectionOption("SolidColor", L("settings.wallpaper.type.solid_color", "Solid Color")) new SelectionOption("SolidColor", L("settings.wallpaper.type.solid_color", "Solid Color"))
};
if (IsSystemWallpaperSupported)
{
types.Add(new SelectionOption("SystemWallpaper", L("settings.wallpaper.type.system", "System Wallpaper")));
}
return types;
}
private IReadOnlyList<SelectionOption> CreateRefreshIntervals()
{
return
[
new SelectionOption("30s", L("settings.wallpaper.refresh.30s", "30 seconds")),
new SelectionOption("1m", L("settings.wallpaper.refresh.1m", "1 minute")),
new SelectionOption("5m", L("settings.wallpaper.refresh.5m", "5 minutes")),
new SelectionOption("10m", L("settings.wallpaper.refresh.10m", "10 minutes")),
new SelectionOption("15m", L("settings.wallpaper.refresh.15m", "15 minutes")),
new SelectionOption("30m", L("settings.wallpaper.refresh.30m", "30 minutes")),
new SelectionOption("1h", L("settings.wallpaper.refresh.1h", "1 hour")),
new SelectionOption("2h", L("settings.wallpaper.refresh.2h", "2 hours")),
new SelectionOption("4h", L("settings.wallpaper.refresh.4h", "4 hours")),
new SelectionOption("8h", L("settings.wallpaper.refresh.8h", "8 hours")),
new SelectionOption("12h", L("settings.wallpaper.refresh.12h", "12 hours")),
new SelectionOption("24h", L("settings.wallpaper.refresh.24h", "24 hours"))
]; ];
} }
@@ -289,6 +412,9 @@ public sealed partial class WallpaperSettingsPageViewModel : ViewModelBase
WallpaperPlacementDescription = L("settings.wallpaper.placement_desc", "Adjust how the image fills the desktop."); WallpaperPlacementDescription = L("settings.wallpaper.placement_desc", "Adjust how the image fills the desktop.");
ImportWallpaperButtonText = L("settings.wallpaper.pick_button", "Import Wallpaper"); ImportWallpaperButtonText = L("settings.wallpaper.pick_button", "Import Wallpaper");
FilePickerTitle = L("filepicker.title", "Select wallpaper"); FilePickerTitle = L("filepicker.title", "Select wallpaper");
SystemWallpaperLabel = L("settings.wallpaper.system.label", "System Wallpaper");
RefreshIntervalLabel = L("settings.wallpaper.refresh_interval", "Refresh Interval");
RefreshButtonTooltip = L("settings.wallpaper.refresh_now", "Refresh Now");
} }
private string L(string key, string fallback) private string L(string key, string fallback)

View File

@@ -2,6 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:material="clr-namespace:Material.Styles;assembly=Material.Styles"
xmlns:materialAssists="clr-namespace:Material.Styles.Assists;assembly=Material.Styles"
mc:Ignorable="d" mc:Ignorable="d"
x:Class="LanMountainDesktop.Views.ComponentEditors.ClassScheduleComponentEditor"> x:Class="LanMountainDesktop.Views.ComponentEditors.ClassScheduleComponentEditor">
<StackPanel Spacing="16"> <StackPanel Spacing="16">
@@ -36,6 +38,45 @@
</StackPanel> </StackPanel>
</Border> </Border>
<Border Classes="component-editor-card"
Padding="20">
<StackPanel Spacing="16">
<TextBlock x:Name="SemesterSettingsHeaderTextBlock"
Classes="component-editor-section-title" />
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="16">
<StackPanel Spacing="8">
<TextBlock x:Name="SemesterStartDateLabel"
Classes="component-editor-secondary-text" />
<CalendarDatePicker x:Name="SemesterStartDatePicker"
HorizontalAlignment="Stretch"
SelectedDateChanged="OnSemesterStartDateChanged" />
</StackPanel>
<StackPanel Grid.Column="1" Spacing="8" Width="160">
<TextBlock x:Name="WeekCycleLabel"
Classes="component-editor-secondary-text" />
<ComboBox x:Name="WeekCycleComboBox"
Classes="component-editor-select"
HorizontalAlignment="Stretch"
SelectionChanged="OnWeekCycleSelectionChanged">
<ComboBoxItem Content="1" Tag="1" />
<ComboBoxItem Content="2" Tag="2" />
<ComboBoxItem Content="3" Tag="3" />
<ComboBoxItem Content="4" Tag="4" />
<ComboBoxItem Content="5" Tag="5" />
<ComboBoxItem Content="6" Tag="6" />
<ComboBoxItem Content="7" Tag="7" />
</ComboBox>
</StackPanel>
</Grid>
<TextBlock x:Name="WeekCycleDescription"
Classes="component-editor-secondary-text"
TextWrapping="Wrap"
Opacity="0.7" />
</StackPanel>
</Border>
<Border Classes="component-editor-card" <Border Classes="component-editor-card"
Padding="20"> Padding="20">
<StackPanel Spacing="12"> <StackPanel Spacing="12">

View File

@@ -76,6 +76,11 @@ public partial class ClassScheduleComponentEditor : ComponentEditorViewBase
FollowSystemColorSchemeItem.Content = L("component.color_scheme.follow_system", "Follow system color scheme"); FollowSystemColorSchemeItem.Content = L("component.color_scheme.follow_system", "Follow system color scheme");
UseNativeColorSchemeItem.Content = L("component.color_scheme.native", "Use component custom color scheme"); UseNativeColorSchemeItem.Content = L("component.color_scheme.native", "Use component custom color scheme");
SemesterSettingsHeaderTextBlock.Text = L("schedule.settings.semester.title", "Semester Settings");
SemesterStartDateLabel.Text = L("schedule.settings.semester.start_date", "Semester Start Date");
WeekCycleLabel.Text = L("schedule.settings.semester.week_cycle", "Week Cycle");
WeekCycleDescription.Text = L("schedule.settings.semester.week_cycle_desc", "Set the week rotation cycle for multi-week schedules (e.g., 2 for odd/even weeks).");
AddScheduleButton.Content = L("schedule.settings.add", "Add Schedule"); AddScheduleButton.Content = L("schedule.settings.add", "Add Schedule");
EmptyStateTextBlock.Text = L("schedule.settings.empty", "No imported schedules yet."); EmptyStateTextBlock.Text = L("schedule.settings.empty", "No imported schedules yet.");
@@ -85,9 +90,25 @@ public partial class ClassScheduleComponentEditor : ComponentEditorViewBase
string.Equals(colorSchemeSource, ThemeAppearanceValues.ColorSchemeFollowSystem, StringComparison.OrdinalIgnoreCase) string.Equals(colorSchemeSource, ThemeAppearanceValues.ColorSchemeFollowSystem, StringComparison.OrdinalIgnoreCase)
? FollowSystemColorSchemeItem ? FollowSystemColorSchemeItem
: UseNativeColorSchemeItem; : UseNativeColorSchemeItem;
if (snapshot.SemesterStartDate.HasValue)
{
SemesterStartDatePicker.SelectedDate = snapshot.SemesterStartDate.Value.ToDateTime(TimeOnly.MinValue);
}
var weekCycle = Math.Clamp(snapshot.SemesterWeekCycle, 1, 7);
WeekCycleComboBox.SelectedIndex = weekCycle - 1;
UpdateWeekCycleDescription(weekCycle);
_suppressEvents = false; _suppressEvents = false;
} }
private void UpdateWeekCycleDescription(int weekCycle)
{
var format = L("schedule.settings.semester.week_cycle_format", "{0}-week rotation");
WeekCycleDescription.Text = string.Format(format, weekCycle);
}
private void OnColorSchemeSelectionChanged(object? sender, SelectionChangedEventArgs e) private void OnColorSchemeSelectionChanged(object? sender, SelectionChangedEventArgs e)
{ {
_ = sender; _ = sender;
@@ -106,6 +127,39 @@ public partial class ClassScheduleComponentEditor : ComponentEditorViewBase
SaveSnapshot(snapshot, nameof(ComponentSettingsSnapshot.ColorSchemeSource)); SaveSnapshot(snapshot, nameof(ComponentSettingsSnapshot.ColorSchemeSource));
} }
private void OnSemesterStartDateChanged(object? sender, SelectionChangedEventArgs e)
{
if (_suppressEvents)
{
return;
}
var snapshot = LoadSnapshot();
if (SemesterStartDatePicker.SelectedDate.HasValue)
{
snapshot.SemesterStartDate = DateOnly.FromDateTime(SemesterStartDatePicker.SelectedDate.Value);
}
else
{
snapshot.SemesterStartDate = null;
}
SaveSnapshot(snapshot, nameof(ComponentSettingsSnapshot.SemesterStartDate));
}
private void OnWeekCycleSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (_suppressEvents)
{
return;
}
var weekCycle = WeekCycleComboBox.SelectedIndex + 1;
var snapshot = LoadSnapshot();
snapshot.SemesterWeekCycle = weekCycle;
SaveSnapshot(snapshot, nameof(ComponentSettingsSnapshot.SemesterWeekCycle));
UpdateWeekCycleDescription(weekCycle);
}
private async void OnAddScheduleClick(object? sender, RoutedEventArgs e) private async void OnAddScheduleClick(object? sender, RoutedEventArgs e)
{ {
_ = sender; _ = sender;
@@ -122,7 +176,15 @@ public partial class ClassScheduleComponentEditor : ComponentEditorViewBase
AllowMultiple = false, AllowMultiple = false,
FileTypeFilter = FileTypeFilter =
[ [
new FilePickerFileType(L("schedule.settings.picker_file_type", "ClassIsland CSES Schedule")) new FilePickerFileType(L("schedule.settings.picker_file_type.all", "ClassIsland Schedule Files"))
{
Patterns = ["*.json", "*.cses", "*.yaml", "*.yml"]
},
new FilePickerFileType(L("schedule.settings.picker_file_type.json", "ClassIsland Profile (JSON)"))
{
Patterns = ["*.json"]
},
new FilePickerFileType(L("schedule.settings.picker_file_type.cses", "CSES Schedule (YAML)"))
{ {
Patterns = ["*.cses", "*.yaml", "*.yml"] Patterns = ["*.cses", "*.yaml", "*.yml"]
} }

View File

@@ -253,7 +253,11 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
var today = DateOnly.FromDateTime(now); var today = DateOnly.FromDateTime(now);
var importedSchedulePath = ResolveImportedSchedulePath(componentSettings); var importedSchedulePath = ResolveImportedSchedulePath(componentSettings);
var readResult = _scheduleService.Load(importedSchedulePath); var readResult = _scheduleService.Load(
importedSchedulePath,
profileFileName: null,
semesterStartDate: componentSettings.SemesterStartDate,
semesterWeekCycle: componentSettings.SemesterWeekCycle);
if (!readResult.Success || readResult.Snapshot is null) if (!readResult.Success || readResult.Snapshot is null)
{ {
_courseItems = Array.Empty<CourseItemViewModel>(); _courseItems = Array.Empty<CourseItemViewModel>();

View File

@@ -10,7 +10,7 @@
x:Class="LanMountainDesktop.Views.Components.OfficeRecentDocumentsWidget"> x:Class="LanMountainDesktop.Views.Components.OfficeRecentDocumentsWidget">
<Border x:Name="RootBorder" <Border x:Name="RootBorder"
CornerRadius="{DynamicResource DesignCornerRadiusIsland}" CornerRadius="{DynamicResource DesignCornerRadiusLg}"
Background="#2D5A8E" Background="#2D5A8E"
ClipToBounds="True" ClipToBounds="True"
BorderThickness="0" BorderThickness="0"

View File

@@ -213,10 +213,17 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget,
ContentGrid.ColumnDefinitions[1].Width = new GridLength(showDial ? dialSize : 0, GridUnitType.Pixel); ContentGrid.ColumnDefinitions[1].Width = new GridLength(showDial ? dialSize : 0, GridUnitType.Pixel);
} }
var leftWidthFactor = Math.Clamp(leftContentWidth / 122d, 0.48, 1.35); var timeTextWidth = leftContentWidth * 0.92;
TimeTextBlock.FontSize = Math.Clamp((metrics.PrimaryTemperatureFont * 0.74) * scale * compactFactor * leftWidthFactor, 10, 62); var timeCharCount = 5;
DateTextBlock.FontSize = Math.Clamp(metrics.SecondaryTextFont * scale * compactFactor * leftWidthFactor, 8, 30); var maxTimeFontSize = timeTextWidth / (timeCharCount * 0.58);
var weatherIconSize = Math.Clamp(metrics.IconFont * scale * compactFactor * leftWidthFactor, 9, 32); var baseTimeFontSize = Math.Clamp(maxTimeFontSize, 12, 48);
var timeFontSize = Math.Clamp(baseTimeFontSize * scale * compactFactor, 10, 48);
TimeTextBlock.FontSize = timeFontSize;
var dateFontSize = Math.Clamp(timeFontSize * 0.48, 8, 22);
DateTextBlock.FontSize = dateFontSize;
var weatherIconSize = Math.Clamp(dateFontSize * 1.1, 10, 24);
WeatherIconImage.Width = weatherIconSize; WeatherIconImage.Width = weatherIconSize;
WeatherIconImage.Height = weatherIconSize; WeatherIconImage.Height = weatherIconSize;
@@ -226,11 +233,11 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget,
LeftStack.Width = leftContentWidth; LeftStack.Width = leftContentWidth;
LeftStack.MaxWidth = leftContentWidth; LeftStack.MaxWidth = leftContentWidth;
DateWeatherStack.MaxWidth = leftContentWidth; DateWeatherStack.MaxWidth = leftContentWidth;
TimeTextBlock.MaxWidth = leftContentWidth; TimeTextBlock.MaxWidth = timeTextWidth;
var showDateLine = leftContentWidth >= Math.Max(40, TimeTextBlock.FontSize * 1.72); var showDateLine = leftContentWidth >= Math.Max(36, timeFontSize * 1.4) && contentHeight >= 38;
DateWeatherStack.IsVisible = showDateLine; DateWeatherStack.IsVisible = showDateLine;
WeatherIconImage.IsVisible = showDateLine && leftContentWidth >= Math.Max(56, DateTextBlock.FontSize * 2.4); WeatherIconImage.IsVisible = showDateLine && leftContentWidth >= Math.Max(48, dateFontSize * 3.2);
var dateReservedWidth = WeatherIconImage.IsVisible var dateReservedWidth = WeatherIconImage.IsVisible
? weatherIconSize + DateWeatherStack.Spacing ? weatherIconSize + DateWeatherStack.Spacing
@@ -477,14 +484,22 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget,
: CreateBrush("#F8FAFF"); : CreateBrush("#F8FAFF");
AnalogDialBorder.BorderBrush = CreateBrush(isNightMode ? "#34DDE7FF" : "#12000000"); AnalogDialBorder.BorderBrush = CreateBrush(isNightMode ? "#34DDE7FF" : "#12000000");
TimeTextBlock.Foreground = WeatherTypographyAccessibility.CreateReadableBrush( if (isNightMode)
isNightMode ? "#F8FBFF" : "#10131A", {
backgroundSamples, TimeTextBlock.Foreground = WeatherTypographyAccessibility.CreateReadableBrush(
WeatherTypographyAccessibility.WcagLargeTextContrast); "#F8FBFF",
DateTextBlock.Foreground = WeatherTypographyAccessibility.CreateReadableBrush( backgroundSamples,
isNightMode ? "#BCC8DD" : "#7A7E87", WeatherTypographyAccessibility.WcagLargeTextContrast);
backgroundSamples, DateTextBlock.Foreground = WeatherTypographyAccessibility.CreateReadableBrush(
WeatherTypographyAccessibility.WcagNormalTextContrast); "#BCC8DD",
backgroundSamples,
WeatherTypographyAccessibility.WcagNormalTextContrast);
}
else
{
TimeTextBlock.Foreground = CreateBrush("#10131A");
DateTextBlock.Foreground = CreateBrush("#7A7E87");
}
_hourHandLine.Stroke = CreateBrush(isNightMode ? "#F1F5FF" : "#232938"); _hourHandLine.Stroke = CreateBrush(isNightMode ? "#F1F5FF" : "#232938");
_minuteHandLine.Stroke = CreateBrush(isNightMode ? "#D6E0F2" : "#2F3749"); _minuteHandLine.Stroke = CreateBrush(isNightMode ? "#D6E0F2" : "#2F3749");

View File

@@ -215,17 +215,21 @@ public partial class MainWindow
string? savedWallpaperPath, string? savedWallpaperPath,
string? type = null, string? type = null,
string? color = null, string? color = null,
string? placement = null) string? placement = null,
int systemWallpaperRefreshIntervalSeconds = 300)
{ {
_wallpaperPath = string.IsNullOrWhiteSpace(savedWallpaperPath) ? null : savedWallpaperPath; _wallpaperPath = string.IsNullOrWhiteSpace(savedWallpaperPath) ? null : savedWallpaperPath;
_wallpaperType = string.IsNullOrWhiteSpace(type) ? "Image" : type.Trim(); _wallpaperType = string.IsNullOrWhiteSpace(type) ? "Image" : type.Trim();
_wallpaperPlacement = WallpaperImageBrushFactory.NormalizePlacement(placement); _wallpaperPlacement = WallpaperImageBrushFactory.NormalizePlacement(placement);
_wallpaperSolidColor = TryParseColor(color, out var parsedColor) ? parsedColor : null; _wallpaperSolidColor = TryParseColor(color, out var parsedColor) ? parsedColor : null;
_wallpaperDisplayState = WallpaperDisplayState.NoWallpaperConfigured; _wallpaperDisplayState = WallpaperDisplayState.NoWallpaperConfigured;
_systemWallpaperRefreshIntervalSeconds = systemWallpaperRefreshIntervalSeconds;
_wallpaperBitmap?.Dispose(); _wallpaperBitmap?.Dispose();
_wallpaperBitmap = null; _wallpaperBitmap = null;
StopSystemWallpaperTimer();
if (string.Equals(_wallpaperType, "SolidColor", StringComparison.OrdinalIgnoreCase)) if (string.Equals(_wallpaperType, "SolidColor", StringComparison.OrdinalIgnoreCase))
{ {
_wallpaperMediaType = WallpaperMediaType.SolidColor; _wallpaperMediaType = WallpaperMediaType.SolidColor;
@@ -235,6 +239,14 @@ public partial class MainWindow
return; return;
} }
if (string.Equals(_wallpaperType, "SystemWallpaper", StringComparison.OrdinalIgnoreCase))
{
_wallpaperMediaType = WallpaperMediaType.Image;
LoadSystemWallpaper();
StartSystemWallpaperTimer();
return;
}
if (string.IsNullOrWhiteSpace(_wallpaperPath)) if (string.IsNullOrWhiteSpace(_wallpaperPath))
{ {
_wallpaperMediaType = WallpaperMediaType.None; _wallpaperMediaType = WallpaperMediaType.None;
@@ -273,6 +285,69 @@ public partial class MainWindow
} }
} }
private void LoadSystemWallpaper()
{
var systemPath = _systemWallpaperProvider.GetWallpaperPath();
if (string.IsNullOrWhiteSpace(systemPath) || !File.Exists(systemPath))
{
_wallpaperDisplayState = WallpaperDisplayState.TemporarilyUnavailable;
_wallpaperBitmap?.Dispose();
_wallpaperBitmap = null;
return;
}
try
{
using var stream = File.OpenRead(systemPath);
_wallpaperBitmap?.Dispose();
_wallpaperBitmap = new Bitmap(stream);
_wallpaperPath = systemPath;
_wallpaperDisplayState = WallpaperDisplayState.CurrentValidWallpaper;
CacheLastValidWallpaperBitmap(systemPath);
}
catch
{
_wallpaperDisplayState = WallpaperDisplayState.TemporarilyUnavailable;
_wallpaperBitmap?.Dispose();
_wallpaperBitmap = null;
}
}
private void StartSystemWallpaperTimer()
{
StopSystemWallpaperTimer();
var intervalSeconds = Math.Clamp(_systemWallpaperRefreshIntervalSeconds, 30, 86400);
_systemWallpaperRefreshTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(intervalSeconds)
};
_systemWallpaperRefreshTimer.Tick += OnSystemWallpaperRefreshTimerTick;
_systemWallpaperRefreshTimer.Start();
}
private void StopSystemWallpaperTimer()
{
if (_systemWallpaperRefreshTimer is not null)
{
_systemWallpaperRefreshTimer.Stop();
_systemWallpaperRefreshTimer.Tick -= OnSystemWallpaperRefreshTimerTick;
_systemWallpaperRefreshTimer = null;
}
}
private void OnSystemWallpaperRefreshTimerTick(object? sender, EventArgs e)
{
if (!string.Equals(_wallpaperType, "SystemWallpaper", StringComparison.OrdinalIgnoreCase))
{
StopSystemWallpaperTimer();
return;
}
LoadSystemWallpaper();
ApplyWallpaperBrush();
}
private void ApplyWallpaperBrush() private void ApplyWallpaperBrush()
{ {
DesktopWallpaperImageLayer.Background = null; DesktopWallpaperImageLayer.Background = null;
@@ -480,7 +555,8 @@ public partial class MainWindow
snapshot.WallpaperPath, snapshot.WallpaperPath,
snapshot.WallpaperType, snapshot.WallpaperType,
snapshot.WallpaperColor, snapshot.WallpaperColor,
snapshot.WallpaperPlacement); snapshot.WallpaperPlacement,
snapshot.SystemWallpaperRefreshIntervalSeconds);
if (!snapshot.IsNightMode.HasValue) if (!snapshot.IsNightMode.HasValue)
{ {
_isNightMode = CalculateCurrentBackgroundLuminance() < LightBackgroundLuminanceThreshold; _isNightMode = CalculateCurrentBackgroundLuminance() < LightBackgroundLuminanceThreshold;
@@ -523,6 +599,7 @@ public partial class MainWindow
? latestWallpaperState.Color ? latestWallpaperState.Color
: null, : null,
WallpaperPlacement = latestWallpaperState.Placement, WallpaperPlacement = latestWallpaperState.Placement,
SystemWallpaperRefreshIntervalSeconds = latestWallpaperState.SystemWallpaperRefreshIntervalSeconds,
LanguageCode = _languageCode, LanguageCode = _languageCode,
TimeZoneId = _timeZoneService.CurrentTimeZone.Id, TimeZoneId = _timeZoneService.CurrentTimeZone.Id,
WeatherLocationMode = latestWeatherState.LocationMode, WeatherLocationMode = latestWeatherState.LocationMode,

View File

@@ -122,6 +122,9 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
private Color? _wallpaperSolidColor; private Color? _wallpaperSolidColor;
private string? _wallpaperPath; private string? _wallpaperPath;
private string _wallpaperStatus = "Current background uses solid color."; private string _wallpaperStatus = "Current background uses solid color.";
private int _systemWallpaperRefreshIntervalSeconds = 300;
private DispatcherTimer? _systemWallpaperRefreshTimer;
private readonly ISystemWallpaperProvider _systemWallpaperProvider = HostSystemWallpaperProvider.GetOrCreate();
private IReadOnlyList<Color> _recommendedColors = Array.Empty<Color>(); private IReadOnlyList<Color> _recommendedColors = Array.Empty<Color>();
private IReadOnlyList<Color> _monetColors = Array.Empty<Color>(); private IReadOnlyList<Color> _monetColors = Array.Empty<Color>();
private Color _selectedThemeColor = Color.Parse("#FF3B82F6"); private Color _selectedThemeColor = Color.Parse("#FF3B82F6");

View File

@@ -77,10 +77,10 @@
<Setter Property="Margin" Value="2" /> <Setter Property="Margin" Value="2" />
</Style> </Style>
</WrapPanel.Styles> </WrapPanel.Styles>
<HyperlinkButton NavigateUri="https://github.com/Lincube/LanMountainDesktop"> <HyperlinkButton NavigateUri="https://github.com/wwiinnddyy/LanMountainDesktop">
<TextBlock Text="GitHub 仓库" /> <TextBlock Text="GitHub 仓库" />
</HyperlinkButton> </HyperlinkButton>
<HyperlinkButton NavigateUri="https://github.com/Lincube/LanMountainDesktop/issues"> <HyperlinkButton NavigateUri="https://github.com/wwiinnddyy/LanMountainDesktop/issues">
<TextBlock Text="问题反馈" /> <TextBlock Text="问题反馈" />
</HyperlinkButton> </HyperlinkButton>
</WrapPanel> </WrapPanel>

View File

@@ -29,6 +29,11 @@
<!-- 纯色预览 --> <!-- 纯色预览 -->
<Border Background="{Binding SelectedColor}" <Border Background="{Binding SelectedColor}"
IsVisible="{Binding IsSolidColor}" /> IsVisible="{Binding IsSolidColor}" />
<!-- 系统壁纸预览 -->
<Border Background="#FFF6F7F9"
IsVisible="{Binding IsSystemWallpaper}">
<Border Background="{Binding PreviewBrush}" />
</Border>
</Panel> </Panel>
</Border> </Border>
</Viewbox> </Viewbox>
@@ -135,6 +140,19 @@
</Button> </Button>
</UniformGrid> </UniformGrid>
</StackPanel> </StackPanel>
<!-- 右侧:系统壁纸状态 -->
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="12" IsVisible="{Binding IsSystemWallpaper}">
<TextBlock Text="{Binding SystemWallpaperLabel}"
FontSize="14"
FontWeight="SemiBold"
Opacity="0.8" />
<TextBlock Text="{Binding SystemWallpaperStatus}"
FontSize="12"
Opacity="0.7"
TextWrapping="Wrap"
MaxWidth="280" />
</StackPanel>
</Grid> </Grid>
<Separator Classes="settings-separator" Margin="0,0,0,24" /> <Separator Classes="settings-separator" Margin="0,0,0,24" />
@@ -183,10 +201,60 @@
</ui:SettingsExpander.Footer> </ui:SettingsExpander.Footer>
</ui:SettingsExpander> </ui:SettingsExpander>
<!-- 填充方式 --> <!-- 系统壁纸刷新设置 -->
<ui:SettingsExpander Header="{Binding RefreshIntervalLabel}"
IsVisible="{Binding IsSystemWallpaper}"
Margin="0,4,0,0">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Clock" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<StackPanel Orientation="Horizontal" Spacing="8">
<ComboBox Width="140"
ItemsSource="{Binding RefreshIntervals}"
SelectedItem="{Binding SelectedRefreshInterval}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:SelectionOption">
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Classes="settings-accent-button"
Command="{Binding RefreshSystemWallpaperCommand}"
ToolTip.Tip="{Binding RefreshButtonTooltip}"
VerticalAlignment="Center"
Padding="12,8">
<fi:SymbolIcon Symbol="ArrowSync" IconVariant="Regular" />
</Button>
</StackPanel>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- 填充方式(图片和系统壁纸都显示) -->
<ui:SettingsExpander Header="{Binding WallpaperPlacementLabel}" <ui:SettingsExpander Header="{Binding WallpaperPlacementLabel}"
Description="{Binding WallpaperPlacementDescription}" Description="{Binding WallpaperPlacementDescription}"
IsVisible="{Binding IsImage}" IsVisible="{Binding IsImageOrVideo}"
Margin="0,4,0,0">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Maximize" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ComboBox Width="200"
ItemsSource="{Binding WallpaperPlacements}"
SelectedItem="{Binding SelectedWallpaperPlacement}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:SelectionOption">
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<!-- 系统壁纸填充方式 -->
<ui:SettingsExpander Header="{Binding WallpaperPlacementLabel}"
Description="{Binding WallpaperPlacementDescription}"
IsVisible="{Binding IsSystemWallpaper}"
Margin="0,4,0,0"> Margin="0,4,0,0">
<ui:SettingsExpander.IconSource> <ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Maximize" /> <fi:SymbolIconSource Symbol="Maximize" />