mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 00:54:26 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15e589aedd | ||
|
|
ac4617f5cf | ||
|
|
0645598753 |
@@ -510,6 +510,8 @@ public partial class App : Application
|
|||||||
|
|
||||||
if (languageChanged)
|
if (languageChanged)
|
||||||
{
|
{
|
||||||
|
// 清除本地化缓存,强制重新加载语言文件
|
||||||
|
_localizationService.ClearCache();
|
||||||
ApplyCurrentCultureFromSettings();
|
ApplyCurrentCultureFromSettings();
|
||||||
if (_trayIcons is not null)
|
if (_trayIcons is not null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# LanMountainDesktop 隐私政策
|
# LanMountainDesktop 隐私政策
|
||||||
|
|
||||||
**最后更新日期:2024年**
|
**最后更新日期:2026年3月17日**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -321,6 +321,6 @@ a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**感谢您信任 LanMountainDesktop!**
|
**感谢您信任阑山桌面LanMountainDesktop!**
|
||||||
|
|
||||||
我们承诺保护您的隐私,并持续改进我们的隐私保护措施。
|
我们承诺保护您的隐私,并持续改进我们的隐私保护措施。
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
@@ -21,7 +21,9 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Models\" />
|
<Folder Include="Models\" />
|
||||||
<AvaloniaResource Include="Assets\**" />
|
<AvaloniaResource Include="Assets\**" />
|
||||||
|
<AvaloniaResource Include="Localization\**" />
|
||||||
<EmbeddedResource Include="Assets\Documents\Privacy.md" />
|
<EmbeddedResource Include="Assets\Documents\Privacy.md" />
|
||||||
|
<EmbeddedResource Include="Localization\*.json" />
|
||||||
<None Include="Localization\*.json" CopyToOutputDirectory="PreserveNewest" />
|
<None Include="Localization\*.json" CopyToOutputDirectory="PreserveNewest" />
|
||||||
<None Include="Extensions\Components\*.json" CopyToOutputDirectory="PreserveNewest" />
|
<None Include="Extensions\Components\*.json" CopyToOutputDirectory="PreserveNewest" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -559,6 +559,7 @@
|
|||||||
"component_category.info": "Info",
|
"component_category.info": "Info",
|
||||||
"component_category.calculator": "Calculator",
|
"component_category.calculator": "Calculator",
|
||||||
"component_category.study": "Study",
|
"component_category.study": "Study",
|
||||||
|
"component_category.file": "File",
|
||||||
"component.date": "Calendar",
|
"component.date": "Calendar",
|
||||||
"component.month_calendar": "Month Calendar",
|
"component.month_calendar": "Month Calendar",
|
||||||
"component.lunar_calendar": "Lunar Calendar",
|
"component.lunar_calendar": "Lunar Calendar",
|
||||||
@@ -892,5 +893,7 @@
|
|||||||
"placement.tile": "Tile",
|
"placement.tile": "Tile",
|
||||||
"single_instance.notice.title": "App already running",
|
"single_instance.notice.title": "App already running",
|
||||||
"single_instance.notice.description": "The app is already running. There is no need to click multiple times to open it.",
|
"single_instance.notice.description": "The app is already running. There is no need to click multiple times to open it.",
|
||||||
"single_instance.notice.button": "OK"
|
"single_instance.notice.button": "OK",
|
||||||
|
"market.status.install_success_restart_format": "✓ Plugin '{0}' installed successfully! Please restart the application to activate it.",
|
||||||
|
"market.dialog.restart_message_format": "Plugin '{0}' has been installed successfully.\n\nTo use this plugin, you need to restart the application now.\n\nWould you like to restart?"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -557,6 +557,7 @@
|
|||||||
"component_category.info": "信息推荐",
|
"component_category.info": "信息推荐",
|
||||||
"component_category.calculator": "计算器",
|
"component_category.calculator": "计算器",
|
||||||
"component_category.study": "自习",
|
"component_category.study": "自习",
|
||||||
|
"component_category.file": "文件",
|
||||||
"component.date": "日历",
|
"component.date": "日历",
|
||||||
"component.month_calendar": "月历",
|
"component.month_calendar": "月历",
|
||||||
"component.lunar_calendar": "农历",
|
"component.lunar_calendar": "农历",
|
||||||
@@ -890,5 +891,7 @@
|
|||||||
"placement.tile": "平铺",
|
"placement.tile": "平铺",
|
||||||
"single_instance.notice.title": "应用已经运行",
|
"single_instance.notice.title": "应用已经运行",
|
||||||
"single_instance.notice.description": "应用已经运行,无需多次点击打开。",
|
"single_instance.notice.description": "应用已经运行,无需多次点击打开。",
|
||||||
"single_instance.notice.button": "确定"
|
"single_instance.notice.button": "确定",
|
||||||
|
"market.status.install_success_restart_format": "✓ 插件'{0}'安装成功!请重启应用以激活它。",
|
||||||
|
"market.dialog.restart_message_format": "插件'{0}'已成功安装。\n\n要使用此插件,您需要立即重启应用。\n\n是否立即重启?"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace LanMountainDesktop.Services;
|
namespace LanMountainDesktop.Services;
|
||||||
@@ -16,6 +17,23 @@ public sealed class LocalizationService
|
|||||||
private readonly Dictionary<string, Dictionary<string, string>> _cache =
|
private readonly Dictionary<string, Dictionary<string, string>> _cache =
|
||||||
new(StringComparer.OrdinalIgnoreCase);
|
new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清除指定语言代码的缓存,强制下次重新加载。
|
||||||
|
/// 在语言切换时调用此方法以确保加载最新的语言文件。
|
||||||
|
/// </summary>
|
||||||
|
public void ClearCache(string? languageCode = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(languageCode))
|
||||||
|
{
|
||||||
|
_cache.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var normalizedCode = NormalizeLanguageCode(languageCode);
|
||||||
|
_cache.Remove(normalizedCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string NormalizeLanguageCode(string? languageCode)
|
public string NormalizeLanguageCode(string? languageCode)
|
||||||
{
|
{
|
||||||
return string.Equals(languageCode, "en-US", StringComparison.OrdinalIgnoreCase)
|
return string.Equals(languageCode, "en-US", StringComparison.OrdinalIgnoreCase)
|
||||||
@@ -42,14 +60,17 @@ public sealed class LocalizationService
|
|||||||
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var filePath = Path.Combine(AppContext.BaseDirectory, "Localization", $"{languageCode}.json");
|
var json = TryLoadFromFileSystem(languageCode);
|
||||||
if (File.Exists(filePath))
|
if (string.IsNullOrEmpty(json))
|
||||||
|
{
|
||||||
|
json = TryLoadFromEmbeddedResource(languageCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(json))
|
||||||
{
|
{
|
||||||
var json = File.ReadAllText(filePath);
|
|
||||||
// Defensive: tolerate accidentally duplicated UTF-8 BOM characters at file start.
|
|
||||||
json = json.TrimStart('\uFEFF');
|
json = json.TrimStart('\uFEFF');
|
||||||
var data = JsonSerializer.Deserialize<Dictionary<string, string>>(json, JsonOptions);
|
var data = JsonSerializer.Deserialize<Dictionary<string, string>>(json, JsonOptions);
|
||||||
if (data is not null)
|
if (data is not null && data.Count > 0)
|
||||||
{
|
{
|
||||||
result = new Dictionary<string, string>(data, StringComparer.OrdinalIgnoreCase);
|
result = new Dictionary<string, string>(data, StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
@@ -60,7 +81,48 @@ public sealed class LocalizationService
|
|||||||
// Keep empty table for resilience.
|
// Keep empty table for resilience.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 只有当语言表非空时才缓存,这样如果加载失败可以下次重试
|
||||||
|
if (result.Count > 0)
|
||||||
|
{
|
||||||
_cache[languageCode] = result;
|
_cache[languageCode] = result;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string? TryLoadFromFileSystem(string languageCode)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var filePath = Path.Combine(AppContext.BaseDirectory, "Localization", $"{languageCode}.json");
|
||||||
|
if (File.Exists(filePath))
|
||||||
|
{
|
||||||
|
return File.ReadAllText(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Continue to next method
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? TryLoadFromEmbeddedResource(string languageCode)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var assembly = Assembly.GetExecutingAssembly();
|
||||||
|
var resourceName = $"LanMountainDesktop.Localization.{languageCode}.json";
|
||||||
|
using var stream = assembly.GetManifestResourceStream(resourceName);
|
||||||
|
if (stream != null)
|
||||||
|
{
|
||||||
|
using var reader = new StreamReader(stream);
|
||||||
|
return reader.ReadToEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Continue to next method
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.Runtime.InteropServices;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using LanMountainDesktop.Services.Settings;
|
using LanMountainDesktop.Services.Settings;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
namespace LanMountainDesktop.Services;
|
namespace LanMountainDesktop.Services;
|
||||||
|
|
||||||
@@ -35,66 +36,19 @@ public sealed class OfficeRecentDocumentsService : IOfficeRecentDocumentsService
|
|||||||
public List<OfficeRecentDocument> GetRecentDocuments(int maxCount = 20)
|
public List<OfficeRecentDocument> GetRecentDocuments(int maxCount = 20)
|
||||||
{
|
{
|
||||||
var documents = new List<OfficeRecentDocument>();
|
var documents = new List<OfficeRecentDocument>();
|
||||||
var recentPaths = GetRecentFolders();
|
|
||||||
|
|
||||||
foreach (var recentPath in recentPaths)
|
// 方法1: 从注册表读取Office最近文档(最可靠)
|
||||||
{
|
TryGetFromRegistry(documents);
|
||||||
if (!Directory.Exists(recentPath))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
// 方法2: 从Recent文件夹读取快捷方式(备用)
|
||||||
{
|
TryGetFromRecentFolders(documents);
|
||||||
var files = Directory.GetFiles(recentPath, "*.lnk");
|
|
||||||
foreach (var lnkPath in files)
|
|
||||||
{
|
|
||||||
var targetPath = GetShortcutTarget(lnkPath);
|
|
||||||
if (string.IsNullOrEmpty(targetPath))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var extension = Path.GetExtension(targetPath).ToLowerInvariant();
|
// 方法3: 从Windows Jump List读取(如果可用)
|
||||||
if (!IsOfficeFile(extension))
|
TryGetFromJumpList(documents);
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!System.IO.File.Exists(targetPath))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var fileInfo = new FileInfo(targetPath);
|
|
||||||
var doc = new OfficeRecentDocument
|
|
||||||
{
|
|
||||||
FileName = Path.GetFileNameWithoutExtension(targetPath),
|
|
||||||
FilePath = targetPath,
|
|
||||||
Extension = extension,
|
|
||||||
LastModifiedTime = fileInfo.LastWriteTime,
|
|
||||||
FileSizeBytes = fileInfo.Length,
|
|
||||||
IconGlyph = GetIconGlyph(extension)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!documents.Any(d => d.FilePath == targetPath))
|
|
||||||
{
|
|
||||||
documents.Add(doc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return documents
|
return documents
|
||||||
|
.GroupBy(d => d.FilePath, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Select(g => g.OrderByDescending(d => d.LastModifiedTime).First())
|
||||||
.OrderByDescending(d => d.LastModifiedTime)
|
.OrderByDescending(d => d.LastModifiedTime)
|
||||||
.Take(maxCount)
|
.Take(maxCount)
|
||||||
.ToList();
|
.ToList();
|
||||||
@@ -116,6 +70,231 @@ public sealed class OfficeRecentDocumentsService : IOfficeRecentDocumentsService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning disable CA1416 // 平台兼容性警告
|
||||||
|
private void TryGetFromRegistry(List<OfficeRecentDocument> documents)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Word最近文档
|
||||||
|
TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\Word\Reading Locations");
|
||||||
|
TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\16.0\Word\Reading Locations");
|
||||||
|
TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\15.0\Word\Reading Locations");
|
||||||
|
|
||||||
|
// Excel最近文档
|
||||||
|
TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\Excel\Reading Locations");
|
||||||
|
TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\16.0\Excel\Reading Locations");
|
||||||
|
TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\15.0\Excel\Reading Locations");
|
||||||
|
|
||||||
|
// PowerPoint最近文档
|
||||||
|
TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\PowerPoint\Reading Locations");
|
||||||
|
TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\16.0\PowerPoint\Reading Locations");
|
||||||
|
TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\15.0\PowerPoint\Reading Locations");
|
||||||
|
|
||||||
|
// 通用Office最近文档
|
||||||
|
TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\16.0\Common\Open Find\Microsoft Office Word");
|
||||||
|
TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\16.0\Common\Open Find\Microsoft Office Excel");
|
||||||
|
TryGetFromOfficeRegistry(documents, @"Software\Microsoft\Office\16.0\Common\Open Find\Microsoft Office PowerPoint");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 忽略注册表访问错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryGetFromOfficeRegistry(List<OfficeRecentDocument> documents, string registryPath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var key = Registry.CurrentUser.OpenSubKey(registryPath);
|
||||||
|
if (key == null) return;
|
||||||
|
|
||||||
|
foreach (var subKeyName in key.GetSubKeyNames())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var subKey = key.OpenSubKey(subKeyName);
|
||||||
|
if (subKey == null) continue;
|
||||||
|
|
||||||
|
var filePath = subKey.GetValue("Path") as string;
|
||||||
|
if (string.IsNullOrEmpty(filePath)) continue;
|
||||||
|
|
||||||
|
AddDocumentIfExists(documents, filePath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 忽略单个子键访问错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 忽略注册表访问错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore CA1416 // 平台兼容性警告
|
||||||
|
|
||||||
|
private void TryGetFromRecentFolders(List<OfficeRecentDocument> documents)
|
||||||
|
{
|
||||||
|
var recentPaths = GetRecentFolders();
|
||||||
|
|
||||||
|
foreach (var recentPath in recentPaths)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(recentPath))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var files = Directory.GetFiles(recentPath, "*.lnk");
|
||||||
|
foreach (var lnkPath in files)
|
||||||
|
{
|
||||||
|
var targetPath = GetShortcutTarget(lnkPath);
|
||||||
|
if (string.IsNullOrEmpty(targetPath))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddDocumentIfExists(documents, targetPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 忽略文件夹访问错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryGetFromJumpList(List<OfficeRecentDocument> documents)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Windows Jump List存储在以下位置
|
||||||
|
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||||
|
var jumpListPath = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||||
|
"Microsoft", "Windows", "Recent", "AutomaticDestinations");
|
||||||
|
|
||||||
|
if (!Directory.Exists(jumpListPath)) return;
|
||||||
|
|
||||||
|
// Office应用的Jump List文件
|
||||||
|
var officeJumpListFiles = new[]
|
||||||
|
{
|
||||||
|
"a7bd7a3f3d5a4c74.automaticDestinations-ms", // Word
|
||||||
|
"9b524fe3be704a4d.automaticDestinations-ms", // Excel
|
||||||
|
"d0063c4c7de64e5e.automaticDestinations-ms" // PowerPoint
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var jumpFile in officeJumpListFiles)
|
||||||
|
{
|
||||||
|
var fullPath = Path.Combine(jumpListPath, jumpFile);
|
||||||
|
if (File.Exists(fullPath))
|
||||||
|
{
|
||||||
|
TryParseJumpListFile(fullPath, documents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Jump List解析失败,忽略
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryParseJumpListFile(string jumpListPath, List<OfficeRecentDocument> documents)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Jump List文件是二进制格式,这里使用简化的方法
|
||||||
|
// 读取文件并尝试提取文件路径
|
||||||
|
var bytes = File.ReadAllBytes(jumpListPath);
|
||||||
|
var text = Encoding.Unicode.GetString(bytes);
|
||||||
|
|
||||||
|
// 查找可能的文件路径(简化实现)
|
||||||
|
var possiblePaths = ExtractPossiblePaths(text);
|
||||||
|
foreach (var path in possiblePaths)
|
||||||
|
{
|
||||||
|
AddDocumentIfExists(documents, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Jump List解析失败,忽略
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<string> ExtractPossiblePaths(string text)
|
||||||
|
{
|
||||||
|
var paths = new List<string>();
|
||||||
|
|
||||||
|
// 查找常见的文件路径模式
|
||||||
|
var patterns = new[]
|
||||||
|
{
|
||||||
|
@"[A-Z]:\\[^\x00-\x1F""<>|]*\.(docx?|xlsx?|pptx?|rtf|csv)",
|
||||||
|
@"\\\\[^\\]+\\[^\x00-\x1F""<>|]*\.(docx?|xlsx?|pptx?|rtf|csv)"
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var pattern in patterns)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var matches = System.Text.RegularExpressions.Regex.Matches(text, pattern,
|
||||||
|
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
foreach (System.Text.RegularExpressions.Match match in matches)
|
||||||
|
{
|
||||||
|
var path = match.Value.Trim('\0', ' ', '"');
|
||||||
|
if (!string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
paths.Add(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 忽略正则表达式错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths.Distinct(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddDocumentIfExists(List<OfficeRecentDocument> documents, string filePath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var extension = Path.GetExtension(filePath).ToLowerInvariant();
|
||||||
|
if (!IsOfficeFile(extension))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileInfo = new FileInfo(filePath);
|
||||||
|
var doc = new OfficeRecentDocument
|
||||||
|
{
|
||||||
|
FileName = Path.GetFileNameWithoutExtension(filePath),
|
||||||
|
FilePath = filePath,
|
||||||
|
Extension = extension,
|
||||||
|
LastModifiedTime = fileInfo.LastWriteTime,
|
||||||
|
FileSizeBytes = fileInfo.Length,
|
||||||
|
IconGlyph = GetIconGlyph(extension)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!documents.Any(d => string.Equals(d.FilePath, filePath, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
documents.Add(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 忽略单个文件处理错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static List<string> GetRecentFolders()
|
private static List<string> GetRecentFolders()
|
||||||
{
|
{
|
||||||
var folders = new List<string>();
|
var folders = new List<string>();
|
||||||
@@ -125,6 +304,12 @@ public sealed class OfficeRecentDocumentsService : IOfficeRecentDocumentsService
|
|||||||
folders.Add(Path.Combine(appData, "Microsoft", "Excel", "Recent"));
|
folders.Add(Path.Combine(appData, "Microsoft", "Excel", "Recent"));
|
||||||
folders.Add(Path.Combine(appData, "Microsoft", "PowerPoint", "Recent"));
|
folders.Add(Path.Combine(appData, "Microsoft", "PowerPoint", "Recent"));
|
||||||
|
|
||||||
|
// 添加Office 365路径
|
||||||
|
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||||
|
folders.Add(Path.Combine(localAppData, "Microsoft", "Office", "Word", "Recent"));
|
||||||
|
folders.Add(Path.Combine(localAppData, "Microsoft", "Office", "Excel", "Recent"));
|
||||||
|
folders.Add(Path.Combine(localAppData, "Microsoft", "Office", "PowerPoint", "Recent"));
|
||||||
|
|
||||||
return folders;
|
return folders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -294,6 +294,8 @@ internal sealed class SettingsWindowService : ISettingsWindowService
|
|||||||
if (languageChanged)
|
if (languageChanged)
|
||||||
{
|
{
|
||||||
var regionState = _settingsFacade.Region.Get();
|
var regionState = _settingsFacade.Region.Get();
|
||||||
|
// 清除本地化缓存,强制重新加载语言文件
|
||||||
|
_localizationService.ClearCache();
|
||||||
_viewModel.RefreshLanguage(regionState.LanguageCode);
|
_viewModel.RefreshLanguage(regionState.LanguageCode);
|
||||||
_pageRegistry.Rebuild();
|
_pageRegistry.Rebuild();
|
||||||
_window.ReloadPages(_viewModel.CurrentPageId);
|
_window.ReloadPages(_viewModel.CurrentPageId);
|
||||||
|
|||||||
@@ -564,11 +564,19 @@ public sealed partial class PluginMarketSettingsPageViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
RefreshInstalledSnapshot();
|
RefreshInstalledSnapshot();
|
||||||
RefreshItemStates();
|
RefreshItemStates();
|
||||||
|
|
||||||
|
// 设置更明显的状态消息
|
||||||
|
var pluginName = result.PluginName ?? item.Name;
|
||||||
StatusMessage = string.Format(
|
StatusMessage = string.Format(
|
||||||
CultureInfo.CurrentCulture,
|
CultureInfo.CurrentCulture,
|
||||||
L("market.status.install_success_format", "Plugin '{0}' has been staged. Restart the app to apply it."),
|
L("market.status.install_success_restart_format", "✓ Plugin '{0}' installed successfully! Please restart the application to activate it."),
|
||||||
result.PluginName ?? item.Name);
|
pluginName);
|
||||||
RestartRequested?.Invoke(RestartRequiredMessage);
|
|
||||||
|
// 触发重启提醒
|
||||||
|
RestartRequested?.Invoke(string.Format(
|
||||||
|
CultureInfo.CurrentCulture,
|
||||||
|
L("market.dialog.restart_message_format", "Plugin '{0}' has been installed successfully.\n\nTo use this plugin, you need to restart the application now.\n\nWould you like to restart?"),
|
||||||
|
pluginName));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -268,12 +268,17 @@ public sealed partial class GeneralSettingsPageViewModel : ViewModelBase
|
|||||||
|
|
||||||
partial void OnSelectedLanguageChanged(SelectionOption value)
|
partial void OnSelectedLanguageChanged(SelectionOption value)
|
||||||
{
|
{
|
||||||
RefreshPreview();
|
|
||||||
if (_isInitializing || value is null)
|
if (_isInitializing || value is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新语言代码并刷新UI文本
|
||||||
|
_languageCode = _localizationService.NormalizeLanguageCode(value.Value);
|
||||||
|
RefreshLocalizedText();
|
||||||
|
RefreshPreview();
|
||||||
|
|
||||||
|
// 保存设置
|
||||||
_settingsFacade.Region.Save(new RegionSettingsState(
|
_settingsFacade.Region.Save(new RegionSettingsState(
|
||||||
value.Value,
|
value.Value,
|
||||||
NormalizeTimeZoneId(SelectedTimeZone?.Id)));
|
NormalizeTimeZoneId(SelectedTimeZone?.Id)));
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
<Grid Grid.Row="1">
|
<Grid Grid.Row="1">
|
||||||
<ScrollViewer x:Name="ContentScrollViewer"
|
<ScrollViewer x:Name="ContentScrollViewer"
|
||||||
HorizontalScrollBarVisibility="Disabled"
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
VerticalScrollBarVisibility="Disabled">
|
VerticalScrollBarVisibility="Auto">
|
||||||
<StackPanel x:Name="CourseListPanel" />
|
<StackPanel x:Name="CourseListPanel" />
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|
||||||
|
|||||||
@@ -198,12 +198,32 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (courseIndex < CourseListPanel.Children.Count)
|
// 确保在UI线程执行
|
||||||
|
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
var targetChild = CourseListPanel.Children[courseIndex];
|
if (courseIndex >= CourseListPanel.Children.Count)
|
||||||
var bounds = targetChild.Bounds;
|
{
|
||||||
ContentScrollViewer.Offset = new Vector(0, bounds.Position.Y);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var targetChild = CourseListPanel.Children[courseIndex];
|
||||||
|
if (targetChild == null || !targetChild.IsArrangeValid)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bounds = targetChild.Bounds;
|
||||||
|
var scrollViewerHeight = ContentScrollViewer.Bounds.Height;
|
||||||
|
var contentHeight = CourseListPanel.Bounds.Height;
|
||||||
|
|
||||||
|
// 计算滚动位置,使当前课程居中显示
|
||||||
|
var targetOffset = bounds.Position.Y - (scrollViewerHeight / 2) + (bounds.Height / 2);
|
||||||
|
|
||||||
|
// 确保不超出边界
|
||||||
|
targetOffset = Math.Max(0, Math.Min(targetOffset, contentHeight - scrollViewerHeight));
|
||||||
|
|
||||||
|
ContentScrollViewer.Offset = new Vector(0, targetOffset);
|
||||||
|
}, Avalonia.Threading.DispatcherPriority.Loaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshFromSettings()
|
public void RefreshFromSettings()
|
||||||
@@ -298,6 +318,15 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
|
|||||||
var currentIndex = FindCurrentCourseIndex();
|
var currentIndex = FindCurrentCourseIndex();
|
||||||
_lastCurrentCourseIndex = currentIndex;
|
_lastCurrentCourseIndex = currentIndex;
|
||||||
HideStatus();
|
HideStatus();
|
||||||
|
|
||||||
|
// 初始化时自动跳转到当前课程
|
||||||
|
if (currentIndex >= 0)
|
||||||
|
{
|
||||||
|
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
ScrollToCurrentCourse(currentIndex);
|
||||||
|
}, Avalonia.Threading.DispatcherPriority.Loaded);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderScheduleItems();
|
RenderScheduleItems();
|
||||||
@@ -484,10 +513,9 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
|
|||||||
: CreateBrush("#FF4D5A");
|
: CreateBrush("#FF4D5A");
|
||||||
var normalBulletBrush = CreateBrush(_isNightVisual ? "#B8BEC9" : "#9AA3B2");
|
var normalBulletBrush = CreateBrush(_isNightVisual ? "#B8BEC9" : "#9AA3B2");
|
||||||
|
|
||||||
var visibleItems = _courseItems.Take(maxVisibleItems).ToList();
|
for (var i = 0; i < _courseItems.Count; i++)
|
||||||
for (var i = 0; i < visibleItems.Count; i++)
|
|
||||||
{
|
{
|
||||||
var item = visibleItems[i];
|
var item = _courseItems[i];
|
||||||
var bulletBrush = item.IsCurrent ? currentBrush : normalBulletBrush;
|
var bulletBrush = item.IsCurrent ? currentBrush : normalBulletBrush;
|
||||||
|
|
||||||
var bullet = new Border
|
var bullet = new Border
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<Border x:Name="RootBorder"
|
<Border x:Name="RootBorder"
|
||||||
CornerRadius="34"
|
CornerRadius="34"
|
||||||
Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
|
Background="#2D5A8E"
|
||||||
ClipToBounds="True"
|
ClipToBounds="True"
|
||||||
BorderThickness="0"
|
BorderThickness="0"
|
||||||
Padding="0">
|
Padding="0">
|
||||||
@@ -23,15 +23,15 @@
|
|||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
Margin="0,-40,-40,0"
|
Margin="0,-40,-40,0"
|
||||||
CornerRadius="70"
|
CornerRadius="70"
|
||||||
Background="{DynamicResource SystemAccentColorLight2Brush}"
|
Background="#4A90D9"
|
||||||
Opacity="0.2"
|
Opacity="0.3"
|
||||||
IsHitTestVisible="False" />
|
IsHitTestVisible="False" />
|
||||||
|
|
||||||
<Grid RowDefinitions="Auto,*" RowSpacing="8" Margin="16,14,16,14">
|
<Grid RowDefinitions="Auto,*" RowSpacing="8" Margin="16,14,16,14">
|
||||||
<Grid Grid.Row="0" ColumnDefinitions="*,Auto">
|
<Grid Grid.Row="0" ColumnDefinitions="*,Auto">
|
||||||
<TextBlock x:Name="HeaderTextBlock"
|
<TextBlock x:Name="HeaderTextBlock"
|
||||||
Text="最近文档"
|
Text="最近文档"
|
||||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
Foreground="#D8FFFFFF"
|
||||||
FontSize="18"
|
FontSize="18"
|
||||||
FontWeight="SemiBold"
|
FontWeight="SemiBold"
|
||||||
VerticalAlignment="Center" />
|
VerticalAlignment="Center" />
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
PointerPressed="OnRefreshPointerPressed">
|
PointerPressed="OnRefreshPointerPressed">
|
||||||
<fi:SymbolIcon Symbol="ArrowSync"
|
<fi:SymbolIcon Symbol="ArrowSync"
|
||||||
FontSize="14"
|
FontSize="14"
|
||||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" />
|
Foreground="#B8FFFFFF" />
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
@@ -68,14 +68,14 @@
|
|||||||
Width="130"
|
Width="130"
|
||||||
Height="90"
|
Height="90"
|
||||||
CornerRadius="10"
|
CornerRadius="10"
|
||||||
Background="{DynamicResource AdaptiveGlassPanelBackgroundBrush}"
|
Background="#3AFFFFFF"
|
||||||
Padding="10"
|
Padding="10"
|
||||||
Cursor="Hand"
|
Cursor="Hand"
|
||||||
PointerPressed="OnDocumentCardPointerPressed">
|
PointerPressed="OnDocumentCardPointerPressed">
|
||||||
<Grid RowDefinitions="Auto,*,Auto">
|
<Grid RowDefinitions="Auto,*,Auto">
|
||||||
<TextBlock Grid.Row="0"
|
<TextBlock Grid.Row="0"
|
||||||
Text="{Binding FileName}"
|
Text="{Binding FileName}"
|
||||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
Foreground="#D8FFFFFF"
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
FontWeight="Medium"
|
FontWeight="Medium"
|
||||||
TextTrimming="CharacterEllipsis"
|
TextTrimming="CharacterEllipsis"
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
VerticalAlignment="Top" />
|
VerticalAlignment="Top" />
|
||||||
<TextBlock Grid.Row="2"
|
<TextBlock Grid.Row="2"
|
||||||
Text="{Binding TimeAgo}"
|
Text="{Binding TimeAgo}"
|
||||||
Foreground="{DynamicResource AdaptiveTextTertiaryBrush}"
|
Foreground="#9AFFFFFF"
|
||||||
FontSize="10"
|
FontSize="10"
|
||||||
TextTrimming="CharacterEllipsis"
|
TextTrimming="CharacterEllipsis"
|
||||||
MaxLines="1" />
|
MaxLines="1" />
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
<TextBlock x:Name="StatusTextBlock"
|
<TextBlock x:Name="StatusTextBlock"
|
||||||
IsVisible="False"
|
IsVisible="False"
|
||||||
Text="暂无最近文档"
|
Text="暂无最近文档"
|
||||||
Foreground="{DynamicResource AdaptiveTextTertiaryBrush}"
|
Foreground="#9AFFFFFF"
|
||||||
FontSize="14"
|
FontSize="14"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center" />
|
VerticalAlignment="Center" />
|
||||||
|
|||||||
@@ -964,6 +964,7 @@ public partial class MainWindow
|
|||||||
DisposeComponentIfNeeded(host);
|
DisposeComponentIfNeeded(host);
|
||||||
contentHost.Child = component;
|
contentHost.Child = component;
|
||||||
ApplyDesktopEditStateToHost(host, _isComponentLibraryOpen);
|
ApplyDesktopEditStateToHost(host, _isComponentLibraryOpen);
|
||||||
|
InvalidateDesktopPageAwareComponentContextCache();
|
||||||
UpdateDesktopPageAwareComponentContext();
|
UpdateDesktopPageAwareComponentContext();
|
||||||
if (_selectedDesktopComponentHost == host)
|
if (_selectedDesktopComponentHost == host)
|
||||||
{
|
{
|
||||||
@@ -1102,6 +1103,7 @@ public partial class MainWindow
|
|||||||
|
|
||||||
ClearTimeZoneServiceBindings(pageGrid.Children.OfType<Control>().ToList());
|
ClearTimeZoneServiceBindings(pageGrid.Children.OfType<Control>().ToList());
|
||||||
pageGrid.Children.Clear();
|
pageGrid.Children.Clear();
|
||||||
|
InvalidateDesktopPageAwareComponentContextCache();
|
||||||
|
|
||||||
var maxColumns = pageGrid.ColumnDefinitions.Count;
|
var maxColumns = pageGrid.ColumnDefinitions.Count;
|
||||||
var maxRows = pageGrid.RowDefinitions.Count;
|
var maxRows = pageGrid.RowDefinitions.Count;
|
||||||
@@ -1204,6 +1206,7 @@ public partial class MainWindow
|
|||||||
pageGrid.Children.Add(host);
|
pageGrid.Children.Add(host);
|
||||||
|
|
||||||
_desktopComponentPlacements.Add(placement);
|
_desktopComponentPlacements.Add(placement);
|
||||||
|
InvalidateDesktopPageAwareComponentContextCache();
|
||||||
UpdateDesktopPageAwareComponentContext();
|
UpdateDesktopPageAwareComponentContext();
|
||||||
PersistSettings();
|
PersistSettings();
|
||||||
|
|
||||||
@@ -1577,14 +1580,86 @@ public partial class MainWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void InvalidateDesktopPageAwareComponentContextCache()
|
||||||
|
{
|
||||||
|
_desktopPageContextInitialized = false;
|
||||||
|
_desktopPageContextActiveMask = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int BuildDesktopPageAwareComponentActiveMask()
|
||||||
|
{
|
||||||
|
if (_isSettingsOpen)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var activeMask = 0;
|
||||||
|
if (_desktopSurfacePageWidth > 1 &&
|
||||||
|
_desktopPagesHostTransform is not null &&
|
||||||
|
(_isDesktopSwipeActive ||
|
||||||
|
_desktopPageContextSettlingSourceIndex is not null ||
|
||||||
|
_desktopPageContextSettlingTargetIndex is not null))
|
||||||
|
{
|
||||||
|
var viewportLeft = -_desktopPagesHostTransform.X;
|
||||||
|
var viewportRight = viewportLeft + _desktopSurfacePageWidth;
|
||||||
|
for (var pageIndex = 0; pageIndex < _desktopPageCount; pageIndex++)
|
||||||
|
{
|
||||||
|
var pageLeft = pageIndex * _desktopSurfacePageWidth;
|
||||||
|
var pageRight = pageLeft + _desktopSurfacePageWidth;
|
||||||
|
if (pageRight > viewportLeft + 0.5d && pageLeft < viewportRight - 0.5d)
|
||||||
|
{
|
||||||
|
activeMask |= 1 << pageIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_currentDesktopSurfaceIndex >= 0 && _currentDesktopSurfaceIndex < _desktopPageCount)
|
||||||
|
{
|
||||||
|
activeMask |= 1 << _currentDesktopSurfaceIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_desktopPageContextSettlingSourceIndex is int sourceIndex &&
|
||||||
|
sourceIndex >= 0 &&
|
||||||
|
sourceIndex < _desktopPageCount)
|
||||||
|
{
|
||||||
|
activeMask |= 1 << sourceIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_desktopPageContextSettlingTargetIndex is int targetIndex &&
|
||||||
|
targetIndex >= 0 &&
|
||||||
|
targetIndex < _desktopPageCount)
|
||||||
|
{
|
||||||
|
activeMask |= 1 << targetIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return activeMask;
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateDesktopPageAwareComponentContext()
|
private void UpdateDesktopPageAwareComponentContext()
|
||||||
{
|
{
|
||||||
var activeDesktopPageIndex = _isSettingsOpen ? -1 : _currentDesktopSurfaceIndex;
|
|
||||||
var isEditMode = _isComponentLibraryOpen || _isSettingsOpen;
|
var isEditMode = _isComponentLibraryOpen || _isSettingsOpen;
|
||||||
|
var activeMask = BuildDesktopPageAwareComponentActiveMask();
|
||||||
|
var pageUpdateMask = !_desktopPageContextInitialized || isEditMode != _desktopPageContextEditMode
|
||||||
|
? _desktopPageComponentGrids.Keys.Aggregate(0, (mask, pageIndex) => mask | (1 << pageIndex))
|
||||||
|
: activeMask ^ _desktopPageContextActiveMask;
|
||||||
|
|
||||||
|
if (_desktopPageContextInitialized &&
|
||||||
|
pageUpdateMask == 0 &&
|
||||||
|
isEditMode == _desktopPageContextEditMode &&
|
||||||
|
activeMask == _desktopPageContextActiveMask)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var pair in _desktopPageComponentGrids)
|
foreach (var pair in _desktopPageComponentGrids)
|
||||||
{
|
{
|
||||||
var isOnActivePage = pair.Key == activeDesktopPageIndex;
|
var pageBit = 1 << pair.Key;
|
||||||
|
if ((pageUpdateMask & pageBit) == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var isOnActivePage = (activeMask & pageBit) != 0;
|
||||||
foreach (var host in pair.Value.Children.OfType<Border>())
|
foreach (var host in pair.Value.Children.OfType<Border>())
|
||||||
{
|
{
|
||||||
if (!host.Classes.Contains(DesktopComponentHostClass))
|
if (!host.Classes.Contains(DesktopComponentHostClass))
|
||||||
@@ -1598,6 +1673,10 @@ public partial class MainWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_desktopPageContextInitialized = true;
|
||||||
|
_desktopPageContextEditMode = isEditMode;
|
||||||
|
_desktopPageContextActiveMask = activeMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ApplyDesktopPageContext(Control root, bool isOnActivePage, bool isEditMode)
|
private static void ApplyDesktopPageContext(Control root, bool isOnActivePage, bool isEditMode)
|
||||||
@@ -2702,6 +2781,11 @@ public partial class MainWindow
|
|||||||
return Symbol.Apps;
|
return Symbol.Apps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.Equals(categoryId, "File", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return Symbol.Folder;
|
||||||
|
}
|
||||||
|
|
||||||
return Symbol.Apps;
|
return Symbol.Apps;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2747,6 +2831,11 @@ public partial class MainWindow
|
|||||||
return L("component_category.study", "Study");
|
return L("component_category.study", "Study");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.Equals(categoryId, "File", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return L("component_category.file", "File");
|
||||||
|
}
|
||||||
|
|
||||||
return categoryId;
|
return categoryId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using Avalonia.Animation;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
@@ -16,6 +17,7 @@ using Avalonia.VisualTree;
|
|||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using LanMountainDesktop.Models;
|
using LanMountainDesktop.Models;
|
||||||
using LanMountainDesktop.Services;
|
using LanMountainDesktop.Services;
|
||||||
|
using LanMountainDesktop.Theme;
|
||||||
|
|
||||||
namespace LanMountainDesktop.Views;
|
namespace LanMountainDesktop.Views;
|
||||||
|
|
||||||
@@ -54,6 +56,8 @@ public partial class MainWindow
|
|||||||
private int _currentDesktopSurfaceIndex;
|
private int _currentDesktopSurfaceIndex;
|
||||||
private double _desktopSurfacePageWidth;
|
private double _desktopSurfacePageWidth;
|
||||||
private TranslateTransform? _desktopPagesHostTransform;
|
private TranslateTransform? _desktopPagesHostTransform;
|
||||||
|
private Transitions? _desktopPagesHostSnapTransitions;
|
||||||
|
private bool _desktopPagesHostTransitionsSuspended;
|
||||||
private bool _isDesktopSwipeActive;
|
private bool _isDesktopSwipeActive;
|
||||||
private bool _isDesktopSwipeDirectionLocked;
|
private bool _isDesktopSwipeDirectionLocked;
|
||||||
private Point _desktopSwipeStartPoint;
|
private Point _desktopSwipeStartPoint;
|
||||||
@@ -62,6 +66,12 @@ public partial class MainWindow
|
|||||||
private long _desktopSwipeLastTimestamp;
|
private long _desktopSwipeLastTimestamp;
|
||||||
private double _desktopSwipeVelocityX;
|
private double _desktopSwipeVelocityX;
|
||||||
private double _desktopSwipeBaseOffset;
|
private double _desktopSwipeBaseOffset;
|
||||||
|
private bool _desktopPageContextInitialized;
|
||||||
|
private bool _desktopPageContextEditMode;
|
||||||
|
private int _desktopPageContextActiveMask;
|
||||||
|
private int? _desktopPageContextSettlingSourceIndex;
|
||||||
|
private int? _desktopPageContextSettlingTargetIndex;
|
||||||
|
private int _desktopPageContextSettleRevision;
|
||||||
|
|
||||||
private int LauncherSurfaceIndex => Math.Max(MinDesktopPageCount, _desktopPageCount);
|
private int LauncherSurfaceIndex => Math.Max(MinDesktopPageCount, _desktopPageCount);
|
||||||
|
|
||||||
@@ -164,6 +174,15 @@ public partial class MainWindow
|
|||||||
DesktopPagesHost.RenderTransform = _desktopPagesHostTransform;
|
DesktopPagesHost.RenderTransform = _desktopPagesHostTransform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_desktopPagesHostTransitionsSuspended)
|
||||||
|
{
|
||||||
|
_desktopPagesHostTransform.Transitions = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_desktopPagesHostSnapTransitions ??= _desktopPagesHostTransform.Transitions;
|
||||||
|
}
|
||||||
|
|
||||||
var viewportRow = gridMetrics.RowCount > 2 ? 1 : 0;
|
var viewportRow = gridMetrics.RowCount > 2 ? 1 : 0;
|
||||||
var viewportRowSpan = gridMetrics.RowCount > 2 ? gridMetrics.RowCount - 2 : 1;
|
var viewportRowSpan = gridMetrics.RowCount > 2 ? gridMetrics.RowCount - 2 : 1;
|
||||||
var pageWidth = Math.Max(1, gridMetrics.GridWidthPx);
|
var pageWidth = Math.Max(1, gridMetrics.GridWidthPx);
|
||||||
@@ -200,6 +219,7 @@ public partial class MainWindow
|
|||||||
DesktopPagesContainer.Width = pageWidth * _desktopPageCount;
|
DesktopPagesContainer.Width = pageWidth * _desktopPageCount;
|
||||||
DesktopPagesContainer.Height = pageHeight;
|
DesktopPagesContainer.Height = pageHeight;
|
||||||
_desktopPageComponentGrids.Clear();
|
_desktopPageComponentGrids.Clear();
|
||||||
|
InvalidateDesktopPageAwareComponentContextCache();
|
||||||
for (var index = 0; index < _desktopPageCount; index++)
|
for (var index = 0; index < _desktopPageCount; index++)
|
||||||
{
|
{
|
||||||
DesktopPagesContainer.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(pageWidth, GridUnitType.Pixel)));
|
DesktopPagesContainer.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(pageWidth, GridUnitType.Pixel)));
|
||||||
@@ -354,6 +374,88 @@ public partial class MainWindow
|
|||||||
UpdateDesktopPageAwareComponentContext();
|
UpdateDesktopPageAwareComponentContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetDesktopPagesHostSnapAnimationEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
if (_desktopPagesHostTransform is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
if (!_desktopPagesHostTransitionsSuspended)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_desktopPagesHostTransform.Transitions = _desktopPagesHostSnapTransitions;
|
||||||
|
_desktopPagesHostTransitionsSuspended = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_desktopPagesHostTransitionsSuspended)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_desktopPagesHostSnapTransitions ??= _desktopPagesHostTransform.Transitions;
|
||||||
|
_desktopPagesHostTransform.Transitions = null;
|
||||||
|
_desktopPagesHostTransitionsSuspended = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearDesktopPageContextSettle(bool refreshContext)
|
||||||
|
{
|
||||||
|
_desktopPageContextSettleRevision++;
|
||||||
|
_desktopPageContextSettlingSourceIndex = null;
|
||||||
|
_desktopPageContextSettlingTargetIndex = null;
|
||||||
|
|
||||||
|
if (refreshContext)
|
||||||
|
{
|
||||||
|
UpdateDesktopPageAwareComponentContext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BeginDesktopPageContextSettle(int previousIndex, int targetIndex)
|
||||||
|
{
|
||||||
|
var sourceIndex = previousIndex >= 0 && previousIndex < _desktopPageCount
|
||||||
|
? previousIndex
|
||||||
|
: (int?)null;
|
||||||
|
var destinationIndex = targetIndex >= 0 && targetIndex < _desktopPageCount
|
||||||
|
? targetIndex
|
||||||
|
: (int?)null;
|
||||||
|
|
||||||
|
if (sourceIndex == destinationIndex && destinationIndex is not null)
|
||||||
|
{
|
||||||
|
ClearDesktopPageContextSettle(refreshContext: false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceIndex is null && destinationIndex is null)
|
||||||
|
{
|
||||||
|
ClearDesktopPageContextSettle(refreshContext: false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_desktopPageContextSettleRevision++;
|
||||||
|
var settleRevision = _desktopPageContextSettleRevision;
|
||||||
|
_desktopPageContextSettlingSourceIndex = sourceIndex;
|
||||||
|
_desktopPageContextSettlingTargetIndex = destinationIndex;
|
||||||
|
|
||||||
|
DispatcherTimer.RunOnce(
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
if (settleRevision != _desktopPageContextSettleRevision)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_desktopPageContextSettlingSourceIndex = null;
|
||||||
|
_desktopPageContextSettlingTargetIndex = null;
|
||||||
|
UpdateDesktopPageAwareComponentContext();
|
||||||
|
},
|
||||||
|
FluttermotionToken.Page + TimeSpan.FromMilliseconds(36));
|
||||||
|
}
|
||||||
|
|
||||||
private void MoveSurfaceBy(int delta)
|
private void MoveSurfaceBy(int delta)
|
||||||
{
|
{
|
||||||
if (delta == 0)
|
if (delta == 0)
|
||||||
@@ -373,7 +475,9 @@ public partial class MainWindow
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var previousIndex = _currentDesktopSurfaceIndex;
|
||||||
_currentDesktopSurfaceIndex = target;
|
_currentDesktopSurfaceIndex = target;
|
||||||
|
BeginDesktopPageContextSettle(previousIndex, target);
|
||||||
ApplyDesktopSurfaceOffset();
|
ApplyDesktopSurfaceOffset();
|
||||||
PersistSettings();
|
PersistSettings();
|
||||||
}
|
}
|
||||||
@@ -426,6 +530,7 @@ public partial class MainWindow
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClearDesktopPageContextSettle(refreshContext: false);
|
||||||
_isDesktopSwipeActive = true;
|
_isDesktopSwipeActive = true;
|
||||||
_isDesktopSwipeDirectionLocked = false;
|
_isDesktopSwipeDirectionLocked = false;
|
||||||
_desktopSwipeStartPoint = pointerInViewport;
|
_desktopSwipeStartPoint = pointerInViewport;
|
||||||
@@ -603,6 +708,7 @@ public partial class MainWindow
|
|||||||
}
|
}
|
||||||
|
|
||||||
_isDesktopSwipeDirectionLocked = true;
|
_isDesktopSwipeDirectionLocked = true;
|
||||||
|
SetDesktopPagesHostSnapAnimationEnabled(enabled: false);
|
||||||
if (e.Pointer.Captured != DesktopPagesViewport)
|
if (e.Pointer.Captured != DesktopPagesViewport)
|
||||||
{
|
{
|
||||||
e.Pointer.Capture(DesktopPagesViewport);
|
e.Pointer.Capture(DesktopPagesViewport);
|
||||||
@@ -621,6 +727,7 @@ public partial class MainWindow
|
|||||||
}
|
}
|
||||||
|
|
||||||
_desktopPagesHostTransform.X = tentative;
|
_desktopPagesHostTransform.X = tentative;
|
||||||
|
UpdateDesktopPageAwareComponentContext();
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,6 +763,7 @@ public partial class MainWindow
|
|||||||
_desktopSwipeLastTimestamp = 0;
|
_desktopSwipeLastTimestamp = 0;
|
||||||
if (wasDirectionLocked)
|
if (wasDirectionLocked)
|
||||||
{
|
{
|
||||||
|
SetDesktopPagesHostSnapAnimationEnabled(enabled: true);
|
||||||
ApplyDesktopSurfaceOffset();
|
ApplyDesktopSurfaceOffset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -682,6 +790,8 @@ public partial class MainWindow
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SetDesktopPagesHostSnapAnimationEnabled(enabled: true);
|
||||||
|
|
||||||
var deltaX = _desktopSwipeCurrentPoint.X - _desktopSwipeStartPoint.X;
|
var deltaX = _desktopSwipeCurrentPoint.X - _desktopSwipeStartPoint.X;
|
||||||
var deltaY = _desktopSwipeCurrentPoint.Y - _desktopSwipeStartPoint.Y;
|
var deltaY = _desktopSwipeCurrentPoint.Y - _desktopSwipeStartPoint.Y;
|
||||||
var absDeltaX = Math.Abs(deltaX);
|
var absDeltaX = Math.Abs(deltaX);
|
||||||
|
|||||||
@@ -70,9 +70,11 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid ColumnDefinitions="Auto,*"
|
<Grid ColumnDefinitions="Auto,*"
|
||||||
|
RowDefinitions="Auto,Auto"
|
||||||
ColumnSpacing="20"
|
ColumnSpacing="20"
|
||||||
RowSpacing="16">
|
RowSpacing="16">
|
||||||
<StackPanel Grid.Column="0"
|
<StackPanel Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
Spacing="4">
|
Spacing="4">
|
||||||
<TextBlock Classes="update-kv-label"
|
<TextBlock Classes="update-kv-label"
|
||||||
Text="{Binding CurrentVersionLabel}" />
|
Text="{Binding CurrentVersionLabel}" />
|
||||||
@@ -80,7 +82,8 @@
|
|||||||
Text="{Binding CurrentVersionText}" />
|
Text="{Binding CurrentVersionText}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Grid.Column="1"
|
<StackPanel Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
Spacing="4"
|
Spacing="4"
|
||||||
IsVisible="{Binding IsLatestVersionVisible}">
|
IsVisible="{Binding IsLatestVersionVisible}">
|
||||||
<TextBlock Classes="update-kv-label"
|
<TextBlock Classes="update-kv-label"
|
||||||
@@ -110,22 +113,26 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<StackPanel Spacing="12">
|
<StackPanel Spacing="12"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
<TextBlock Classes="settings-item-description"
|
<TextBlock Classes="settings-item-description"
|
||||||
Text="{Binding UpdateStatus}"
|
Text="{Binding UpdateStatus}"
|
||||||
TextWrapping="Wrap"
|
TextWrapping="Wrap"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
MaxWidth="500" />
|
MaxWidth="500" />
|
||||||
|
|
||||||
<ProgressBar Minimum="0"
|
<ProgressBar Minimum="0"
|
||||||
Maximum="100"
|
Maximum="100"
|
||||||
Value="{Binding DownloadProgressValue}"
|
Value="{Binding DownloadProgressValue}"
|
||||||
IsVisible="{Binding IsDownloadProgressVisible}"
|
IsVisible="{Binding IsDownloadProgressVisible}"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
Margin="0,4,0,4" />
|
Margin="0,4,0,4" />
|
||||||
|
|
||||||
<TextBlock Classes="settings-item-description"
|
<TextBlock Classes="settings-item-description"
|
||||||
IsVisible="{Binding IsDownloadProgressVisible}"
|
IsVisible="{Binding IsDownloadProgressVisible}"
|
||||||
Text="{Binding DownloadProgressText}"
|
Text="{Binding DownloadProgressText}"
|
||||||
TextWrapping="Wrap"
|
TextWrapping="Wrap"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
Margin="0,4,0,0" />
|
Margin="0,4,0,0" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user