mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-23 01:44:26 +08:00
,0.6.0
重构了设置系统。解决了大量的bug,正式添加了图标。引入了遥测的同意与许可(暂无实际功能)
This commit is contained in:
72
LanMountainDesktop/Services/AppLogoService.cs
Normal file
72
LanMountainDesktop/Services/AppLogoService.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Platform;
|
||||
|
||||
namespace LanMountainDesktop.Services;
|
||||
|
||||
public enum AppLogoVariant
|
||||
{
|
||||
Auto = 0,
|
||||
Day = 1,
|
||||
Night = 2
|
||||
}
|
||||
|
||||
public interface IAppLogoService
|
||||
{
|
||||
WindowIcon CreateWindowIcon(AppLogoVariant variant = AppLogoVariant.Auto);
|
||||
WindowIcon CreateTrayIcon(AppLogoVariant variant = AppLogoVariant.Auto);
|
||||
Uri GetVectorLogoUri(AppLogoVariant variant = AppLogoVariant.Auto);
|
||||
}
|
||||
|
||||
internal sealed class AppLogoService : IAppLogoService
|
||||
{
|
||||
private static readonly Uri NightVectorLogoUri = new("avares://LanMountainDesktop/Assets/logo_nightly.svg");
|
||||
private static readonly Uri DayVectorLogoUri = new("avares://LanMountainDesktop/Assets/logo_nightly.svg");
|
||||
private static readonly Uri NightIconUri = new("avares://LanMountainDesktop/Assets/logo_nightly.ico");
|
||||
private static readonly Uri DayIconUri = new("avares://LanMountainDesktop/Assets/logo_nightly.ico");
|
||||
|
||||
public WindowIcon CreateWindowIcon(AppLogoVariant variant = AppLogoVariant.Auto) => CreateIcon(ResolveIconUri(variant));
|
||||
|
||||
public WindowIcon CreateTrayIcon(AppLogoVariant variant = AppLogoVariant.Auto) => CreateIcon(ResolveIconUri(variant));
|
||||
|
||||
public Uri GetVectorLogoUri(AppLogoVariant variant = AppLogoVariant.Auto) => ResolveVectorLogoUri(variant);
|
||||
|
||||
private static WindowIcon CreateIcon(Uri assetUri)
|
||||
{
|
||||
using var stream = AssetLoader.Open(assetUri);
|
||||
return new WindowIcon(stream);
|
||||
}
|
||||
|
||||
private static Uri ResolveIconUri(AppLogoVariant variant) => ResolveVariant(variant) switch
|
||||
{
|
||||
AppLogoVariant.Day => DayIconUri,
|
||||
_ => NightIconUri
|
||||
};
|
||||
|
||||
private static Uri ResolveVectorLogoUri(AppLogoVariant variant) => ResolveVariant(variant) switch
|
||||
{
|
||||
AppLogoVariant.Day => DayVectorLogoUri,
|
||||
_ => NightVectorLogoUri
|
||||
};
|
||||
|
||||
private static AppLogoVariant ResolveVariant(AppLogoVariant variant) => variant switch
|
||||
{
|
||||
AppLogoVariant.Day => AppLogoVariant.Day,
|
||||
AppLogoVariant.Night => AppLogoVariant.Night,
|
||||
_ => AppLogoVariant.Night
|
||||
};
|
||||
}
|
||||
|
||||
internal static class HostAppLogoProvider
|
||||
{
|
||||
private static readonly object Gate = new();
|
||||
private static IAppLogoService? _instance;
|
||||
|
||||
public static IAppLogoService GetOrCreate()
|
||||
{
|
||||
lock (Gate)
|
||||
{
|
||||
return _instance ??= new AppLogoService();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -464,6 +464,7 @@ internal sealed class MaterialSurfaceService : IMaterialSurfaceService
|
||||
internal sealed class AppearanceThemeService : IAppearanceThemeService, IDisposable
|
||||
{
|
||||
private static readonly Color DefaultAccentColor = Color.Parse("#FF3B82F6");
|
||||
private static readonly Color NeutralFallbackSeedColor = Color.Parse("#FF8A8A8A");
|
||||
private readonly ISettingsFacadeService _settingsFacade;
|
||||
private readonly ISystemWallpaperService _systemWallpaperService;
|
||||
private readonly IWindowMaterialService _windowMaterialService;
|
||||
@@ -811,7 +812,7 @@ internal sealed class AppearanceThemeService : IAppearanceThemeService, IDisposa
|
||||
|
||||
private WallpaperPaletteResolution BuildFallbackWallpaperPaletteResolution(bool nightMode, string? resolvedWallpaperPath)
|
||||
{
|
||||
var palette = _settingsFacade.Theme.BuildPalette(nightMode, null, null);
|
||||
var palette = _monetColorService.BuildPaletteFromSeedCandidates([], nightMode, NeutralFallbackSeedColor);
|
||||
return new WallpaperPaletteResolution(
|
||||
palette,
|
||||
[],
|
||||
|
||||
196
LanMountainDesktop/Services/CurrentUserProfileService.cs
Normal file
196
LanMountainDesktop/Services/CurrentUserProfileService.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Avalonia.Media.Imaging;
|
||||
|
||||
namespace LanMountainDesktop.Services;
|
||||
|
||||
public sealed record CurrentUserProfileSnapshot(
|
||||
string DisplayName,
|
||||
Bitmap? AvatarBitmap,
|
||||
string FallbackMonogram,
|
||||
bool IsPlaceholder);
|
||||
|
||||
public interface ICurrentUserProfileService
|
||||
{
|
||||
CurrentUserProfileSnapshot GetCurrentProfile();
|
||||
}
|
||||
|
||||
internal sealed class CurrentUserProfileService : ICurrentUserProfileService, IDisposable
|
||||
{
|
||||
private readonly object _gate = new();
|
||||
private CurrentUserProfileSnapshot? _cachedSnapshot;
|
||||
private Bitmap? _cachedAvatarBitmap;
|
||||
|
||||
public CurrentUserProfileSnapshot GetCurrentProfile()
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
if (_cachedSnapshot is not null)
|
||||
{
|
||||
return _cachedSnapshot;
|
||||
}
|
||||
|
||||
var displayName = ResolveDisplayName();
|
||||
_cachedAvatarBitmap = TryLoadSystemAvatarBitmap();
|
||||
_cachedSnapshot = new CurrentUserProfileSnapshot(
|
||||
displayName,
|
||||
_cachedAvatarBitmap,
|
||||
BuildMonogram(displayName),
|
||||
_cachedAvatarBitmap is null);
|
||||
return _cachedSnapshot;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
_cachedSnapshot = null;
|
||||
_cachedAvatarBitmap?.Dispose();
|
||||
_cachedAvatarBitmap = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ResolveDisplayName()
|
||||
{
|
||||
var userName = Environment.UserName?.Trim();
|
||||
return string.IsNullOrWhiteSpace(userName) ? "User" : userName;
|
||||
}
|
||||
|
||||
private static Bitmap? TryLoadSystemAvatarBitmap()
|
||||
{
|
||||
foreach (var path in EnumerateAvatarCandidates())
|
||||
{
|
||||
try
|
||||
{
|
||||
using var stream = File.OpenRead(path);
|
||||
return new Bitmap(stream);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore unreadable avatar files and continue with the next candidate.
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> EnumerateAvatarCandidates()
|
||||
{
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var path in EnumerateDirectoryCandidates(
|
||||
Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"Microsoft",
|
||||
"Windows",
|
||||
"AccountPictures")))
|
||||
{
|
||||
if (seen.Add(path))
|
||||
{
|
||||
yield return path;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var path in EnumerateDirectoryCandidates(
|
||||
Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"Microsoft",
|
||||
"Windows",
|
||||
"AccountPictures")))
|
||||
{
|
||||
if (seen.Add(path))
|
||||
{
|
||||
yield return path;
|
||||
}
|
||||
}
|
||||
|
||||
var commonPicturesDirectory = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
|
||||
"Microsoft",
|
||||
"User Account Pictures");
|
||||
|
||||
foreach (var fileName in new[]
|
||||
{
|
||||
"user-448.png",
|
||||
"user-240.png",
|
||||
"user-192.png",
|
||||
"user-96.png",
|
||||
"user-64.png",
|
||||
"user-48.png",
|
||||
"user.png"
|
||||
})
|
||||
{
|
||||
var path = Path.Combine(commonPicturesDirectory, fileName);
|
||||
if (File.Exists(path) && seen.Add(path))
|
||||
{
|
||||
yield return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> EnumerateDirectoryCandidates(string directoryPath)
|
||||
{
|
||||
if (!Directory.Exists(directoryPath))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
var files = Directory.EnumerateFiles(directoryPath)
|
||||
.Where(path =>
|
||||
{
|
||||
var extension = Path.GetExtension(path);
|
||||
return extension.Equals(".png", StringComparison.OrdinalIgnoreCase) ||
|
||||
extension.Equals(".jpg", StringComparison.OrdinalIgnoreCase) ||
|
||||
extension.Equals(".jpeg", StringComparison.OrdinalIgnoreCase) ||
|
||||
extension.Equals(".bmp", StringComparison.OrdinalIgnoreCase) ||
|
||||
extension.Equals(".webp", StringComparison.OrdinalIgnoreCase);
|
||||
})
|
||||
.Select(path => new FileInfo(path))
|
||||
.OrderByDescending(file => file.LastWriteTimeUtc)
|
||||
.ThenByDescending(file => file.Length);
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
yield return file.FullName;
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildMonogram(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return "?";
|
||||
}
|
||||
|
||||
var letters = text
|
||||
.Trim()
|
||||
.Split(' ', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(part => part[0])
|
||||
.Take(2)
|
||||
.ToArray();
|
||||
|
||||
if (letters.Length == 0)
|
||||
{
|
||||
return "?";
|
||||
}
|
||||
|
||||
return new string(letters).ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
internal static class HostCurrentUserProfileProvider
|
||||
{
|
||||
private static readonly object Gate = new();
|
||||
private static ICurrentUserProfileService? _instance;
|
||||
|
||||
public static ICurrentUserProfileService GetOrCreate()
|
||||
{
|
||||
lock (Gate)
|
||||
{
|
||||
return _instance ??= new CurrentUserProfileService();
|
||||
}
|
||||
}
|
||||
}
|
||||
69
LanMountainDesktop/Services/WallpaperImageBrushFactory.cs
Normal file
69
LanMountainDesktop/Services/WallpaperImageBrushFactory.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Imaging;
|
||||
|
||||
namespace LanMountainDesktop.Services;
|
||||
|
||||
internal static class WallpaperImageBrushFactory
|
||||
{
|
||||
internal const string Fill = "Fill";
|
||||
internal const string Fit = "Fit";
|
||||
internal const string StretchMode = "Stretch";
|
||||
internal const string Center = "Center";
|
||||
internal const string Tile = "Tile";
|
||||
|
||||
public static string NormalizePlacement(string? placement)
|
||||
{
|
||||
return placement switch
|
||||
{
|
||||
_ when string.Equals(placement, Fit, StringComparison.OrdinalIgnoreCase) => Fit,
|
||||
_ when string.Equals(placement, StretchMode, StringComparison.OrdinalIgnoreCase) => StretchMode,
|
||||
_ when string.Equals(placement, Center, StringComparison.OrdinalIgnoreCase) => Center,
|
||||
_ when string.Equals(placement, Tile, StringComparison.OrdinalIgnoreCase) => Tile,
|
||||
_ => Fill
|
||||
};
|
||||
}
|
||||
|
||||
public static ImageBrush Create(Bitmap bitmap, string? placement)
|
||||
{
|
||||
var normalizedPlacement = NormalizePlacement(placement);
|
||||
var brush = new ImageBrush(bitmap)
|
||||
{
|
||||
AlignmentX = AlignmentX.Center,
|
||||
AlignmentY = AlignmentY.Center,
|
||||
Stretch = Stretch.UniformToFill,
|
||||
TileMode = TileMode.None
|
||||
};
|
||||
|
||||
switch (normalizedPlacement)
|
||||
{
|
||||
case Fit:
|
||||
brush.Stretch = Stretch.Uniform;
|
||||
break;
|
||||
|
||||
case StretchMode:
|
||||
brush.Stretch = Stretch.Fill;
|
||||
break;
|
||||
|
||||
case Center:
|
||||
brush.Stretch = Stretch.None;
|
||||
break;
|
||||
|
||||
case Tile:
|
||||
brush.AlignmentX = AlignmentX.Left;
|
||||
brush.AlignmentY = AlignmentY.Top;
|
||||
brush.Stretch = Stretch.None;
|
||||
brush.TileMode = TileMode.Tile;
|
||||
brush.DestinationRect = new RelativeRect(
|
||||
0,
|
||||
0,
|
||||
Math.Max(1, bitmap.Size.Width),
|
||||
Math.Max(1, bitmap.Size.Height),
|
||||
RelativeUnit.Absolute);
|
||||
break;
|
||||
}
|
||||
|
||||
return brush;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user