mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-21 16:14:28 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3db5af923 | ||
|
|
1a7dde34d0 | ||
|
|
73cdefe296 |
@@ -101,6 +101,11 @@ public partial class App : Application
|
|||||||
|
|
||||||
public App()
|
public App()
|
||||||
{
|
{
|
||||||
|
if (Design.IsDesignMode)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_settingsFacade.Settings.Changed += OnSettingsChanged;
|
_settingsFacade.Settings.Changed += OnSettingsChanged;
|
||||||
_appearanceThemeService.Changed += OnAppearanceThemeChanged;
|
_appearanceThemeService.Changed += OnAppearanceThemeChanged;
|
||||||
}
|
}
|
||||||
@@ -108,9 +113,16 @@ public partial class App : Application
|
|||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
AppLogger.Info("App", "Initializing application resources.");
|
AppLogger.Info("App", "Initializing application resources.");
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
|
||||||
|
if (Design.IsDesignMode)
|
||||||
|
{
|
||||||
|
ApplyDesignTimeTheme();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ConfigureWebViewUserDataFolder();
|
ConfigureWebViewUserDataFolder();
|
||||||
AvaloniaWebViewBuilder.Initialize(default);
|
AvaloniaWebViewBuilder.Initialize(default);
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
ApplyThemeFromSettings();
|
ApplyThemeFromSettings();
|
||||||
ApplyCurrentCultureFromSettings();
|
ApplyCurrentCultureFromSettings();
|
||||||
EnsureSettingsWindowService();
|
EnsureSettingsWindowService();
|
||||||
@@ -119,6 +131,12 @@ public partial class App : Application
|
|||||||
|
|
||||||
public override void OnFrameworkInitializationCompleted()
|
public override void OnFrameworkInitializationCompleted()
|
||||||
{
|
{
|
||||||
|
if (Design.IsDesignMode)
|
||||||
|
{
|
||||||
|
base.OnFrameworkInitializationCompleted();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
AppLogger.Info("App", "Framework initialization completed.");
|
AppLogger.Info("App", "Framework initialization completed.");
|
||||||
RegisterUiUnhandledExceptionGuard();
|
RegisterUiUnhandledExceptionGuard();
|
||||||
LinuxDesktopEntryInstaller.EnsureInstalled();
|
LinuxDesktopEntryInstaller.EnsureInstalled();
|
||||||
@@ -127,6 +145,20 @@ public partial class App : Application
|
|||||||
base.OnFrameworkInitializationCompleted();
|
base.OnFrameworkInitializationCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ApplyDesignTimeTheme()
|
||||||
|
{
|
||||||
|
RequestedThemeVariant = ThemeVariant.Light;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ApplyAdaptiveThemeResources();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppLogger.Warn("Previewer", "Failed to apply adaptive theme resources in design mode.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void InitializeDesktopShell()
|
private void InitializeDesktopShell()
|
||||||
{
|
{
|
||||||
_desktopShellHost ??= new DesktopShellHost(
|
_desktopShellHost ??= new DesktopShellHost(
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ using LanMountainDesktop.Services.Settings;
|
|||||||
|
|
||||||
namespace LanMountainDesktop;
|
namespace LanMountainDesktop;
|
||||||
|
|
||||||
sealed class Program
|
public sealed class Program
|
||||||
{
|
{
|
||||||
internal static string StartupRenderMode { get; private set; } = AppRenderingModeHelper.Default;
|
internal static string StartupRenderMode { get; private set; } = AppRenderingModeHelper.Default;
|
||||||
|
|
||||||
@@ -67,7 +67,12 @@ sealed class Program
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AppBuilder BuildAvaloniaApp(string renderMode = AppRenderingModeHelper.Default)
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
|
{
|
||||||
|
return BuildAvaloniaApp(AppRenderingModeHelper.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AppBuilder BuildAvaloniaApp(string renderMode)
|
||||||
{
|
{
|
||||||
var builder = AppBuilder.Configure<App>()
|
var builder = AppBuilder.Configure<App>()
|
||||||
.UsePlatformDetect()
|
.UsePlatformDetect()
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -337,6 +337,14 @@ public sealed class PostHogUsageTelemetryService : IDisposable
|
|||||||
["timestamp"] = timestamp.ToString("o"),
|
["timestamp"] = timestamp.ToString("o"),
|
||||||
["properties"] = new Dictionary<string, object?>
|
["properties"] = new Dictionary<string, object?>
|
||||||
{
|
{
|
||||||
|
["install_id"] = installId,
|
||||||
|
["app_version"] = TelemetryEnvironmentInfo.GetAppVersion(),
|
||||||
|
["os_name"] = TelemetryEnvironmentInfo.GetOsName(),
|
||||||
|
["os_version"] = TelemetryEnvironmentInfo.GetOsVersion(),
|
||||||
|
["device_model"] = TelemetryEnvironmentInfo.GetDeviceModel(),
|
||||||
|
["device_arch"] = TelemetryEnvironmentInfo.GetDeviceArchitecture(),
|
||||||
|
["runtime_version"] = TelemetryEnvironmentInfo.GetRuntimeVersion(),
|
||||||
|
["language"] = TelemetryEnvironmentInfo.GetSystemLanguage(),
|
||||||
["launch_time_utc"] = timestamp.ToString("o")
|
["launch_time_utc"] = timestamp.ToString("o")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
65
LanMountainDesktop/Services/SystemWallpaperProvider.cs
Normal file
65
LanMountainDesktop/Services/SystemWallpaperProvider.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -78,25 +78,6 @@ public sealed class TelemetryIdentityService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string RefreshTelemetryId()
|
|
||||||
{
|
|
||||||
lock (_syncRoot)
|
|
||||||
{
|
|
||||||
EnsureInitialized();
|
|
||||||
|
|
||||||
var snapshot = _settingsFacade.Settings.LoadSnapshot<AppSettingsSnapshot>(SettingsScope.App);
|
|
||||||
snapshot.TelemetryId = GenerateId();
|
|
||||||
_settingsFacade.Settings.SaveSnapshot(
|
|
||||||
SettingsScope.App,
|
|
||||||
snapshot,
|
|
||||||
changedKeys: [nameof(AppSettingsSnapshot.TelemetryId)]);
|
|
||||||
|
|
||||||
_telemetryId = snapshot.TelemetryId ?? GenerateId();
|
|
||||||
AppLogger.Info("TelemetryIdentity", $"Telemetry id refreshed. TelemetryId={_telemetryId}");
|
|
||||||
return _telemetryId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool MarkBaselineReported()
|
public bool MarkBaselineReported()
|
||||||
{
|
{
|
||||||
lock (_syncRoot)
|
lock (_syncRoot)
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -58,9 +58,6 @@ public sealed partial class PrivacySettingsPageViewModel : ViewModelBase
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string _telemetryIdDescription = string.Empty;
|
private string _telemetryIdDescription = string.Empty;
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private string _refreshTelemetryIdText = string.Empty;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string _viewPrivacyPolicyText = string.Empty;
|
private string _viewPrivacyPolicyText = string.Empty;
|
||||||
|
|
||||||
@@ -75,27 +72,6 @@ public sealed partial class PrivacySettingsPageViewModel : ViewModelBase
|
|||||||
TelemetryId = TelemetryServices.Identity?.TelemetryId ?? string.Empty;
|
TelemetryId = TelemetryServices.Identity?.TelemetryId ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private void RefreshTelemetryId()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var identity = TelemetryServices.Identity;
|
|
||||||
if (identity is null)
|
|
||||||
{
|
|
||||||
AppLogger.Warn("PrivacySettings", "Telemetry identity service is unavailable.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TelemetryId = identity.RefreshTelemetryId();
|
|
||||||
AppLogger.Info("PrivacySettings", $"Telemetry ID refreshed: {TelemetryId}");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
AppLogger.Warn("PrivacySettings", "Failed to refresh telemetry ID.", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void OnUploadAnonymousCrashDataChanged(bool value)
|
partial void OnUploadAnonymousCrashDataChanged(bool value)
|
||||||
{
|
{
|
||||||
if (_isInitializing)
|
if (_isInitializing)
|
||||||
@@ -137,8 +113,7 @@ public sealed partial class PrivacySettingsPageViewModel : ViewModelBase
|
|||||||
TelemetryIdHeader = L("settings.privacy.telemetry_id_title", "Telemetry ID");
|
TelemetryIdHeader = L("settings.privacy.telemetry_id_title", "Telemetry ID");
|
||||||
TelemetryIdDescription = L(
|
TelemetryIdDescription = L(
|
||||||
"settings.privacy.telemetry_id_description",
|
"settings.privacy.telemetry_id_description",
|
||||||
"A refreshable anonymous identifier used for detailed telemetry sessions.");
|
"An anonymous identifier used for detailed telemetry sessions.");
|
||||||
RefreshTelemetryIdText = L("settings.privacy.refresh_telemetry_id", "Refresh");
|
|
||||||
PrivacyPolicyHintPrefix = L("settings.privacy.policy_hint_prefix", "For more details, please ");
|
PrivacyPolicyHintPrefix = L("settings.privacy.policy_hint_prefix", "For more details, please ");
|
||||||
ViewPrivacyPolicyText = L("settings.privacy.view_policy", "view our privacy policy");
|
ViewPrivacyPolicyText = L("settings.privacy.view_policy", "view our privacy policy");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ public sealed partial class WeatherSettingsPageViewModel : ViewModelBase
|
|||||||
ISettingsFacadeService settingsFacade,
|
ISettingsFacadeService settingsFacade,
|
||||||
LocalizationService localizationService,
|
LocalizationService localizationService,
|
||||||
ILocationService locationService,
|
ILocationService locationService,
|
||||||
WeatherLocationRefreshService weatherLocationRefreshService)
|
WeatherLocationRefreshService weatherLocationRefreshService,
|
||||||
|
bool enableStartupPreviewRefresh = true)
|
||||||
{
|
{
|
||||||
_settingsFacade = settingsFacade ?? throw new ArgumentNullException(nameof(settingsFacade));
|
_settingsFacade = settingsFacade ?? throw new ArgumentNullException(nameof(settingsFacade));
|
||||||
_localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService));
|
_localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService));
|
||||||
@@ -52,7 +53,10 @@ public sealed partial class WeatherSettingsPageViewModel : ViewModelBase
|
|||||||
? LocationReadyText
|
? LocationReadyText
|
||||||
: LocationUnsupportedText;
|
: LocationUnsupportedText;
|
||||||
|
|
||||||
_ = RefreshPreviewAsync();
|
if (enableStartupPreviewRefresh)
|
||||||
|
{
|
||||||
|
_ = RefreshPreviewAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<SelectionOption> LocationModes { get; }
|
public IReadOnlyList<SelectionOption> LocationModes { get; }
|
||||||
@@ -476,6 +480,65 @@ public sealed partial class WeatherSettingsPageViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void ApplyDesignTimePreview()
|
||||||
|
{
|
||||||
|
_isInitializing = true;
|
||||||
|
|
||||||
|
var previewLocation = new WeatherLocation(
|
||||||
|
"Shenzhen Nanshan",
|
||||||
|
"101280601",
|
||||||
|
22.5431,
|
||||||
|
114.0579,
|
||||||
|
"Guangdong, China");
|
||||||
|
var alternateLocation = new WeatherLocation(
|
||||||
|
"Shanghai Pudong",
|
||||||
|
"101020600",
|
||||||
|
31.2304,
|
||||||
|
121.4737,
|
||||||
|
"Shanghai, China");
|
||||||
|
|
||||||
|
SelectedLocationMode = LocationModes.FirstOrDefault(option =>
|
||||||
|
string.Equals(option.Value, "CitySearch", StringComparison.OrdinalIgnoreCase))
|
||||||
|
?? LocationModes[0];
|
||||||
|
SearchKeyword = "shenzhen";
|
||||||
|
SelectedSearchResult = previewLocation;
|
||||||
|
|
||||||
|
SearchResults.Clear();
|
||||||
|
SearchResults.Add(previewLocation);
|
||||||
|
SearchResults.Add(alternateLocation);
|
||||||
|
|
||||||
|
Latitude = previewLocation.Latitude;
|
||||||
|
Longitude = previewLocation.Longitude;
|
||||||
|
LocationKey = previewLocation.LocationKey;
|
||||||
|
LocationName = previewLocation.Name;
|
||||||
|
AutoRefreshLocation = true;
|
||||||
|
ExcludedAlerts = "Heat\nThunderstorm";
|
||||||
|
NoTlsRequests = false;
|
||||||
|
IsLocationSupported = true;
|
||||||
|
IsRefreshingLocation = false;
|
||||||
|
IsRefreshingPreview = false;
|
||||||
|
|
||||||
|
_isInitializing = false;
|
||||||
|
|
||||||
|
UpdateModeVisibility();
|
||||||
|
UpdateCurrentLocationSummary();
|
||||||
|
|
||||||
|
var preview = XiaomiWeatherVisualResolver.Resolve(
|
||||||
|
"Partly cloudy",
|
||||||
|
4,
|
||||||
|
isNight: false,
|
||||||
|
_languageCode);
|
||||||
|
|
||||||
|
SearchStatus = "2 sample locations are shown for design preview.";
|
||||||
|
LocationActionStatus = "Using mocked Windows location support in design mode.";
|
||||||
|
PreviewIcon = HyperOS3WeatherAssetLoader.LoadImage(preview.PrimaryIconAsset);
|
||||||
|
PreviewLocation = previewLocation.Name;
|
||||||
|
PreviewTemperature = "24 deg C";
|
||||||
|
PreviewCondition = preview.DisplayText;
|
||||||
|
PreviewUpdated = "Updated 09:42";
|
||||||
|
PreviewStatus = "Preview data is mocked for Avalonia design mode.";
|
||||||
|
}
|
||||||
|
|
||||||
private void RefreshLocalizedText()
|
private void RefreshLocalizedText()
|
||||||
{
|
{
|
||||||
PageTitle = L("settings.weather.title", "Weather");
|
PageTitle = L("settings.weather.title", "Weather");
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
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:fi="using:FluentIcons.Avalonia"
|
xmlns:fi="using:FluentIcons.Avalonia"
|
||||||
xmlns:webview="clr-namespace:AvaloniaWebView;assembly=Avalonia.WebView"
|
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignWidth="480"
|
d:DesignWidth="480"
|
||||||
d:DesignHeight="480"
|
d:DesignHeight="480"
|
||||||
@@ -24,20 +23,27 @@
|
|||||||
BorderBrush="#22000000"
|
BorderBrush="#22000000"
|
||||||
BorderThickness="1">
|
BorderThickness="1">
|
||||||
<Grid>
|
<Grid>
|
||||||
<webview:WebView x:Name="BrowserWebView" />
|
<Grid x:Name="WebViewPresenter" />
|
||||||
<Border x:Name="UnavailableOverlay"
|
<Border x:Name="UnavailableOverlay"
|
||||||
IsVisible="False"
|
IsVisible="False"
|
||||||
Background="#CC0F172A"
|
Background="#CC0F172A"
|
||||||
Padding="16">
|
Padding="16">
|
||||||
<TextBlock x:Name="UnavailableMessageTextBlock"
|
<StackPanel HorizontalAlignment="Center"
|
||||||
Foreground="#F8FAFC"
|
VerticalAlignment="Center"
|
||||||
TextWrapping="Wrap"
|
Spacing="10"
|
||||||
TextAlignment="Center"
|
MaxWidth="360">
|
||||||
HorizontalAlignment="Center"
|
<fi:SymbolIcon Symbol="Desktop"
|
||||||
VerticalAlignment="Center"
|
FontSize="28"
|
||||||
MaxWidth="360"
|
HorizontalAlignment="Center"
|
||||||
FontSize="13"
|
Foreground="#F8FAFC" />
|
||||||
Text="Browser runtime unavailable." />
|
<TextBlock x:Name="UnavailableMessageTextBlock"
|
||||||
|
Foreground="#F8FAFC"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
TextAlignment="Center"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
FontSize="13"
|
||||||
|
Text="Browser runtime unavailable." />
|
||||||
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
|||||||
{
|
{
|
||||||
private static readonly Uri DefaultHomeUri = new("https://www.bing.com");
|
private static readonly Uri DefaultHomeUri = new("https://www.bing.com");
|
||||||
|
|
||||||
|
private readonly bool _isDesignModePreview = Design.IsDesignMode;
|
||||||
private double _currentCellSize = 48;
|
private double _currentCellSize = 48;
|
||||||
private string _componentId = BuiltInComponentIds.DesktopBrowser;
|
private string _componentId = BuiltInComponentIds.DesktopBrowser;
|
||||||
private string _placementId = string.Empty;
|
private string _placementId = string.Empty;
|
||||||
@@ -27,6 +28,7 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
|||||||
private bool _isEditMode;
|
private bool _isEditMode;
|
||||||
private bool _isWebViewActive = true;
|
private bool _isWebViewActive = true;
|
||||||
private bool _isWebViewFaulted;
|
private bool _isWebViewFaulted;
|
||||||
|
private WebView? _browserWebView;
|
||||||
private readonly WebView2RuntimeAvailability _runtimeAvailability;
|
private readonly WebView2RuntimeAvailability _runtimeAvailability;
|
||||||
private bool _isDisposed;
|
private bool _isDisposed;
|
||||||
|
|
||||||
@@ -41,10 +43,15 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
|||||||
ApplyCellSize(_currentCellSize);
|
ApplyCellSize(_currentCellSize);
|
||||||
ApplyTheme(force: true);
|
ApplyTheme(force: true);
|
||||||
|
|
||||||
_runtimeAvailability = WebView2RuntimeProbe.GetAvailability();
|
_runtimeAvailability = _isDesignModePreview
|
||||||
|
? new WebView2RuntimeAvailability(
|
||||||
|
IsAvailable: false,
|
||||||
|
Version: null,
|
||||||
|
Message: "WebView preview is disabled in Avalonia design mode.")
|
||||||
|
: WebView2RuntimeProbe.GetAvailability();
|
||||||
if (_runtimeAvailability.IsAvailable)
|
if (_runtimeAvailability.IsAvailable)
|
||||||
{
|
{
|
||||||
BrowserWebView.NavigationStarting += OnBrowserWebViewNavigationStarting;
|
EnsureWebViewCreated();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -69,9 +76,9 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
|||||||
AttachedToVisualTree -= OnAttachedToVisualTree;
|
AttachedToVisualTree -= OnAttachedToVisualTree;
|
||||||
DetachedFromVisualTree -= OnDetachedFromVisualTree;
|
DetachedFromVisualTree -= OnDetachedFromVisualTree;
|
||||||
|
|
||||||
if (_runtimeAvailability.IsAvailable)
|
if (_browserWebView is not null)
|
||||||
{
|
{
|
||||||
BrowserWebView.NavigationStarting -= OnBrowserWebViewNavigationStarting;
|
_browserWebView.NavigationStarting -= OnBrowserWebViewNavigationStarting;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,6 +307,13 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
|||||||
|
|
||||||
private void UpdateWebViewActiveState()
|
private void UpdateWebViewActiveState()
|
||||||
{
|
{
|
||||||
|
if (_isDesignModePreview)
|
||||||
|
{
|
||||||
|
_isWebViewActive = false;
|
||||||
|
ApplyRuntimeUnavailableState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!_runtimeAvailability.IsAvailable || _isWebViewFaulted)
|
if (!_runtimeAvailability.IsAvailable || _isWebViewFaulted)
|
||||||
{
|
{
|
||||||
_isWebViewActive = false;
|
_isWebViewActive = false;
|
||||||
@@ -325,14 +339,21 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
|||||||
|
|
||||||
private void ActivateWebView()
|
private void ActivateWebView()
|
||||||
{
|
{
|
||||||
|
EnsureWebViewCreated();
|
||||||
if (_isWebViewFaulted || !_runtimeAvailability.IsAvailable)
|
if (_isWebViewFaulted || !_runtimeAvailability.IsAvailable)
|
||||||
{
|
{
|
||||||
ApplyRuntimeUnavailableState();
|
ApplyRuntimeUnavailableState();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BrowserWebView.IsVisible = true;
|
if (_browserWebView is null)
|
||||||
BrowserWebView.IsHitTestVisible = true;
|
{
|
||||||
|
ApplyRuntimeUnavailableState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_browserWebView.IsVisible = true;
|
||||||
|
_browserWebView.IsHitTestVisible = true;
|
||||||
RefreshButton.IsEnabled = true;
|
RefreshButton.IsEnabled = true;
|
||||||
GoButton.IsEnabled = true;
|
GoButton.IsEnabled = true;
|
||||||
AddressTextBox.IsEnabled = true;
|
AddressTextBox.IsEnabled = true;
|
||||||
@@ -341,8 +362,11 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
|||||||
|
|
||||||
private void DeactivateWebView(bool clearUrl)
|
private void DeactivateWebView(bool clearUrl)
|
||||||
{
|
{
|
||||||
BrowserWebView.IsHitTestVisible = false;
|
if (_browserWebView is not null)
|
||||||
BrowserWebView.IsVisible = false;
|
{
|
||||||
|
_browserWebView.IsHitTestVisible = false;
|
||||||
|
_browserWebView.IsVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (clearUrl)
|
if (clearUrl)
|
||||||
{
|
{
|
||||||
@@ -352,9 +376,14 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
|||||||
|
|
||||||
private bool TryReloadWebView(string action)
|
private bool TryReloadWebView(string action)
|
||||||
{
|
{
|
||||||
|
if (_browserWebView is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
BrowserWebView.Reload();
|
_browserWebView.Reload();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (!UiExceptionGuard.IsFatalException(ex))
|
catch (Exception ex) when (!UiExceptionGuard.IsFatalException(ex))
|
||||||
@@ -366,9 +395,14 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
|||||||
|
|
||||||
private bool TryNavigate(Uri uri, string action)
|
private bool TryNavigate(Uri uri, string action)
|
||||||
{
|
{
|
||||||
|
if (_browserWebView is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
BrowserWebView.Url = uri;
|
_browserWebView.Url = uri;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (!UiExceptionGuard.IsFatalException(ex))
|
catch (Exception ex) when (!UiExceptionGuard.IsFatalException(ex))
|
||||||
@@ -380,9 +414,14 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
|||||||
|
|
||||||
private void TryClearWebViewUrl()
|
private void TryClearWebViewUrl()
|
||||||
{
|
{
|
||||||
|
if (_browserWebView is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
BrowserWebView.Url = null;
|
_browserWebView.Url = null;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -392,14 +431,20 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
|||||||
|
|
||||||
private bool CanUseWebView()
|
private bool CanUseWebView()
|
||||||
{
|
{
|
||||||
return _runtimeAvailability.IsAvailable && !_isWebViewFaulted && _isWebViewActive;
|
return _runtimeAvailability.IsAvailable &&
|
||||||
|
!_isWebViewFaulted &&
|
||||||
|
_isWebViewActive &&
|
||||||
|
_browserWebView is not null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyRuntimeUnavailableState()
|
private void ApplyRuntimeUnavailableState()
|
||||||
{
|
{
|
||||||
_isWebViewActive = false;
|
_isWebViewActive = false;
|
||||||
BrowserWebView.IsVisible = false;
|
if (_browserWebView is not null)
|
||||||
BrowserWebView.IsHitTestVisible = false;
|
{
|
||||||
|
_browserWebView.IsVisible = false;
|
||||||
|
_browserWebView.IsHitTestVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
RefreshButton.IsEnabled = false;
|
RefreshButton.IsEnabled = false;
|
||||||
GoButton.IsEnabled = false;
|
GoButton.IsEnabled = false;
|
||||||
@@ -414,6 +459,22 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
|||||||
UnavailableOverlay.IsVisible = true;
|
UnavailableOverlay.IsVisible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void EnsureWebViewCreated()
|
||||||
|
{
|
||||||
|
if (_browserWebView is not null || _isDesignModePreview || !_runtimeAvailability.IsAvailable)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_browserWebView = new WebView
|
||||||
|
{
|
||||||
|
IsVisible = false,
|
||||||
|
IsHitTestVisible = false
|
||||||
|
};
|
||||||
|
_browserWebView.NavigationStarting += OnBrowserWebViewNavigationStarting;
|
||||||
|
WebViewPresenter.Children.Insert(0, _browserWebView);
|
||||||
|
}
|
||||||
|
|
||||||
private void EnterFaultedState(string action, Exception ex)
|
private void EnterFaultedState(string action, Exception ex)
|
||||||
{
|
{
|
||||||
_isWebViewFaulted = true;
|
_isWebViewFaulted = true;
|
||||||
|
|||||||
@@ -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>();
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
|
|||||||
Interval = TimeSpan.FromMinutes(30)
|
Interval = TimeSpan.FromMinutes(30)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private readonly bool _isDesignModePreview = Design.IsDesignMode;
|
||||||
private LanMountainDesktop.PluginSdk.ISettingsService _appSettingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
|
private LanMountainDesktop.PluginSdk.ISettingsService _appSettingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
|
||||||
private IComponentInstanceSettingsStore _componentSettingsService = HostComponentSettingsStoreProvider.GetOrCreate();
|
private IComponentInstanceSettingsStore _componentSettingsService = HostComponentSettingsStoreProvider.GetOrCreate();
|
||||||
private readonly LocalizationService _localizationService = new();
|
private readonly LocalizationService _localizationService = new();
|
||||||
@@ -102,12 +103,19 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
|
|||||||
News2TitleTextBlock.FontFamily = MiSansFontFamily;
|
News2TitleTextBlock.FontFamily = MiSansFontFamily;
|
||||||
StatusTextBlock.FontFamily = MiSansFontFamily;
|
StatusTextBlock.FontFamily = MiSansFontFamily;
|
||||||
|
|
||||||
|
SizeChanged += OnSizeChanged;
|
||||||
|
ActualThemeVariantChanged += OnActualThemeVariantChanged;
|
||||||
|
if (_isDesignModePreview)
|
||||||
|
{
|
||||||
|
ApplyCellSize(_currentCellSize);
|
||||||
|
ApplyDesignTimePreview();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_refreshTimer.Tick += OnRefreshTimerTick;
|
_refreshTimer.Tick += OnRefreshTimerTick;
|
||||||
RefreshButton.Click += OnRefreshButtonClick;
|
RefreshButton.Click += OnRefreshButtonClick;
|
||||||
AttachedToVisualTree += OnAttachedToVisualTree;
|
AttachedToVisualTree += OnAttachedToVisualTree;
|
||||||
DetachedFromVisualTree += OnDetachedFromVisualTree;
|
DetachedFromVisualTree += OnDetachedFromVisualTree;
|
||||||
SizeChanged += OnSizeChanged;
|
|
||||||
ActualThemeVariantChanged += OnActualThemeVariantChanged;
|
|
||||||
|
|
||||||
ApplyCellSize(_currentCellSize);
|
ApplyCellSize(_currentCellSize);
|
||||||
UpdateLanguageCode();
|
UpdateLanguageCode();
|
||||||
@@ -226,6 +234,12 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
|
|||||||
|
|
||||||
private async void OnRefreshButtonClick(object? sender, RoutedEventArgs e)
|
private async void OnRefreshButtonClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (_isDesignModePreview)
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (_isRefreshing)
|
if (_isRefreshing)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -242,6 +256,12 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
|
|||||||
|
|
||||||
private void OnNewsItem1PointerPressed(object? sender, PointerPressedEventArgs e)
|
private void OnNewsItem1PointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (_isDesignModePreview)
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -253,6 +273,12 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
|
|||||||
|
|
||||||
private void OnNewsItem2PointerPressed(object? sender, PointerPressedEventArgs e)
|
private void OnNewsItem2PointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (_isDesignModePreview)
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -264,6 +290,12 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
|
|||||||
|
|
||||||
private void OnExtraNewsItemPointerPressed(object? sender, PointerPressedEventArgs e)
|
private void OnExtraNewsItemPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (_isDesignModePreview)
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed ||
|
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed ||
|
||||||
sender is not Control control ||
|
sender is not Control control ||
|
||||||
control.Tag is not int index)
|
control.Tag is not int index)
|
||||||
@@ -408,6 +440,55 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
|
|||||||
UpdateAdaptiveLayout();
|
UpdateAdaptiveLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ApplyDesignTimePreview()
|
||||||
|
{
|
||||||
|
_isNightVisual = ResolveNightMode();
|
||||||
|
_activeNewsItems =
|
||||||
|
[
|
||||||
|
new DailyNewsItemSnapshot(
|
||||||
|
"LanMountain preview mode now shows mocked widget content in Rider.",
|
||||||
|
null,
|
||||||
|
"https://example.com/news/preview-1",
|
||||||
|
null,
|
||||||
|
"09:30"),
|
||||||
|
new DailyNewsItemSnapshot(
|
||||||
|
"Weather, artwork, and plugin market cards render without live network calls.",
|
||||||
|
null,
|
||||||
|
"https://example.com/news/preview-2",
|
||||||
|
null,
|
||||||
|
"09:10"),
|
||||||
|
new DailyNewsItemSnapshot(
|
||||||
|
"Design-time mocks make isolated widget layout tuning much faster.",
|
||||||
|
null,
|
||||||
|
"https://example.com/news/preview-3",
|
||||||
|
null,
|
||||||
|
"08:55")
|
||||||
|
];
|
||||||
|
|
||||||
|
_newsUrls.Clear();
|
||||||
|
foreach (var item in _activeNewsItems)
|
||||||
|
{
|
||||||
|
_newsUrls.Add(item.Url);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateHotHeadlineText(_activeNewsItems[0].Title);
|
||||||
|
News2TitleTextBlock.Text = NormalizeCompactText(_activeNewsItems[1].Title);
|
||||||
|
StatusTextBlock.Text = string.Empty;
|
||||||
|
StatusTextBlock.IsVisible = false;
|
||||||
|
|
||||||
|
SetNewsBitmap(0, null);
|
||||||
|
SetNewsBitmap(1, null);
|
||||||
|
RenderExtraNewsRows(_activeNewsItems.Skip(2).ToArray());
|
||||||
|
UpdateNewsInteractionState();
|
||||||
|
|
||||||
|
RefreshButton.IsEnabled = false;
|
||||||
|
RefreshButton.Opacity = 1.0;
|
||||||
|
RefreshGlyphIcon.Opacity = 0.82;
|
||||||
|
RefreshLabelTextBlock.Opacity = 0.82;
|
||||||
|
|
||||||
|
UpdateAdaptiveLayout();
|
||||||
|
}
|
||||||
|
|
||||||
private int ResolveDesiredNewsItemCount()
|
private int ResolveDesiredNewsItemCount()
|
||||||
{
|
{
|
||||||
return 2;
|
return 2;
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
|
|||||||
Interval = TimeSpan.FromHours(6)
|
Interval = TimeSpan.FromHours(6)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private readonly bool _isDesignModePreview = Design.IsDesignMode;
|
||||||
private ISettingsService _settingsService = HostSettingsFacadeProvider.GetOrCreate().Settings;
|
private ISettingsService _settingsService = HostSettingsFacadeProvider.GetOrCreate().Settings;
|
||||||
private readonly LocalizationService _localizationService = new();
|
private readonly LocalizationService _localizationService = new();
|
||||||
|
|
||||||
@@ -85,10 +86,17 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
|
|||||||
ArtistTextBlock.FontFamily = MiSansFontFamily;
|
ArtistTextBlock.FontFamily = MiSansFontFamily;
|
||||||
YearTextBlock.FontFamily = MiSansFontFamily;
|
YearTextBlock.FontFamily = MiSansFontFamily;
|
||||||
|
|
||||||
|
SizeChanged += OnSizeChanged;
|
||||||
|
if (_isDesignModePreview)
|
||||||
|
{
|
||||||
|
ApplyCellSize(_currentCellSize);
|
||||||
|
ApplyDesignTimePreview();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_refreshTimer.Tick += OnRefreshTimerTick;
|
_refreshTimer.Tick += OnRefreshTimerTick;
|
||||||
AttachedToVisualTree += OnAttachedToVisualTree;
|
AttachedToVisualTree += OnAttachedToVisualTree;
|
||||||
DetachedFromVisualTree += OnDetachedFromVisualTree;
|
DetachedFromVisualTree += OnDetachedFromVisualTree;
|
||||||
SizeChanged += OnSizeChanged;
|
|
||||||
|
|
||||||
ApplyCellSize(_currentCellSize);
|
ApplyCellSize(_currentCellSize);
|
||||||
UpdateLanguageCode();
|
UpdateLanguageCode();
|
||||||
@@ -177,6 +185,11 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
|
|||||||
|
|
||||||
private void OnArtworkPanelPointerPressed(object? sender, PointerPressedEventArgs e)
|
private void OnArtworkPanelPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (_isDesignModePreview)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -188,6 +201,11 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
|
|||||||
|
|
||||||
private void OnInfoPanelPointerPressed(object? sender, PointerPressedEventArgs e)
|
private void OnInfoPanelPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (_isDesignModePreview)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -420,6 +438,36 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
|
|||||||
UpdateAdaptiveLayout();
|
UpdateAdaptiveLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ApplyDesignTimePreview()
|
||||||
|
{
|
||||||
|
DisposeArtworkBitmap();
|
||||||
|
_currentArtworkSourceUrl = null;
|
||||||
|
_currentArtworkImageUrl = null;
|
||||||
|
|
||||||
|
RootBorder.Background = new SolidColorBrush(Color.Parse("#C6B08B"));
|
||||||
|
ArtworkPanel.Background = new LinearGradientBrush
|
||||||
|
{
|
||||||
|
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
|
||||||
|
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
|
||||||
|
GradientStops = new GradientStops
|
||||||
|
{
|
||||||
|
new GradientStop(Color.Parse("#AA8B69"), 0),
|
||||||
|
new GradientStop(Color.Parse("#5F4B3B"), 1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
InfoPanel.Background = new SolidColorBrush(Color.Parse("#15181D"));
|
||||||
|
|
||||||
|
DateTextBlock.Text = "03/22";
|
||||||
|
WeekdayTextBlock.Text = "Sunday";
|
||||||
|
PaintingTitleTextBlock.Text = BuildQuotedTitle("The Starry Night");
|
||||||
|
ArtistTextBlock.Text = NormalizeCompactText("Vincent van Gogh");
|
||||||
|
YearTextBlock.Text = "1889 | MoMA";
|
||||||
|
StatusTextBlock.IsVisible = false;
|
||||||
|
StatusTextBlock.Text = string.Empty;
|
||||||
|
|
||||||
|
UpdateAdaptiveLayout();
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateAdaptiveLayout()
|
private void UpdateAdaptiveLayout()
|
||||||
{
|
{
|
||||||
var scale = ResolveScale();
|
var scale = ResolveScale();
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public partial class DailyWordWidget : UserControl, IDesktopComponentWidget, IRe
|
|||||||
Interval = TimeSpan.FromHours(6)
|
Interval = TimeSpan.FromHours(6)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private readonly bool _isDesignModePreview = Design.IsDesignMode;
|
||||||
private LanMountainDesktop.PluginSdk.ISettingsService _appSettingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
|
private LanMountainDesktop.PluginSdk.ISettingsService _appSettingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
|
||||||
private IComponentInstanceSettingsStore _componentSettingsService = HostComponentSettingsStoreProvider.GetOrCreate();
|
private IComponentInstanceSettingsStore _componentSettingsService = HostComponentSettingsStoreProvider.GetOrCreate();
|
||||||
private readonly LocalizationService _localizationService = new();
|
private readonly LocalizationService _localizationService = new();
|
||||||
@@ -55,12 +56,19 @@ public partial class DailyWordWidget : UserControl, IDesktopComponentWidget, IRe
|
|||||||
ExampleTranslationTextBlock.FontFamily = MiSansFontFamily;
|
ExampleTranslationTextBlock.FontFamily = MiSansFontFamily;
|
||||||
StatusTextBlock.FontFamily = MiSansFontFamily;
|
StatusTextBlock.FontFamily = MiSansFontFamily;
|
||||||
|
|
||||||
|
SizeChanged += OnSizeChanged;
|
||||||
|
ActualThemeVariantChanged += OnActualThemeVariantChanged;
|
||||||
|
if (_isDesignModePreview)
|
||||||
|
{
|
||||||
|
ApplyCellSize(_currentCellSize);
|
||||||
|
ApplyDesignTimePreview();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_refreshTimer.Tick += OnRefreshTimerTick;
|
_refreshTimer.Tick += OnRefreshTimerTick;
|
||||||
RefreshButton.Click += OnRefreshButtonClick;
|
RefreshButton.Click += OnRefreshButtonClick;
|
||||||
AttachedToVisualTree += OnAttachedToVisualTree;
|
AttachedToVisualTree += OnAttachedToVisualTree;
|
||||||
DetachedFromVisualTree += OnDetachedFromVisualTree;
|
DetachedFromVisualTree += OnDetachedFromVisualTree;
|
||||||
SizeChanged += OnSizeChanged;
|
|
||||||
ActualThemeVariantChanged += OnActualThemeVariantChanged;
|
|
||||||
|
|
||||||
ApplyCellSize(_currentCellSize);
|
ApplyCellSize(_currentCellSize);
|
||||||
UpdateLanguageCode();
|
UpdateLanguageCode();
|
||||||
@@ -175,6 +183,12 @@ public partial class DailyWordWidget : UserControl, IDesktopComponentWidget, IRe
|
|||||||
|
|
||||||
private async void OnRefreshButtonClick(object? sender, RoutedEventArgs e)
|
private async void OnRefreshButtonClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (_isDesignModePreview)
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (_isRefreshing)
|
if (_isRefreshing)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -284,6 +298,26 @@ public partial class DailyWordWidget : UserControl, IDesktopComponentWidget, IRe
|
|||||||
UpdateAdaptiveLayout();
|
UpdateAdaptiveLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ApplyDesignTimePreview()
|
||||||
|
{
|
||||||
|
_isNightVisual = ResolveNightMode();
|
||||||
|
ApplyNightModeVisual();
|
||||||
|
|
||||||
|
WordTextBlock.Text = "serendipity";
|
||||||
|
PronunciationTextBlock.Text = "UK /,seren'dipiti/ | US /,seren'dipiti/";
|
||||||
|
MeaningTextBlock.Text = "n. finding something valuable by accident; a pleasant surprise.";
|
||||||
|
ExampleTextBlock.Text = "The widget preview became useful by pure serendipity.";
|
||||||
|
ExampleTranslationTextBlock.Text = "A mocked sample sentence shown only in design mode.";
|
||||||
|
StatusTextBlock.Text = string.Empty;
|
||||||
|
StatusTextBlock.IsVisible = false;
|
||||||
|
|
||||||
|
RefreshButton.IsEnabled = false;
|
||||||
|
RefreshButton.Opacity = 1.0;
|
||||||
|
RefreshIcon.Opacity = 0.82;
|
||||||
|
|
||||||
|
UpdateAdaptiveLayout();
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateAdaptiveLayout()
|
private void UpdateAdaptiveLayout()
|
||||||
{
|
{
|
||||||
var scale = ResolveScale();
|
var scale = ResolveScale();
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk
|
|||||||
Interval = FluttermotionToken.WeatherAnimationFrameInterval
|
Interval = FluttermotionToken.WeatherAnimationFrameInterval
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private readonly bool _isDesignModePreview = Design.IsDesignMode;
|
||||||
private LanMountainDesktop.PluginSdk.ISettingsService _settingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
|
private LanMountainDesktop.PluginSdk.ISettingsService _settingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
|
||||||
private IComponentInstanceSettingsStore _componentSettingsStore = HostComponentSettingsStoreProvider.GetOrCreate();
|
private IComponentInstanceSettingsStore _componentSettingsStore = HostComponentSettingsStoreProvider.GetOrCreate();
|
||||||
private readonly LocalizationService _localizationService = new();
|
private readonly LocalizationService _localizationService = new();
|
||||||
@@ -128,11 +129,19 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
InitializeMotionTransform();
|
InitializeMotionTransform();
|
||||||
|
|
||||||
|
SizeChanged += OnSizeChanged;
|
||||||
|
if (_isDesignModePreview)
|
||||||
|
{
|
||||||
|
InitializeParticleVisuals();
|
||||||
|
ApplyCellSize(_currentCellSize);
|
||||||
|
ApplyDesignTimePreview();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_refreshTimer.Tick += OnRefreshTimerTick;
|
_refreshTimer.Tick += OnRefreshTimerTick;
|
||||||
_backgroundAnimationTimer.Tick += OnBackgroundAnimationTick;
|
_backgroundAnimationTimer.Tick += OnBackgroundAnimationTick;
|
||||||
AttachedToVisualTree += OnAttachedToVisualTree;
|
AttachedToVisualTree += OnAttachedToVisualTree;
|
||||||
DetachedFromVisualTree += OnDetachedFromVisualTree;
|
DetachedFromVisualTree += OnDetachedFromVisualTree;
|
||||||
SizeChanged += OnSizeChanged;
|
|
||||||
|
|
||||||
InitializeParticleVisuals();
|
InitializeParticleVisuals();
|
||||||
ApplyVisualTheme(WeatherVisualKind.ClearDay);
|
ApplyVisualTheme(WeatherVisualKind.ClearDay);
|
||||||
@@ -512,6 +521,29 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk
|
|||||||
_latestSnapshot = null;
|
_latestSnapshot = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ApplyDesignTimePreview()
|
||||||
|
{
|
||||||
|
const WeatherVisualKind previewVisualKind = WeatherVisualKind.PartlyCloudyDay;
|
||||||
|
|
||||||
|
_languageCode = "en-US";
|
||||||
|
_latestSnapshot = null;
|
||||||
|
|
||||||
|
ApplyVisualTheme(previewVisualKind);
|
||||||
|
SetWeatherIcon(
|
||||||
|
HyperOS3WeatherTheme.ResolveHeroIconAsset(HyperOS3WeatherVisualKind.PartlyCloudyDay),
|
||||||
|
previewVisualKind);
|
||||||
|
SetLoadingSkeleton(false);
|
||||||
|
|
||||||
|
CityTextBlock.Text = "Shenzhen Bay";
|
||||||
|
ConditionTextBlock.Text = "Partly cloudy";
|
||||||
|
TemperatureTextBlock.Text = "24°";
|
||||||
|
RangeTextBlock.Text = "28°/20°";
|
||||||
|
|
||||||
|
ResetAnimationState();
|
||||||
|
ResetParticles();
|
||||||
|
ApplyAdaptiveTypography();
|
||||||
|
}
|
||||||
|
|
||||||
private void ApplyVisualTheme(WeatherVisualKind kind)
|
private void ApplyVisualTheme(WeatherVisualKind kind)
|
||||||
{
|
{
|
||||||
_activeVisualKind = kind;
|
_activeVisualKind = kind;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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");
|
||||||
@@ -160,7 +163,9 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
var pluginRuntimeService = (Application.Current as App)?.PluginRuntimeService;
|
var pluginRuntimeService = Design.IsDesignMode
|
||||||
|
? null
|
||||||
|
: (Application.Current as App)?.PluginRuntimeService;
|
||||||
_componentRegistry = DesktopComponentRegistryFactory.Create(pluginRuntimeService);
|
_componentRegistry = DesktopComponentRegistryFactory.Create(pluginRuntimeService);
|
||||||
_settingsService = _settingsFacade.Settings;
|
_settingsService = _settingsFacade.Settings;
|
||||||
_gridSettingsService = _settingsFacade.Grid;
|
_gridSettingsService = _settingsFacade.Grid;
|
||||||
@@ -173,7 +178,6 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
Icon = _appLogoService.CreateWindowIcon();
|
Icon = _appLogoService.CreateWindowIcon();
|
||||||
InitializeTaskbarProfileFlyout();
|
|
||||||
_componentRuntimeRegistry = DesktopComponentRegistryFactory.CreateRuntimeRegistry(
|
_componentRuntimeRegistry = DesktopComponentRegistryFactory.CreateRuntimeRegistry(
|
||||||
_componentRegistry,
|
_componentRegistry,
|
||||||
pluginRuntimeService,
|
pluginRuntimeService,
|
||||||
@@ -183,6 +187,14 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
pluginRuntimeService);
|
pluginRuntimeService);
|
||||||
_componentLibraryService = new ComponentLibraryService(_componentRegistry, _componentRuntimeRegistry);
|
_componentLibraryService = new ComponentLibraryService(_componentRegistry, _componentRuntimeRegistry);
|
||||||
_componentEditorWindowService = new ComponentEditorWindowService(_settingsFacade);
|
_componentEditorWindowService = new ComponentEditorWindowService(_settingsFacade);
|
||||||
|
|
||||||
|
if (Design.IsDesignMode)
|
||||||
|
{
|
||||||
|
ApplyDesignTimePreview();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
InitializeTaskbarProfileFlyout();
|
||||||
_fluentAvaloniaTheme = Application.Current?.Styles.OfType<FluentAvaloniaTheme>().FirstOrDefault();
|
_fluentAvaloniaTheme = Application.Current?.Styles.OfType<FluentAvaloniaTheme>().FirstOrDefault();
|
||||||
_settingsService.Changed += OnSettingsChanged;
|
_settingsService.Changed += OnSettingsChanged;
|
||||||
_appearanceThemeService.Changed += OnAppearanceThemeChanged;
|
_appearanceThemeService.Changed += OnAppearanceThemeChanged;
|
||||||
@@ -196,6 +208,170 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ApplyDesignTimePreview()
|
||||||
|
{
|
||||||
|
Title = "LanMountainDesktop Preview";
|
||||||
|
ShowInTaskbar = false;
|
||||||
|
DesktopWallpaperLayer.Background = new LinearGradientBrush
|
||||||
|
{
|
||||||
|
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
|
||||||
|
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
|
||||||
|
GradientStops = new GradientStops
|
||||||
|
{
|
||||||
|
new GradientStop(Color.Parse("#FFF6F8FB"), 0d),
|
||||||
|
new GradientStop(Color.Parse("#FFE9EEF7"), 0.55d),
|
||||||
|
new GradientStop(Color.Parse("#FFDCE5F3"), 1d)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
DesktopWallpaperImageLayer.IsVisible = false;
|
||||||
|
LauncherPagePanel.IsVisible = false;
|
||||||
|
ComponentLibraryWindow.IsVisible = false;
|
||||||
|
|
||||||
|
BackToWindowsTextBlock.Text = "Back to Windows";
|
||||||
|
ComponentLibraryTitleTextBlock.Text = "Widgets";
|
||||||
|
ComponentLibraryBackTextBlock.Text = "Back";
|
||||||
|
TaskbarProfileDisplayNameTextBlock.Text = "Preview User";
|
||||||
|
TaskbarProfileSettingsActionTextBlock.Text = "Settings";
|
||||||
|
TaskbarProfileDesktopEditActionTextBlock.Text = "Edit Desktop";
|
||||||
|
TaskbarProfileAvatarFallbackText.Text = "P";
|
||||||
|
TaskbarProfileHeaderAvatarFallbackText.Text = "P";
|
||||||
|
TaskbarProfileButton.IsEnabled = false;
|
||||||
|
TaskbarProfilePopup.IsOpen = false;
|
||||||
|
|
||||||
|
ClockWidget.IsVisible = true;
|
||||||
|
ClockWidget.SetDisplayFormat(ClockDisplayFormat.HourMinute);
|
||||||
|
ClockWidget.SetTransparentBackground(false);
|
||||||
|
|
||||||
|
ConfigureDesignTimeDesktopGrid();
|
||||||
|
PopulateDesignTimeDesktopSurface();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureDesignTimeDesktopGrid()
|
||||||
|
{
|
||||||
|
const int previewRows = 7;
|
||||||
|
const int previewColumns = 12;
|
||||||
|
|
||||||
|
DesktopGrid.RowDefinitions.Clear();
|
||||||
|
DesktopGrid.ColumnDefinitions.Clear();
|
||||||
|
|
||||||
|
for (var row = 0; row < previewRows; row++)
|
||||||
|
{
|
||||||
|
DesktopGrid.RowDefinitions.Add(new RowDefinition(new GridLength(1, GridUnitType.Star)));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var column = 0; column < previewColumns; column++)
|
||||||
|
{
|
||||||
|
DesktopGrid.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(1, GridUnitType.Star)));
|
||||||
|
}
|
||||||
|
|
||||||
|
DesktopGrid.Margin = new Thickness(28);
|
||||||
|
DesktopGrid.RowSpacing = 14;
|
||||||
|
DesktopGrid.ColumnSpacing = 14;
|
||||||
|
DesktopGrid.Width = double.NaN;
|
||||||
|
DesktopGrid.Height = double.NaN;
|
||||||
|
|
||||||
|
Grid.SetRow(TopStatusBarHost, 0);
|
||||||
|
Grid.SetColumn(TopStatusBarHost, 0);
|
||||||
|
Grid.SetRowSpan(TopStatusBarHost, 1);
|
||||||
|
Grid.SetColumnSpan(TopStatusBarHost, previewColumns);
|
||||||
|
|
||||||
|
Grid.SetRow(DesktopPagesViewport, 1);
|
||||||
|
Grid.SetColumn(DesktopPagesViewport, 0);
|
||||||
|
Grid.SetRowSpan(DesktopPagesViewport, previewRows - 2);
|
||||||
|
Grid.SetColumnSpan(DesktopPagesViewport, previewColumns);
|
||||||
|
|
||||||
|
Grid.SetRow(BottomTaskbarContainer, previewRows - 1);
|
||||||
|
Grid.SetColumn(BottomTaskbarContainer, 0);
|
||||||
|
Grid.SetRowSpan(BottomTaskbarContainer, 1);
|
||||||
|
Grid.SetColumnSpan(BottomTaskbarContainer, previewColumns);
|
||||||
|
|
||||||
|
DesktopPagesHost.ColumnDefinitions.Clear();
|
||||||
|
DesktopPagesHost.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(1, GridUnitType.Star)));
|
||||||
|
|
||||||
|
ClockWidget.ApplyCellSize(72);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PopulateDesignTimeDesktopSurface()
|
||||||
|
{
|
||||||
|
DesktopPagesContainer.Children.Clear();
|
||||||
|
DesktopPagesContainer.Width = double.NaN;
|
||||||
|
DesktopPagesContainer.Height = double.NaN;
|
||||||
|
|
||||||
|
DesktopPagesContainer.Children.Add(CreateDesignTimePreviewCard(
|
||||||
|
"Focus Clock",
|
||||||
|
"Compact widget preview",
|
||||||
|
32,
|
||||||
|
32,
|
||||||
|
300,
|
||||||
|
170,
|
||||||
|
"#FFFFFFFF",
|
||||||
|
"#FFE8EEF8"));
|
||||||
|
DesktopPagesContainer.Children.Add(CreateDesignTimePreviewCard(
|
||||||
|
"Weather",
|
||||||
|
"26°C Qingdao",
|
||||||
|
360,
|
||||||
|
86,
|
||||||
|
260,
|
||||||
|
132,
|
||||||
|
"#FFF8FBFF",
|
||||||
|
"#FFDDE8F6"));
|
||||||
|
DesktopPagesContainer.Children.Add(CreateDesignTimePreviewCard(
|
||||||
|
"Study Session",
|
||||||
|
"Deep work · 48 min",
|
||||||
|
210,
|
||||||
|
248,
|
||||||
|
340,
|
||||||
|
144,
|
||||||
|
"#FFFDFEFF",
|
||||||
|
"#FFE7EEF7"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Border CreateDesignTimePreviewCard(
|
||||||
|
string title,
|
||||||
|
string subtitle,
|
||||||
|
double left,
|
||||||
|
double top,
|
||||||
|
double width,
|
||||||
|
double height,
|
||||||
|
string backgroundColor,
|
||||||
|
string borderColor)
|
||||||
|
{
|
||||||
|
return new Border
|
||||||
|
{
|
||||||
|
Width = width,
|
||||||
|
Height = height,
|
||||||
|
Margin = new Thickness(left, top, 0, 0),
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top,
|
||||||
|
Background = new SolidColorBrush(Color.Parse(backgroundColor)),
|
||||||
|
BorderBrush = new SolidColorBrush(Color.Parse(borderColor)),
|
||||||
|
BorderThickness = new Thickness(1),
|
||||||
|
CornerRadius = new CornerRadius(28),
|
||||||
|
Child = new StackPanel
|
||||||
|
{
|
||||||
|
Margin = new Thickness(20),
|
||||||
|
Spacing = 8,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new TextBlock
|
||||||
|
{
|
||||||
|
Text = title,
|
||||||
|
FontSize = 20,
|
||||||
|
FontWeight = FontWeight.SemiBold,
|
||||||
|
Foreground = new SolidColorBrush(Color.Parse("#FF1E293B"))
|
||||||
|
},
|
||||||
|
new TextBlock
|
||||||
|
{
|
||||||
|
Text = subtitle,
|
||||||
|
FontSize = 13,
|
||||||
|
Foreground = new SolidColorBrush(Color.Parse("#FF64748B"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private void OnNightModeIsCheckedChanged(object? sender, RoutedEventArgs e)
|
private void OnNightModeIsCheckedChanged(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is not ToggleButton toggleButton)
|
if (sender is not ToggleButton toggleButton)
|
||||||
@@ -231,6 +407,14 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
protected override void OnOpened(EventArgs e)
|
protected override void OnOpened(EventArgs e)
|
||||||
{
|
{
|
||||||
base.OnOpened(e);
|
base.OnOpened(e);
|
||||||
|
|
||||||
|
if (Design.IsDesignMode)
|
||||||
|
{
|
||||||
|
ConfigureDesignTimeDesktopGrid();
|
||||||
|
PopulateDesignTimeDesktopSurface();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SyncSettingsWindowState();
|
SyncSettingsWindowState();
|
||||||
|
|
||||||
_suppressSettingsPersistence = true;
|
_suppressSettingsPersistence = true;
|
||||||
@@ -307,6 +491,12 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
|||||||
|
|
||||||
protected override void OnClosed(EventArgs e)
|
protected override void OnClosed(EventArgs e)
|
||||||
{
|
{
|
||||||
|
if (Design.IsDesignMode)
|
||||||
|
{
|
||||||
|
base.OnClosed(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var wasVisible = IsVisible;
|
var wasVisible = IsVisible;
|
||||||
var windowState = WindowState.ToString();
|
var windowState = WindowState.ToString();
|
||||||
|
|
||||||
|
|||||||
@@ -62,15 +62,34 @@
|
|||||||
</ui:InfoBar.IconSource>
|
</ui:InfoBar.IconSource>
|
||||||
</ui:InfoBar>
|
</ui:InfoBar>
|
||||||
|
|
||||||
<Border Classes="settings-section-card">
|
<!-- 版权声明 - 放在渲染显示前面 -->
|
||||||
<StackPanel Spacing="12">
|
<ui:SettingsExpander Header="版权声明"
|
||||||
<controls:IconText Icon="WindowConsole"
|
IsExpanded="True">
|
||||||
Text="{Binding RenderBackendLabel}" />
|
<ui:SettingsExpander.IconSource>
|
||||||
<TextBlock Classes="settings-item-description"
|
<fi:SymbolIconSource Symbol="Document" />
|
||||||
Text="{Binding RenderBackendText}"
|
</ui:SettingsExpander.IconSource>
|
||||||
TextWrapping="Wrap" />
|
<ui:SettingsExpanderItem>
|
||||||
</StackPanel>
|
<ui:SettingsExpanderItem.Footer>
|
||||||
</Border>
|
<WrapPanel>
|
||||||
|
<WrapPanel.Styles>
|
||||||
|
<Style Selector="HyperlinkButton">
|
||||||
|
<Setter Property="Padding" Value="4" />
|
||||||
|
<Setter Property="Margin" Value="2" />
|
||||||
|
</Style>
|
||||||
|
</WrapPanel.Styles>
|
||||||
|
<HyperlinkButton NavigateUri="https://github.com/wwiinnddyy/LanMountainDesktop">
|
||||||
|
<TextBlock Text="GitHub 仓库" />
|
||||||
|
</HyperlinkButton>
|
||||||
|
<HyperlinkButton NavigateUri="https://github.com/wwiinnddyy/LanMountainDesktop/issues">
|
||||||
|
<TextBlock Text="问题反馈" />
|
||||||
|
</HyperlinkButton>
|
||||||
|
</WrapPanel>
|
||||||
|
</ui:SettingsExpanderItem.Footer>
|
||||||
|
<TextBlock>
|
||||||
|
<Run Text="Copyright (c) 2024-" /><Run Text="2025" /> Lincube
|
||||||
|
</TextBlock>
|
||||||
|
</ui:SettingsExpanderItem>
|
||||||
|
</ui:SettingsExpander>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -12,12 +12,7 @@ namespace LanMountainDesktop.Views.SettingsPages;
|
|||||||
public partial class GeneratedPluginSettingsPage : SettingsPageBase
|
public partial class GeneratedPluginSettingsPage : SettingsPageBase
|
||||||
{
|
{
|
||||||
public GeneratedPluginSettingsPage()
|
public GeneratedPluginSettingsPage()
|
||||||
: this(
|
: this(Design.IsDesignMode ? CreateDesignTimeViewModel() : CreateDefaultViewModel())
|
||||||
new PluginGeneratedSettingsPageViewModel(
|
|
||||||
HostSettingsFacadeProvider.GetOrCreate().Settings,
|
|
||||||
string.Empty,
|
|
||||||
new PluginSettingsSectionRegistration("_preview", "preview", []),
|
|
||||||
new PluginLocalizer(AppContext.BaseDirectory, "en-US")))
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,4 +218,272 @@ public partial class GeneratedPluginSettingsPage : SettingsPageBase
|
|||||||
|
|
||||||
return textBox;
|
return textBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static PluginGeneratedSettingsPageViewModel CreateDefaultViewModel()
|
||||||
|
{
|
||||||
|
return new PluginGeneratedSettingsPageViewModel(
|
||||||
|
HostSettingsFacadeProvider.GetOrCreate().Settings,
|
||||||
|
string.Empty,
|
||||||
|
new PluginSettingsSectionRegistration("_preview", "preview", []),
|
||||||
|
new PluginLocalizer(AppContext.BaseDirectory, "en-US"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PluginGeneratedSettingsPageViewModel CreateDesignTimeViewModel()
|
||||||
|
{
|
||||||
|
const string pluginId = "preview.plugin";
|
||||||
|
var settingsService = new DesignTimeSettingsService();
|
||||||
|
var section = new PluginSettingsSectionRegistration(
|
||||||
|
"desktop_preview",
|
||||||
|
"Preview Widget Settings",
|
||||||
|
[
|
||||||
|
new SettingsOptionDefinition(
|
||||||
|
"enable_glow",
|
||||||
|
SettingsOptionType.Toggle,
|
||||||
|
"Enable glow",
|
||||||
|
"Adds a soft highlight around the preview widget.",
|
||||||
|
true),
|
||||||
|
new SettingsOptionDefinition(
|
||||||
|
"refresh_minutes",
|
||||||
|
SettingsOptionType.Number,
|
||||||
|
"Refresh interval",
|
||||||
|
"How often the plugin refreshes its cached content.",
|
||||||
|
30d,
|
||||||
|
minimum: 5d,
|
||||||
|
maximum: 120d),
|
||||||
|
new SettingsOptionDefinition(
|
||||||
|
"layout_density",
|
||||||
|
SettingsOptionType.Select,
|
||||||
|
"Layout density",
|
||||||
|
"Choose how compact the widget layout should feel.",
|
||||||
|
"balanced",
|
||||||
|
[
|
||||||
|
new SettingsOptionChoice("compact", "Compact"),
|
||||||
|
new SettingsOptionChoice("balanced", "Balanced"),
|
||||||
|
new SettingsOptionChoice("comfortable", "Comfortable")
|
||||||
|
]),
|
||||||
|
new SettingsOptionDefinition(
|
||||||
|
"content_path",
|
||||||
|
SettingsOptionType.Path,
|
||||||
|
"Content folder",
|
||||||
|
"Local folder used by the plugin for mock assets.",
|
||||||
|
@"C:\Preview\PluginAssets"),
|
||||||
|
new SettingsOptionDefinition(
|
||||||
|
"keywords",
|
||||||
|
SettingsOptionType.List,
|
||||||
|
"Pinned keywords",
|
||||||
|
"Comma-separated topics that will be emphasized in the widget.",
|
||||||
|
new[] { "avalonia", "preview", "design-time" })
|
||||||
|
],
|
||||||
|
"Mock plugin settings shown only in Avalonia design mode.");
|
||||||
|
|
||||||
|
settingsService.SetValue(
|
||||||
|
SettingsScope.Plugin,
|
||||||
|
"enable_glow",
|
||||||
|
true,
|
||||||
|
pluginId,
|
||||||
|
sectionId: section.Id);
|
||||||
|
settingsService.SetValue(
|
||||||
|
SettingsScope.Plugin,
|
||||||
|
"refresh_minutes",
|
||||||
|
30d,
|
||||||
|
pluginId,
|
||||||
|
sectionId: section.Id);
|
||||||
|
settingsService.SetValue(
|
||||||
|
SettingsScope.Plugin,
|
||||||
|
"layout_density",
|
||||||
|
"balanced",
|
||||||
|
pluginId,
|
||||||
|
sectionId: section.Id);
|
||||||
|
settingsService.SetValue(
|
||||||
|
SettingsScope.Plugin,
|
||||||
|
"content_path",
|
||||||
|
@"C:\Preview\PluginAssets",
|
||||||
|
pluginId,
|
||||||
|
sectionId: section.Id);
|
||||||
|
settingsService.SetValue(
|
||||||
|
SettingsScope.Plugin,
|
||||||
|
"keywords",
|
||||||
|
new[] { "avalonia", "preview", "design-time" },
|
||||||
|
pluginId,
|
||||||
|
sectionId: section.Id);
|
||||||
|
|
||||||
|
return new PluginGeneratedSettingsPageViewModel(
|
||||||
|
settingsService,
|
||||||
|
pluginId,
|
||||||
|
section,
|
||||||
|
new PluginLocalizer(AppContext.BaseDirectory, "en-US"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class DesignTimeSettingsService : ISettingsService
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, object?> _values = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
public event EventHandler<SettingsChangedEvent>? Changed;
|
||||||
|
|
||||||
|
public T LoadSnapshot<T>(SettingsScope scope, string? subjectId = null, string? placementId = null) where T : new()
|
||||||
|
=> new();
|
||||||
|
|
||||||
|
public void SaveSnapshot<T>(
|
||||||
|
SettingsScope scope,
|
||||||
|
T snapshot,
|
||||||
|
string? subjectId = null,
|
||||||
|
string? placementId = null,
|
||||||
|
string? sectionId = null,
|
||||||
|
IReadOnlyCollection<string>? changedKeys = null)
|
||||||
|
{
|
||||||
|
RaiseChanged(scope, subjectId, placementId, sectionId, changedKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T LoadSection<T>(
|
||||||
|
SettingsScope scope,
|
||||||
|
string subjectId,
|
||||||
|
string sectionId,
|
||||||
|
string? placementId = null) where T : new()
|
||||||
|
=> new();
|
||||||
|
|
||||||
|
public void SaveSection<T>(
|
||||||
|
SettingsScope scope,
|
||||||
|
string subjectId,
|
||||||
|
string sectionId,
|
||||||
|
T section,
|
||||||
|
string? placementId = null,
|
||||||
|
IReadOnlyCollection<string>? changedKeys = null)
|
||||||
|
{
|
||||||
|
RaiseChanged(scope, subjectId, placementId, sectionId, changedKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteSection(
|
||||||
|
SettingsScope scope,
|
||||||
|
string subjectId,
|
||||||
|
string sectionId,
|
||||||
|
string? placementId = null)
|
||||||
|
{
|
||||||
|
var prefix = BuildStorageKey(scope, subjectId, placementId, sectionId, key: null);
|
||||||
|
foreach (var existingKey in _values.Keys.Where(key => key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToArray())
|
||||||
|
{
|
||||||
|
_values.Remove(existingKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
RaiseChanged(scope, subjectId, placementId, sectionId, changedKeys: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T? GetValue<T>(
|
||||||
|
SettingsScope scope,
|
||||||
|
string key,
|
||||||
|
string? subjectId = null,
|
||||||
|
string? placementId = null,
|
||||||
|
string? sectionId = null)
|
||||||
|
{
|
||||||
|
return _values.TryGetValue(BuildStorageKey(scope, subjectId, placementId, sectionId, key), out var value)
|
||||||
|
? ConvertValue<T>(value)
|
||||||
|
: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetValue<T>(
|
||||||
|
SettingsScope scope,
|
||||||
|
string key,
|
||||||
|
T value,
|
||||||
|
string? subjectId = null,
|
||||||
|
string? placementId = null,
|
||||||
|
string? sectionId = null,
|
||||||
|
IReadOnlyCollection<string>? changedKeys = null)
|
||||||
|
{
|
||||||
|
_values[BuildStorageKey(scope, subjectId, placementId, sectionId, key)] = value;
|
||||||
|
RaiseChanged(scope, subjectId, placementId, sectionId, changedKeys ?? [key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IComponentSettingsAccessor GetComponentAccessor(string componentId, string? placementId)
|
||||||
|
{
|
||||||
|
return new DesignTimeComponentSettingsAccessor(this, componentId, placementId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T? ConvertValue<T>(object? value)
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value is T typedValue)
|
||||||
|
{
|
||||||
|
return typedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (T?)Convert.ChangeType(value, targetType);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildStorageKey(
|
||||||
|
SettingsScope scope,
|
||||||
|
string? subjectId,
|
||||||
|
string? placementId,
|
||||||
|
string? sectionId,
|
||||||
|
string? key)
|
||||||
|
{
|
||||||
|
return string.Join(
|
||||||
|
"|",
|
||||||
|
scope,
|
||||||
|
subjectId ?? string.Empty,
|
||||||
|
placementId ?? string.Empty,
|
||||||
|
sectionId ?? string.Empty,
|
||||||
|
key ?? string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RaiseChanged(
|
||||||
|
SettingsScope scope,
|
||||||
|
string? subjectId,
|
||||||
|
string? placementId,
|
||||||
|
string? sectionId,
|
||||||
|
IReadOnlyCollection<string>? changedKeys)
|
||||||
|
{
|
||||||
|
Changed?.Invoke(this, new SettingsChangedEvent(scope, subjectId, placementId, sectionId, changedKeys));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class DesignTimeComponentSettingsAccessor : IComponentSettingsAccessor
|
||||||
|
{
|
||||||
|
private readonly DesignTimeSettingsService _settingsService;
|
||||||
|
|
||||||
|
public DesignTimeComponentSettingsAccessor(
|
||||||
|
DesignTimeSettingsService settingsService,
|
||||||
|
string componentId,
|
||||||
|
string? placementId)
|
||||||
|
{
|
||||||
|
_settingsService = settingsService;
|
||||||
|
ComponentId = componentId;
|
||||||
|
PlacementId = placementId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ComponentId { get; }
|
||||||
|
|
||||||
|
public string? PlacementId { get; }
|
||||||
|
|
||||||
|
public T LoadSnapshot<T>() where T : new()
|
||||||
|
=> _settingsService.LoadSnapshot<T>(SettingsScope.ComponentInstance, ComponentId, PlacementId);
|
||||||
|
|
||||||
|
public void SaveSnapshot<T>(T snapshot, IReadOnlyCollection<string>? changedKeys = null)
|
||||||
|
=> _settingsService.SaveSnapshot(SettingsScope.ComponentInstance, snapshot, ComponentId, PlacementId, changedKeys: changedKeys);
|
||||||
|
|
||||||
|
public T LoadSection<T>(string sectionId) where T : new()
|
||||||
|
=> _settingsService.LoadSection<T>(SettingsScope.ComponentInstance, ComponentId, sectionId, PlacementId);
|
||||||
|
|
||||||
|
public void SaveSection<T>(string sectionId, T section, IReadOnlyCollection<string>? changedKeys = null)
|
||||||
|
=> _settingsService.SaveSection(SettingsScope.ComponentInstance, ComponentId, sectionId, section, PlacementId, changedKeys);
|
||||||
|
|
||||||
|
public void DeleteSection(string sectionId)
|
||||||
|
=> _settingsService.DeleteSection(SettingsScope.ComponentInstance, ComponentId, sectionId, PlacementId);
|
||||||
|
|
||||||
|
public T? GetValue<T>(string key)
|
||||||
|
=> _settingsService.GetValue<T>(SettingsScope.ComponentInstance, key, ComponentId, PlacementId);
|
||||||
|
|
||||||
|
public void SetValue<T>(string key, T value, IReadOnlyCollection<string>? changedKeys = null)
|
||||||
|
=> _settingsService.SetValue(SettingsScope.ComponentInstance, key, value, ComponentId, PlacementId, changedKeys: changedKeys);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System;
|
||||||
|
using Avalonia.Controls;
|
||||||
using LanMountainDesktop.PluginSdk;
|
using LanMountainDesktop.PluginSdk;
|
||||||
using LanMountainDesktop.Services;
|
using LanMountainDesktop.Services;
|
||||||
using LanMountainDesktop.Services.PluginMarket;
|
using LanMountainDesktop.Services.PluginMarket;
|
||||||
@@ -17,7 +19,7 @@ namespace LanMountainDesktop.Views.SettingsPages;
|
|||||||
public partial class PluginMarketSettingsPage : SettingsPageBase
|
public partial class PluginMarketSettingsPage : SettingsPageBase
|
||||||
{
|
{
|
||||||
public PluginMarketSettingsPage()
|
public PluginMarketSettingsPage()
|
||||||
: this(CreateDefaultViewModel())
|
: this(Design.IsDesignMode ? CreateDesignTimeViewModel() : CreateDefaultViewModel())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +36,11 @@ public partial class PluginMarketSettingsPage : SettingsPageBase
|
|||||||
|
|
||||||
public override async void OnNavigatedTo(object? parameter)
|
public override async void OnNavigatedTo(object? parameter)
|
||||||
{
|
{
|
||||||
|
if (Design.IsDesignMode)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await ViewModel.InitializeAsync();
|
await ViewModel.InitializeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,6 +55,113 @@ public partial class PluginMarketSettingsPage : SettingsPageBase
|
|||||||
new AirAppMarketReadmeService());
|
new AirAppMarketReadmeService());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static PluginMarketSettingsPageViewModel CreateDesignTimeViewModel()
|
||||||
|
{
|
||||||
|
var settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
|
||||||
|
var localizationService = new LocalizationService();
|
||||||
|
var viewModel = new PluginMarketSettingsPageViewModel(
|
||||||
|
settingsFacade,
|
||||||
|
localizationService,
|
||||||
|
new AirAppMarketIconService(),
|
||||||
|
new AirAppMarketReadmeService());
|
||||||
|
|
||||||
|
var previewHostVersion = new Version(1, 2, 0);
|
||||||
|
var items = new[]
|
||||||
|
{
|
||||||
|
CreateMarketItem(
|
||||||
|
new PluginMarketPluginInfo(
|
||||||
|
"news-tiles",
|
||||||
|
"News Tiles",
|
||||||
|
"Brings editorial news cards and ticker rows to the desktop.",
|
||||||
|
"LanMountain Labs",
|
||||||
|
"1.2.0",
|
||||||
|
"1.0.0",
|
||||||
|
"1.0.0",
|
||||||
|
"https://example.com/news-tiles.zip",
|
||||||
|
"v1.2.0",
|
||||||
|
"news-tiles.zip",
|
||||||
|
string.Empty,
|
||||||
|
"https://example.com/news-tiles/readme",
|
||||||
|
"https://example.com/news-tiles",
|
||||||
|
"https://example.com/news-tiles/repo",
|
||||||
|
["news", "widgets"],
|
||||||
|
[],
|
||||||
|
DateTimeOffset.Now.AddDays(-8),
|
||||||
|
DateTimeOffset.Now.AddDays(-2)),
|
||||||
|
localizationService,
|
||||||
|
installedPlugin: null,
|
||||||
|
previewHostVersion),
|
||||||
|
CreateMarketItem(
|
||||||
|
new PluginMarketPluginInfo(
|
||||||
|
"workspace-pulse",
|
||||||
|
"Workspace Pulse",
|
||||||
|
"Tracks active projects and shows a compact productivity summary.",
|
||||||
|
"Studio North",
|
||||||
|
"2.4.0",
|
||||||
|
"1.0.0",
|
||||||
|
"1.0.0",
|
||||||
|
"https://example.com/workspace-pulse.zip",
|
||||||
|
"v2.4.0",
|
||||||
|
"workspace-pulse.zip",
|
||||||
|
string.Empty,
|
||||||
|
"https://example.com/workspace-pulse/readme",
|
||||||
|
"https://example.com/workspace-pulse",
|
||||||
|
"https://example.com/workspace-pulse/repo",
|
||||||
|
["dashboard", "productivity"],
|
||||||
|
[],
|
||||||
|
DateTimeOffset.Now.AddDays(-30),
|
||||||
|
DateTimeOffset.Now.AddDays(-1)),
|
||||||
|
localizationService,
|
||||||
|
new InstalledPluginInfo(
|
||||||
|
new PluginManifest(
|
||||||
|
"workspace-pulse",
|
||||||
|
"Workspace Pulse",
|
||||||
|
"WorkspacePulse.dll",
|
||||||
|
"Tracks active projects and shows a compact productivity summary.",
|
||||||
|
"Studio North",
|
||||||
|
"2.1.0"),
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
null),
|
||||||
|
previewHostVersion),
|
||||||
|
CreateMarketItem(
|
||||||
|
new PluginMarketPluginInfo(
|
||||||
|
"glass-panels",
|
||||||
|
"Glass Panels",
|
||||||
|
"Adds experimental acrylic surfaces for plugin-powered widgets.",
|
||||||
|
"Aster Team",
|
||||||
|
"0.8.0",
|
||||||
|
"1.0.0",
|
||||||
|
"9.0.0",
|
||||||
|
"https://example.com/glass-panels.zip",
|
||||||
|
"v0.8.0",
|
||||||
|
"glass-panels.zip",
|
||||||
|
string.Empty,
|
||||||
|
"https://example.com/glass-panels/readme",
|
||||||
|
"https://example.com/glass-panels",
|
||||||
|
"https://example.com/glass-panels/repo",
|
||||||
|
["theme", "experimental"],
|
||||||
|
[],
|
||||||
|
DateTimeOffset.Now.AddDays(-12),
|
||||||
|
DateTimeOffset.Now.AddDays(-3)),
|
||||||
|
localizationService,
|
||||||
|
installedPlugin: null,
|
||||||
|
previewHostVersion)
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
viewModel.MarketPlugins.Add(item);
|
||||||
|
viewModel.FilteredPlugins.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.ShowEmptyState = false;
|
||||||
|
viewModel.EmptyStateText = string.Empty;
|
||||||
|
viewModel.StatusMessage = "Showing 3 mocked marketplace plugins for Avalonia design mode.";
|
||||||
|
return viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnRestartRequested(string? reason)
|
private void OnRestartRequested(string? reason)
|
||||||
{
|
{
|
||||||
RequestRestart(reason ?? ViewModel.RestartRequiredMessage);
|
RequestRestart(reason ?? ViewModel.RestartRequiredMessage);
|
||||||
@@ -60,4 +174,17 @@ public partial class PluginMarketSettingsPage : SettingsPageBase
|
|||||||
OpenDrawer(drawer, detailViewModel.DrawerTitle);
|
OpenDrawer(drawer, detailViewModel.DrawerTitle);
|
||||||
await detailViewModel.InitializeAsync();
|
await detailViewModel.InitializeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static PluginMarketItemViewModel CreateMarketItem(
|
||||||
|
PluginMarketPluginInfo plugin,
|
||||||
|
LocalizationService localizationService,
|
||||||
|
InstalledPluginInfo? installedPlugin,
|
||||||
|
Version hostVersion)
|
||||||
|
{
|
||||||
|
var languageCode = localizationService.NormalizeLanguageCode(
|
||||||
|
HostSettingsFacadeProvider.GetOrCreate().Region.Get().LanguageCode);
|
||||||
|
var item = new PluginMarketItemViewModel(plugin, localizationService, languageCode);
|
||||||
|
item.ApplyInstallState(installedPlugin, hostVersion);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
using LanMountainDesktop.PluginSdk;
|
using LanMountainDesktop.PluginSdk;
|
||||||
using LanMountainDesktop.Services.Settings;
|
using LanMountainDesktop.Services.Settings;
|
||||||
using LanMountainDesktop.ViewModels;
|
using LanMountainDesktop.ViewModels;
|
||||||
@@ -15,7 +16,7 @@ namespace LanMountainDesktop.Views.SettingsPages;
|
|||||||
public partial class PluginsSettingsPage : SettingsPageBase
|
public partial class PluginsSettingsPage : SettingsPageBase
|
||||||
{
|
{
|
||||||
public PluginsSettingsPage()
|
public PluginsSettingsPage()
|
||||||
: this(new PluginsSettingsPageViewModel(HostSettingsFacadeProvider.GetOrCreate()))
|
: this(Design.IsDesignMode ? CreateDesignTimeViewModel() : new PluginsSettingsPageViewModel(HostSettingsFacadeProvider.GetOrCreate()))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,6 +32,11 @@ public partial class PluginsSettingsPage : SettingsPageBase
|
|||||||
|
|
||||||
public override async void OnNavigatedTo(object? parameter)
|
public override async void OnNavigatedTo(object? parameter)
|
||||||
{
|
{
|
||||||
|
if (Design.IsDesignMode)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await ViewModel.InitializeAsync();
|
await ViewModel.InitializeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,4 +44,47 @@ public partial class PluginsSettingsPage : SettingsPageBase
|
|||||||
{
|
{
|
||||||
RequestRestart(ViewModel.RestartRequiredMessage);
|
RequestRestart(ViewModel.RestartRequiredMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static PluginsSettingsPageViewModel CreateDesignTimeViewModel()
|
||||||
|
{
|
||||||
|
var viewModel = new PluginsSettingsPageViewModel(HostSettingsFacadeProvider.GetOrCreate());
|
||||||
|
viewModel.InstalledPlugins.Add(new InstalledPluginItemViewModel(new InstalledPluginInfo(
|
||||||
|
new PluginManifest(
|
||||||
|
"calendar-plus",
|
||||||
|
"Calendar Plus",
|
||||||
|
"CalendarPlus.dll",
|
||||||
|
"Adds a compact agenda widget and richer date cards.",
|
||||||
|
"LanMountain Labs",
|
||||||
|
"1.4.0"),
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
null)));
|
||||||
|
viewModel.InstalledPlugins.Add(new InstalledPluginItemViewModel(new InstalledPluginInfo(
|
||||||
|
new PluginManifest(
|
||||||
|
"focus-mode",
|
||||||
|
"Focus Mode",
|
||||||
|
"FocusMode.dll",
|
||||||
|
"Provides a distraction-free overlay and quick toggles.",
|
||||||
|
"Studio North",
|
||||||
|
"0.9.2"),
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
null)));
|
||||||
|
viewModel.InstalledPlugins.Add(new InstalledPluginItemViewModel(new InstalledPluginInfo(
|
||||||
|
new PluginManifest(
|
||||||
|
"notes-dock",
|
||||||
|
"Notes Dock",
|
||||||
|
"NotesDock.dll",
|
||||||
|
"Pins short markdown notes directly on the desktop.",
|
||||||
|
"Aster Team",
|
||||||
|
"2.1.0"),
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
null)));
|
||||||
|
viewModel.StatusMessage = "Loaded 3 mocked plugins for Avalonia design mode.";
|
||||||
|
return viewModel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,14 +51,9 @@
|
|||||||
FontFamily="Consolas"
|
FontFamily="Consolas"
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
Focusable="False"
|
Focusable="False"
|
||||||
IsTabStop="False" />
|
IsTabStop="False"
|
||||||
|
HorizontalAlignment="Stretch" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Button Grid.Column="1"
|
|
||||||
Content="{Binding RefreshTelemetryIdText}"
|
|
||||||
Command="{Binding RefreshTelemetryIdCommand}"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Margin="16,0,0,0"
|
|
||||||
Classes="accent-button" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
using LanMountainDesktop.PluginSdk;
|
using LanMountainDesktop.PluginSdk;
|
||||||
using LanMountainDesktop.Services;
|
using LanMountainDesktop.Services;
|
||||||
using LanMountainDesktop.Services.Settings;
|
using LanMountainDesktop.Services.Settings;
|
||||||
@@ -16,7 +17,7 @@ namespace LanMountainDesktop.Views.SettingsPages;
|
|||||||
public partial class WeatherSettingsPage : SettingsPageBase
|
public partial class WeatherSettingsPage : SettingsPageBase
|
||||||
{
|
{
|
||||||
public WeatherSettingsPage()
|
public WeatherSettingsPage()
|
||||||
: this(CreateDefaultViewModel())
|
: this(Design.IsDesignMode ? CreateDesignTimeViewModel() : CreateDefaultViewModel())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ public partial class WeatherSettingsPage : SettingsPageBase
|
|||||||
|
|
||||||
public WeatherSettingsPageViewModel ViewModel { get; }
|
public WeatherSettingsPageViewModel ViewModel { get; }
|
||||||
|
|
||||||
private static WeatherSettingsPageViewModel CreateDefaultViewModel()
|
private static WeatherSettingsPageViewModel CreateDefaultViewModel(bool enableStartupPreviewRefresh = true)
|
||||||
{
|
{
|
||||||
var settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
|
var settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
|
||||||
var localizationService = new LocalizationService();
|
var localizationService = new LocalizationService();
|
||||||
@@ -42,6 +43,14 @@ public partial class WeatherSettingsPage : SettingsPageBase
|
|||||||
settingsFacade,
|
settingsFacade,
|
||||||
localizationService,
|
localizationService,
|
||||||
locationService,
|
locationService,
|
||||||
weatherLocationRefreshService);
|
weatherLocationRefreshService,
|
||||||
|
enableStartupPreviewRefresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WeatherSettingsPageViewModel CreateDesignTimeViewModel()
|
||||||
|
{
|
||||||
|
var viewModel = CreateDefaultViewModel(enableStartupPreviewRefresh: false);
|
||||||
|
viewModel.ApplyDesignTimePreview();
|
||||||
|
return viewModel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,12 @@
|
|||||||
|
|
||||||
## 2. 使用场景
|
## 2. 使用场景
|
||||||
|
|
||||||
| 场景 | 说明 |
|
| 场景 | 说明 |
|
||||||
|-----|------|
|
| ---- | ---------------------- |
|
||||||
| 学习辅助 | 查看课程表、记录自习时长、获取每日诗词单词 |
|
| 学习辅助 | 查看课程表、记录自习时长、获取每日诗词单词 |
|
||||||
| 办公效率 | 查看日历日程、快速访问最近文档、获取新闻资讯 |
|
| 办公效率 | 查看日历日程、快速访问最近文档、获取新闻资讯 |
|
||||||
| 信息聚合 | 桌面一站式查看天气、日历、热搜、新闻 |
|
| 信息聚合 | 桌面一站式查看天气、日历、热搜、新闻 |
|
||||||
| 个性美化 | 自由定制桌面组件布局、主题、壁纸 |
|
| 个性美化 | 自由定制桌面组件布局、主题、壁纸 |
|
||||||
|
|
||||||
## 3. 解决方案
|
## 3. 解决方案
|
||||||
|
|
||||||
@@ -27,20 +27,17 @@
|
|||||||
|
|
||||||
## 4. 解决的问题
|
## 4. 解决的问题
|
||||||
|
|
||||||
| 痛点 | 解决方案 |
|
| 痛点 | 解决方案 |
|
||||||
|-----|---------|
|
| -------------- | -------------------- |
|
||||||
| 信息分散,需打开多个应用 | 桌面聚合展示天气、日历、新闻等信息 |
|
| 信息分散,需打开多个应用 | 桌面聚合展示天气、日历、新闻等信息 |
|
||||||
| 桌面单调,缺乏个性化 | 丰富的组件和主题自由定制 |
|
| 桌面单调,缺乏个性化 | 丰富的组件和主题自由定制 |
|
||||||
| 学习管理不便 | 课程表、自习监测专为学生设计 |
|
| 学习管理不便 | 课程表、自习监测专为学生设计 |
|
||||||
| 功能单一,需安装多个独立应用 | 一个应用整合考试看板、噪音监测等多种功能 |
|
| 功能单一,需安装多个独立应用 | 一个应用整合考试看板、噪音监测等多种功能 |
|
||||||
| 功能无法满足个性需求 | 插件系统支持无限扩展 |
|
| 功能无法满足个性需求 | 插件系统支持无限扩展 |
|
||||||
|
|
||||||
## 5. 产品进度
|
## 5. 产品进度
|
||||||
|
|
||||||
- **当前版本**:v0.7.0(插件 API 3.0.0)
|
- **当前版本**:v0.7.0(插件 API 3.0.0)
|
||||||
- **开发状态**:核心功能开发中,预计 v1.0 正式发布
|
- **开发状态**:功能开发中,预计 1\~2 个月内发布 v1.0 正式版
|
||||||
- **用户统计**:通过 PostHog 收集匿名数据(具体数据需后台查看)
|
- **用户统计**:通过 PostHog 收集匿名数据(具体数据需后台查看)
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**一句话总结**:阑山桌面是一款面向个人用户的可定制桌面工具,专注个人学习办公场景,通过组件化设计和插件生态提供轻量、开放、跨平台的桌面信息聚合方案。
|
|
||||||
|
|||||||
Reference in New Issue
Block a user