diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs index 7d443117..896cb4cb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs @@ -6,6 +6,7 @@ using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Imaging; using Snap.Hutao.Core.Caching; using Snap.Hutao.Extension; +using Windows.Storage; namespace Snap.Hutao.Control.Image; @@ -14,15 +15,11 @@ namespace Snap.Hutao.Control.Image; /// public class CachedImage : ImageEx { - private readonly IImageCache imageCache; - /// /// 构造一个新的缓存图像 /// public CachedImage() { - imageCache = Ioc.Default.GetRequiredService(); - IsCacheEnabled = true; EnableLazyLoading = true; } @@ -30,10 +27,15 @@ public class CachedImage : ImageEx /// protected override async Task ProvideCachedResourceAsync(Uri imageUri, CancellationToken token) { - BitmapImage? image; + IImageCache imageCache = Ioc.Default.GetRequiredService(); + try { - image = await imageCache.GetFromCacheAsync(imageUri, true); + StorageFile file = await imageCache.GetFileFromCacheAsync(imageUri); + + // check token state to determine whether the operation should be canceled. + Must.TryThrowOnCanceled(token, "Image source has changed."); + return new BitmapImage(new(file.Path)); } catch (TaskCanceledException) { @@ -43,18 +45,8 @@ public class CachedImage : ImageEx catch { // maybe the image is corrupted, remove it. - await imageCache.RemoveAsync(imageUri.Enumerate()); + await imageCache.RemoveAsync(imageUri.Enumerate()).ConfigureAwait(false); throw; } - - // check token state to determine whether the operation should be canceled. - if (token.IsCancellationRequested) - { - throw new TaskCanceledException("Image source has changed."); - } - else - { - return Must.NotNull(image!); - } } } diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/Gradient.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/Gradient.cs index 801e836e..984e40bf 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Image/Gradient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/Gradient.cs @@ -60,7 +60,7 @@ public class Gradient : Microsoft.UI.Xaml.Controls.Control { Ioc.Default .GetRequiredService() - .Error(exception, "应用渐变背景时发生异常"); + .Error(exception, "应用渐变图像时发生异常"); } private static Task GetCachedFileAsync(string url) diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/MonoChrome.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/MonoChrome.cs new file mode 100644 index 00000000..1b375540 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/MonoChrome.cs @@ -0,0 +1,149 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using CommunityToolkit.WinUI.UI.Animations; +using Microsoft.Graphics.Canvas.Effects; +using Microsoft.UI; +using Microsoft.UI.Composition; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Hosting; +using Microsoft.UI.Xaml.Media; +using Snap.Hutao.Core; +using Snap.Hutao.Core.Caching; +using Snap.Hutao.Core.Threading; +using Snap.Hutao.Extension; +using Snap.Hutao.Service.Abstraction; +using Windows.Graphics.Imaging; +using Windows.Storage; +using Windows.Storage.Streams; + +namespace Snap.Hutao.Control.Image; + +/// +/// 支持单色的图像 +/// +public class MonoChrome : Microsoft.UI.Xaml.Controls.Control +{ + private static readonly DependencyProperty SourceProperty = Property.Depend(nameof(Source), string.Empty, OnSourceChanged); + private static readonly ConcurrentCancellationTokenSource LoadingTokenSource = new(); + + private SpriteVisual? spriteVisual; + private CompositionColorBrush? backgroundBrush; + + /// + /// 构造一个新的单色图像 + /// + public MonoChrome() + { + SizeChanged += OnSizeChanged; + ActualThemeChanged += OnActualThemeChanged; + } + + /// + /// 源 + /// + public string Source + { + get => (string)GetValue(SourceProperty); + set => SetValue(SourceProperty, value); + } + + private static void OnSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs arg) + { + MonoChrome monoChrome = (MonoChrome)sender; + string url = (string)arg.NewValue; + + ILogger logger = Ioc.Default.GetRequiredService>(); + monoChrome.ApplyImageAsync(url, LoadingTokenSource.Register(monoChrome)).SafeForget(logger, OnApplyImageFailed); + } + + private static void OnApplyImageFailed(Exception exception) + { + Ioc.Default + .GetRequiredService() + .Error(exception, "应用单色背景时发生异常"); + } + + private static async Task LoadImageSurfaceAsync(StorageFile storageFile, CancellationToken token) + { + using (IRandomAccessStream imageStream = await storageFile.OpenAsync(FileAccessMode.Read).AsTask(token)) + { + BitmapDecoder decoder = await BitmapDecoder.CreateAsync(imageStream).AsTask(token); + return LoadedImageSurface.StartLoadFromStream(imageStream); + } + } + + private static Task GetCachedFileAsync(string url) + { + return Ioc.Default.GetRequiredService().GetFileFromCacheAsync(new(url)); + } + + private void OnSizeChanged(object sender, SizeChangedEventArgs e) + { + if (e.NewSize != e.PreviousSize && spriteVisual is not null) + { + UpdateVisual(spriteVisual); + } + } + + private void OnActualThemeChanged(FrameworkElement sender, object args) + { + if (backgroundBrush != null) + { + SetBackgroundColor(backgroundBrush); + } + } + + private void UpdateVisual(SpriteVisual spriteVisual) + { + if (spriteVisual is not null) + { + spriteVisual.Size = ActualSize; + } + } + + private void SetBackgroundColor(CompositionColorBrush backgroundBrush) + { + ApplicationTheme theme = ActualTheme switch + { + ElementTheme.Light => ApplicationTheme.Light, + ElementTheme.Dark => ApplicationTheme.Dark, + _ => App.Current.RequestedTheme, + }; + + backgroundBrush.Color = theme switch + { + ApplicationTheme.Light => Colors.Black, + ApplicationTheme.Dark => Colors.White, + _ => throw Must.NeverHappen(), + }; + } + + private async Task ApplyImageAsync(string url, CancellationToken token) + { + await AnimationBuilder.Create().Opacity(0d).StartAsync(this, token); + + StorageFile? storageFile = Must.NotNull((await GetCachedFileAsync(url))!); + + Compositor compositor = ElementCompositionPreview.GetElementVisual(this).Compositor; + + LoadedImageSurface imageSurface = await LoadImageSurfaceAsync(storageFile, token); + + CompositionColorBrush blackLayerBursh = compositor.CreateColorBrush(Colors.Black); + CompositionSurfaceBrush imageSurfaceBrush = compositor.CompositeSurfaceBrush(imageSurface, stretch: CompositionStretch.Uniform, vRatio: 0f); + + CompositionEffectBrush overlayBrush = compositor.CompositeBlendEffectBrush(blackLayerBursh, imageSurfaceBrush, BlendEffectMode.Overlay); + CompositionEffectBrush opacityBrush = compositor.CompositeLuminanceToAlphaEffectBrush(overlayBrush); + + backgroundBrush = compositor.CreateColorBrush(); + SetBackgroundColor(backgroundBrush); + CompositionEffectBrush alphaMaskEffectBrush = compositor.CompositeAlphaMaskEffectBrush(backgroundBrush, opacityBrush); + + spriteVisual = compositor.CompositeSpriteVisual(alphaMaskEffectBrush); + UpdateVisual(spriteVisual); + + ElementCompositionPreview.SetElementChildVisual(this, spriteVisual); + + await AnimationBuilder.Create().Opacity(1d).StartAsync(this, token); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Caching/CacheBase.cs b/src/Snap.Hutao/Snap.Hutao/Core/Caching/CacheBase.cs index 26f277de..b7add47e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Caching/CacheBase.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Caching/CacheBase.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using Snap.Hutao.Core.Logging; -using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; @@ -23,7 +22,6 @@ public abstract class CacheBase where T : class { private readonly SemaphoreSlim cacheFolderSemaphore = new(1); - private readonly ConcurrentDictionary> concurrentTasks = new(); private readonly ILogger logger; private readonly HttpClient httpClient; @@ -48,12 +46,12 @@ public abstract class CacheBase /// /// Gets or sets the life duration of every cache entry. /// - public TimeSpan CacheDuration { get; set; } + public TimeSpan CacheDuration { get; } /// /// Gets or sets the number of retries trying to ensure the file is cached. /// - public uint RetryCount { get; set; } + public uint RetryCount { get; } /// /// Clears all files in the cache @@ -76,12 +74,12 @@ public abstract class CacheBase { TimeSpan expiryDuration = duration ?? CacheDuration; - StorageFolder? folder = await GetCacheFolderAsync().ConfigureAwait(false); - IReadOnlyList? files = await folder.GetFilesAsync().AsTask().ConfigureAwait(false); + StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false); + IReadOnlyList files = await folder.GetFilesAsync().AsTask().ConfigureAwait(false); - List? filesToDelete = new(); + List filesToDelete = new(); - foreach (StorageFile? file in files) + foreach (StorageFile file in files) { if (file == null) { @@ -135,24 +133,14 @@ public abstract class CacheBase await InternalClearAsync(filesToDelete).ConfigureAwait(false); } - /// - /// Retrieves item represented by Uri from the cache. If the item is not found in the cache, it will try to downloaded and saved before returning it to the caller. - /// - /// Uri of the item. - /// Indicates whether or not exception should be thrown if item cannot be found / downloaded. - /// an instance of Generic type - public Task GetFromCacheAsync(Uri uri, bool throwOnError = false) - { - return GetItemAsync(uri, throwOnError); - } - /// /// Gets the StorageFile containing cached item for given Uri /// /// Uri of the item. /// a StorageFile - public async Task GetFileFromCacheAsync(Uri uri) + public async Task GetFileFromCacheAsync(Uri uri) { + logger.LogInformation(EventIds.FileCaching, "Begin caching for [{uri}]", uri); StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false); string fileName = GetCacheFileName(uri); @@ -161,28 +149,14 @@ public abstract class CacheBase if (item == null) { - StorageFile? baseFile = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting).AsTask().ConfigureAwait(false); + StorageFile baseFile = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting).AsTask().ConfigureAwait(false); await DownloadFileAsync(uri, baseFile).ConfigureAwait(false); item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false); } - return item as StorageFile; + return Must.NotNull((item as StorageFile)!); } - /// - /// Cache specific hooks to process items from HTTP response - /// - /// input stream - /// awaitable task - protected abstract Task InitializeTypeAsync(Stream stream); - - /// - /// Cache specific hooks to process items from HTTP response - /// - /// storage file - /// awaitable task - protected abstract Task InitializeTypeAsync(StorageFile baseFile); - /// /// Override-able method that checks whether file is valid or not. /// @@ -220,121 +194,16 @@ public abstract class CacheBase return value; } - private async Task GetItemAsync(Uri uri, bool throwOnError) + private async Task DownloadFileAsync(Uri uri, StorageFile baseFile) { - T? instance = default(T); - - string fileName = GetCacheFileName(uri); - concurrentTasks.TryGetValue(fileName, out Task? request); - - // complete previous task first - if (request != null) + using (Stream httpStream = await httpClient.GetStreamAsync(uri)) { - await request.ConfigureAwait(false); - request = null; - } - - if (request == null) - { - request = GetFromCacheOrDownloadAsync(uri, fileName); - - concurrentTasks[fileName] = request; - } - - try - { - instance = await request.ConfigureAwait(false); - } - catch (Exception ex) - { - logger.LogError(EventIds.CacheException, ex, "Exception happened when caching."); - if (throwOnError) + using (Stream fs = await baseFile.OpenStreamForWriteAsync()) { - throw; + await httpStream.CopyToAsync(fs); + await fs.FlushAsync(); } } - finally - { - concurrentTasks.TryRemove(fileName, out _); - } - - return instance; - } - - private async Task GetFromCacheOrDownloadAsync(Uri uri, string fileName) - { - T? instance = default; - - StorageFolder folder = await GetCacheFolderAsync().ConfigureAwait(false); - StorageFile? baseFile = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false) as StorageFile; - - bool downloadDataFile = baseFile == null || await IsFileOutOfDateAsync(baseFile, CacheDuration).ConfigureAwait(false); - baseFile ??= await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting).AsTask().ConfigureAwait(false); - - if (downloadDataFile) - { - uint retries = 0; - try - { - while (retries < RetryCount) - { - try - { - instance = await DownloadFileAsync(uri, baseFile).ConfigureAwait(false); - - if (instance != null) - { - break; - } - } - catch (FileNotFoundException) - { - } - - retries++; - } - } - catch (Exception) - { - await baseFile.DeleteAsync().AsTask().ConfigureAwait(false); - throw; // re-throwing the exception changes the stack trace. just throw - } - } - - if (EqualityComparer.Default.Equals(instance, default(T))) - { - instance = await InitializeTypeAsync(baseFile).ConfigureAwait(false); - } - - return instance; - } - - private async Task DownloadFileAsync(Uri uri, StorageFile baseFile) - { - T? instance = default; - - using (MemoryStream memory = new()) - { - using (Stream httpStream = await httpClient.GetStreamAsync(uri)) - { - await httpStream.CopyToAsync(memory); - await memory.FlushAsync(); - - memory.Position = 0; - - using (Stream fs = await baseFile.OpenStreamForWriteAsync()) - { - await memory.CopyToAsync(fs); - await fs.FlushAsync(); - - memory.Position = 0; - } - } - - instance = await InitializeTypeAsync(memory).ConfigureAwait(false); - } - - return instance; } [SuppressMessage("", "CA1822")] diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Caching/IImageCache.cs b/src/Snap.Hutao/Snap.Hutao/Core/Caching/IImageCache.cs index a0383a45..41c3c549 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Caching/IImageCache.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Caching/IImageCache.cs @@ -18,15 +18,7 @@ internal interface IImageCache /// /// Uri of the item. /// a StorageFile - Task GetFileFromCacheAsync(Uri uri); - - /// - /// Retrieves item represented by Uri from the cache. If the item is not found in the cache, it will try to downloaded and saved before returning it to the caller. - /// - /// Uri of the item. - /// Indicates whether or not exception should be thrown if item cannot be found / downloaded. - /// an instance of Generic type - Task GetFromCacheAsync(Uri uri, bool throwOnError = false); + Task GetFileFromCacheAsync(Uri uri); /// /// Removed items based on uri list passed @@ -34,4 +26,11 @@ internal interface IImageCache /// Enumerable uri list /// awaitable Task Task RemoveAsync(IEnumerable uriForCachedItems); + + /// + /// Removes cached files that have expired + /// + /// Optional timespan to compute whether file has expired or not. If no value is supplied, is used. + /// awaitable task + Task RemoveExpiredAsync(TimeSpan? duration = null); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs index 4a27b7d2..8dd0f2a4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs @@ -1,11 +1,8 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using CommunityToolkit.WinUI; -using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml.Media.Imaging; using System.Collections.Generic; -using System.IO; using System.Net.Http; using Windows.Storage; using Windows.Storage.FileProperties; @@ -21,10 +18,7 @@ public class ImageCache : CacheBase, IImageCache { private const string DateAccessedProperty = "System.DateAccessed"; - private readonly List extendedPropertyNames = new() - { - DateAccessedProperty, - }; + private readonly List extendedPropertyNames = new() { DateAccessedProperty }; /// /// Initializes a new instance of the class. @@ -34,48 +28,6 @@ public class ImageCache : CacheBase, IImageCache public ImageCache(ILogger logger, HttpClient httpClient) : base(logger, httpClient) { - DispatcherQueue = Program.UIDispatcherQueue; - } - - /// - /// Gets or sets which DispatcherQueue is used to dispatch UI updates. - /// - private DispatcherQueue DispatcherQueue { get; } - - /// - /// Cache specific hooks to process items from HTTP response - /// - /// input stream - /// awaitable task - protected override Task InitializeTypeAsync(Stream stream) - { - if (stream.Length == 0) - { - throw new FileNotFoundException(); - } - - return DispatcherQueue.EnqueueAsync(async () => - { - BitmapImage image = new(); - - // This action will run on the UI thread, no need to care which thread to continue with - await image.SetSourceAsync(stream.AsRandomAccessStream()).AsTask().ConfigureAwait(false); - - return image; - }); - } - - /// - /// Cache specific hooks to process items from HTTP response - /// - /// storage file - /// awaitable task - protected override async Task InitializeTypeAsync(StorageFile baseFile) - { - using (Stream stream = await baseFile.OpenStreamForReadAsync().ConfigureAwait(false)) - { - return await InitializeTypeAsync(stream).ConfigureAwait(false); - } } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/EventIds.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/EventIds.cs index 274bc70f..cae4373a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Logging/EventIds.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Logging/EventIds.cs @@ -57,6 +57,11 @@ internal static class EventIds /// public static readonly EventId MetadataFileMD5Check = 100111; + /// + /// 文件缓存 + /// + public static readonly EventId FileCaching = 100120; + // 杂项 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs b/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs index 7abe5d52..f4661e83 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs @@ -84,4 +84,20 @@ public static class Must #pragma warning restore CS8763 // 不应返回标记为 [DoesNotReturn] 的方法。 } } + + /// + /// 尝试抛出任务取消异常 + /// + /// 取消令牌 + /// 取消消息 + /// 任务被取消 + [DebuggerStepThrough] + [SuppressMessage("", "CA1068")] + public static void TryThrowOnCanceled(CancellationToken token, string message) + { + if (token.IsCancellationRequested) + { + throw new TaskCanceledException("Image source has changed."); + } + } } diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/CompositionExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Extension/CompositionExtensions.cs index a4028d3a..52a35d2b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/CompositionExtensions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/CompositionExtensions.cs @@ -54,6 +54,26 @@ internal static class CompositionExtensions return brush; } + /// + /// 创建灰阶效果画刷 + /// + /// 合成器 + /// 源 + /// 合成效果画刷 + public static CompositionEffectBrush CompositeGrayScaleEffectBrush(this Compositor compositor, CompositionBrush source) + { + GrayscaleEffect effect = new() + { + Source = new CompositionEffectSourceParameter("Source"), + }; + + CompositionEffectBrush brush = compositor.CreateEffectFactory(effect).CreateBrush(); + + brush.SetSourceParameter("Source", source); + + return brush; + } + /// /// 创建亮度转不透明度效果画刷 /// diff --git a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs index b7d5fa0c..d276af60 100644 --- a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs @@ -68,14 +68,6 @@ public sealed partial class MainWindow : Window if (!rect.Size.IsEmpty) { WINDOWPLACEMENT windowPlacement = WINDOWPLACEMENT.Create(new POINT(-1, -1), rect, ShowWindowCommand.Normal); - //WINDOWPLACEMENT windowPlacement = new() - //{ - // Length = Marshal.SizeOf(), - // MaxPosition = new POINT(-1, -1), - // NormalPosition = rect, - // ShowCmd = ShowWindowCommand.Normal, - //}; - User32.SetWindowPlacement(handle, ref windowPlacement); } diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/DescParamDescriptor.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/DescParamDescriptor.cs index ead0aad4..08ec486d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/DescParamDescriptor.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/DescParamDescriptor.cs @@ -44,20 +44,7 @@ internal class DescParamDescriptor : IValueConverter throw Must.NeverHappen(); } - private IList GetFormattedParameters(IEnumerable formats, IList param) - { - List results = new(); - foreach (DescFormat descFormat in formats) - { - string format = descFormat.Format; - string resultFormatted = Regex.Replace(format, @"{param\d+.*?}", match => EvaluateMatch(match, param)); - results.Add(resultFormatted); - } - - return results; - } - - private string EvaluateMatch(Match match, IList param) + private static string EvaluateMatch(Match match, IList param) { if (match.Success) { @@ -87,6 +74,19 @@ internal class DescParamDescriptor : IValueConverter } } + private IList GetFormattedParameters(IEnumerable formats, IList param) + { + List results = new(); + foreach (DescFormat descFormat in formats) + { + string format = descFormat.Format; + string resultFormatted = Regex.Replace(format, @"{param\d+.*?}", match => EvaluateMatch(match, param)); + results.Add(resultFormatted); + } + + return results; + } + private class DescFormat { public DescFormat(string description, string format) diff --git a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml index 8d63ca99..fe2e95b2 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml @@ -25,17 +25,17 @@ + Icon="{cwu:BitmapIcon ShowAsMonochrome=True,Source=ms-appx:///Resource/Icon/UI_BtnIcon_ActivityEntry.png}"/> + Icon="{cwu:BitmapIcon ShowAsMonochrome=True,Source=ms-appx:///Resource/Icon/UI_Icon_Achievement.png}"/> + Icon="{cwu:BitmapIcon ShowAsMonochrome=True,Source=ms-appx:///Resource/Icon/UI_BagTabIcon_Avatar.png}"/> diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml index 88fe9533..2d0f700c 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml @@ -5,7 +5,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls" - xmlns:cwum="using:CommunityToolkit.WinUI.UI.Media" xmlns:mxi="using:Microsoft.Xaml.Interactivity" xmlns:shc="using:Snap.Hutao.Control" xmlns:shcb="using:Snap.Hutao.Control.Behavior" @@ -200,12 +199,12 @@ - -