Skip to content

Commit

Permalink
impl #1373 #1389
Browse files Browse the repository at this point in the history
  • Loading branch information
Lightczx committed Feb 21, 2024
1 parent 6a922d9 commit 30c055e
Show file tree
Hide file tree
Showing 14 changed files with 274 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ internal static class ControlAnimationConstants
/// <summary>
/// 图像淡入
/// </summary>
public static readonly TimeSpan ImageFadeIn = TimeSpan.FromSeconds(0.3);
public static readonly TimeSpan ImageScaleFadeIn = TimeSpan.FromSeconds(0.3);

/// <summary>
/// 图像淡出
/// </summary>
public static readonly TimeSpan ImageFadeOut = TimeSpan.FromSeconds(0.2);
public static readonly TimeSpan ImageScaleFadeOut = TimeSpan.FromSeconds(0.2);

public static readonly TimeSpan ImageOpacityFadeInOut = TimeSpan.FromSeconds(1);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.

using CommunityToolkit.WinUI.Behaviors;
using Microsoft.UI.Xaml;

namespace Snap.Hutao.Control.Behavior;

[DependencyProperty("Period", typeof(TimeSpan))]
[DependencyProperty("Command", typeof(ICommand))]
[DependencyProperty("CommandParameter", typeof(object))]
internal sealed partial class PeriodicInvokeCommandOrOnActualThemeChangedBehavior : BehaviorBase<FrameworkElement>, IDisposable
{
private TaskCompletionSource acutalThemeChangedTaskCompletionSource = new();
private CancellationTokenSource periodicTimerCancellationTokenSource = new();

public void Dispose()
{
periodicTimerCancellationTokenSource.Dispose();
}

protected override bool Initialize()
{
AssociatedObject.ActualThemeChanged += OnActualThemeChanged;
return true;
}

protected override void OnAssociatedObjectLoaded()
{
RunCoreAsync().SafeForget();
}

protected override bool Uninitialize()
{
AssociatedObject.ActualThemeChanged -= OnActualThemeChanged;
return true;
}

private void OnActualThemeChanged(FrameworkElement sender, object args)
{
acutalThemeChangedTaskCompletionSource.TrySetResult();
periodicTimerCancellationTokenSource.Cancel();
}

private void TryExecuteCommand()
{
if (AssociatedObject is null)
{
return;
}

if (Command is not null && Command.CanExecute(CommandParameter))
{
Command.Execute(CommandParameter);
}
}

private async ValueTask RunCoreAsync()
{
using (PeriodicTimer timer = new(Period))
{
do
{
if (!IsAttached)
{
break;
}

TryExecuteCommand();

try
{
Task nextTickTask = timer.WaitForNextTickAsync(periodicTimerCancellationTokenSource.Token).AsTask();
await Task.WhenAny(nextTickTask, acutalThemeChangedTaskCompletionSource.Task).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}

acutalThemeChangedTaskCompletionSource = new();
periodicTimerCancellationTokenSource = new();
}
while (true);
}
}
}
4 changes: 2 additions & 2 deletions src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ private async ValueTask ShowAsync(CancellationToken token)
{
await AnimationBuilder
.Create()
.Opacity(from: 0D, to: 1D, duration: ControlAnimationConstants.ImageFadeIn)
.Opacity(from: 0D, to: 1D, duration: ControlAnimationConstants.ImageScaleFadeIn)
.StartAsync(this, token)
.ConfigureAwait(true);
}
Expand All @@ -213,7 +213,7 @@ private async ValueTask HideAsync(CancellationToken token)
{
await AnimationBuilder
.Create()
.Opacity(from: 1D, to: 0D, duration: ControlAnimationConstants.ImageFadeOut)
.Opacity(from: 1D, to: 0D, duration: ControlAnimationConstants.ImageScaleFadeOut)
.StartAsync(this, token)
.ConfigureAwait(true);
}
Expand Down
10 changes: 8 additions & 2 deletions src/Snap.Hutao/Snap.Hutao/Model/CollectionsNameValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ public static List<NameValue<TEnum>> FromEnum<TEnum>()
return [.. Enum.GetValues<TEnum>().Select(x => new NameValue<TEnum>(x.ToString(), x))];
}

public static List<NameValue<TEnum>> FromEnum<TEnum>(Func<TEnum, bool> codiction)
public static List<NameValue<TEnum>> FromEnum<TEnum>(Func<TEnum, bool> condition)
where TEnum : struct, Enum
{
return [.. Enum.GetValues<TEnum>().Where(codiction).Select(x => new NameValue<TEnum>(x.ToString(), x))];
return [.. Enum.GetValues<TEnum>().Where(condition).Select(x => new NameValue<TEnum>(x.ToString(), x))];
}

public static List<NameValue<TEnum>> FromEnum<TEnum>(Func<TEnum, string> nameSelector)
where TEnum : struct, Enum
{
return [.. Enum.GetValues<TEnum>().Select(x => new NameValue<TEnum>(nameSelector(x), x))];
}

public static List<NameValue<TSource>> From<TSource>(IEnumerable<TSource> sources, Func<TSource, string> nameSelector)
Expand Down
21 changes: 21 additions & 0 deletions src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,21 @@
<data name="ServiceAvatarInfoSummaryShowcaseRefreshTimeFormat" xml:space="preserve">
<value>角色橱窗:{0:MM-dd HH:mm}</value>
</data>
<data name="ServiceBackgroundImageTypeBing" xml:space="preserve">
<value>必应每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeDaily" xml:space="preserve">
<value>胡桃每日一图</value>
</data>
<data name="ServiceBackgroundImageTypeLauncher" xml:space="preserve">
<value>官方启动器壁纸</value>
</data>
<data name="ServiceBackgroundImageTypeLocalFolder" xml:space="preserve">
<value>本地随机图片</value>
</data>
<data name="ServiceBackgroundImageTypeNone" xml:space="preserve">
<value>无背景图片</value>
</data>
<data name="ServiceCultivationProjectCurrentUserdataCourrpted" xml:space="preserve">
<value>保存养成计划状态失败</value>
</data>
Expand Down Expand Up @@ -2339,6 +2354,12 @@
<data name="ViewPageSettingBackdropMaterialHeader" xml:space="preserve">
<value>背景材质</value>
</data>
<data name="ViewPageSettingBackgroundImageDescription" xml:space="preserve">
<value>更改窗体的背景图片来源,重启胡桃以尽快生效</value>
</data>
<data name="ViewPageSettingBackgroundImageHeader" xml:space="preserve">
<value>背景图片</value>
</data>
<data name="ViewPageSettingCacheFolderDescription" xml:space="preserve">
<value>图片缓存 在此处存放</value>
</data>
Expand Down
2 changes: 2 additions & 0 deletions src/Snap.Hutao/Snap.Hutao/Service/AppOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public BackdropType BackdropType
set => SetOption(ref backdropType, SettingEntry.SystemBackdropType, value, EnumToStringOrEmpty);
}

public List<NameValue<BackgroundImageType>> BackgroundImageTypes { get; } = CollectionsNameValue.FromEnum<BackgroundImageType>(type => type.GetLocalizedDescription());

public BackgroundImageType BackgroundImageType
{
get => GetOption(ref backgroundImageType, SettingEntry.BackgroundImageType, EnumParse<BackgroundImageType>, BackgroundImageType.HutaoOfficialLauncher).Value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
using Snap.Hutao.Core;
using Snap.Hutao.Core.Caching;
using Snap.Hutao.Core.IO;
using Snap.Hutao.Service.Game.Scheme;
using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher.Content;
using Snap.Hutao.Web.Hutao.Wallpaper;
using Snap.Hutao.Web.Response;
using System.IO;
using Windows.Graphics.Imaging;
Expand All @@ -23,8 +21,9 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService
private readonly IServiceProvider serviceProvider;
private readonly RuntimeOptions runtimeOptions;
private readonly ITaskContext taskContext;
private readonly AppOptions appOptions;

private HashSet<string> backgroundPathSet;
private HashSet<string> currentBackgroundPathSet;

public async ValueTask<ValueResult<bool, BackgroundImage>> GetNextBackgroundImageAsync(BackgroundImage? previous)
{
Expand All @@ -35,7 +34,7 @@ public async ValueTask<ValueResult<bool, BackgroundImage>> GetNextBackgroundImag
return new(false, default!);
}

string path = System.Random.Shared.GetItems(backgroundSet.ToArray(), 1)[0];
string path = System.Random.Shared.GetItems([..backgroundSet], 1)[0];
backgroundSet.Remove(path);

if (string.Equals(path, previous?.Path, StringComparison.OrdinalIgnoreCase))
Expand Down Expand Up @@ -65,32 +64,47 @@ public async ValueTask<ValueResult<bool, BackgroundImage>> GetNextBackgroundImag

private async ValueTask<HashSet<string>> SkipOrInitBackgroundAsync()
{
if (backgroundPathSet is not { Count: > 0 })
switch (appOptions.BackgroundImageType)
{
string backgroundFolder = runtimeOptions.GetDataFolderBackgroundFolder();
Directory.CreateDirectory(backgroundFolder);
backgroundPathSet = Directory
.GetFiles(backgroundFolder, "*.*", SearchOption.AllDirectories)
.Where(path => AllowedFormats.Contains(Path.GetExtension(path)))
.ToHashSet();

// No image found
if (backgroundPathSet.Count <= 0)
{
ResourceClient resourceClient = serviceProvider.GetRequiredService<ResourceClient>();
string launguageCode = serviceProvider.GetRequiredService<CultureOptions>().LanguageCode;
LaunchScheme scheme = launguageCode is "zh-cn"
? KnownLaunchSchemes.Get().First(scheme => !scheme.IsOversea && scheme.IsNotCompatOnly)
: KnownLaunchSchemes.Get().First(scheme => scheme.IsOversea && scheme.IsNotCompatOnly);
Response<GameContent> response = await resourceClient.GetContentAsync(scheme, launguageCode).ConfigureAwait(false);
if (response is { Data.Advertisement.Background: string url })
case BackgroundImageType.LocalFolder:
{
ValueFile file = await serviceProvider.GetRequiredService<IImageCache>().GetFileFromCacheAsync(url.ToUri()).ConfigureAwait(false);
backgroundPathSet = [file];
if (currentBackgroundPathSet is not { Count: > 0 })
{
string backgroundFolder = runtimeOptions.GetDataFolderBackgroundFolder();
Directory.CreateDirectory(backgroundFolder);

currentBackgroundPathSet = Directory
.GetFiles(backgroundFolder, "*.*", SearchOption.AllDirectories)
.Where(path => AllowedFormats.Contains(Path.GetExtension(path)))
.ToHashSet();
}

break;
}
}

case BackgroundImageType.HutaoBing:
await SetCurrentBackgroundPathSetAsync(client => client.GetBingWallpaperAsync()).ConfigureAwait(false);
break;
case BackgroundImageType.HutaoDaily:
await SetCurrentBackgroundPathSetAsync(client => client.GetTodayWallpaperAsync()).ConfigureAwait(false);
break;
case BackgroundImageType.HutaoOfficialLauncher:
await SetCurrentBackgroundPathSetAsync(client => client.GetLauncherWallpaperAsync()).ConfigureAwait(false);
break;
}

return backgroundPathSet;
currentBackgroundPathSet ??= [];
return currentBackgroundPathSet;

async Task SetCurrentBackgroundPathSetAsync(Func<HutaoWallpaperClient, ValueTask<Response<Wallpaper>>> responseFactory)
{
HutaoWallpaperClient wallpaperClient = serviceProvider.GetRequiredService<HutaoWallpaperClient>();
Response<Wallpaper> response = await responseFactory(wallpaperClient).ConfigureAwait(false);
if (response is { Data.Url: Uri url })
{
ValueFile file = await serviceProvider.GetRequiredService<IImageCache>().GetFileFromCacheAsync(url).ConfigureAwait(false);
currentBackgroundPathSet = [file];
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@

namespace Snap.Hutao.Service.BackgroundImage;

[Localization]
internal enum BackgroundImageType
{
[LocalizationKey(nameof(SH.ServiceBackgroundImageTypeNone))]
None,

[LocalizationKey(nameof(SH.ServiceBackgroundImageTypeLocalFolder))]
LocalFolder,

[LocalizationKey(nameof(SH.ServiceBackgroundImageTypeBing))]
HutaoBing,

[LocalizationKey(nameof(SH.ServiceBackgroundImageTypeDaily))]
HutaoDaily,

[LocalizationKey(nameof(SH.ServiceBackgroundImageTypeLauncher))]
HutaoOfficialLauncher,
}
12 changes: 12 additions & 0 deletions src/Snap.Hutao/Snap.Hutao/View/MainView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,24 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shch="using:Snap.Hutao.Control.Helper"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shv="using:Snap.Hutao.View"
xmlns:shvh="using:Snap.Hutao.View.Helper"
xmlns:shvm="using:Snap.Hutao.ViewModel"
xmlns:shvp="using:Snap.Hutao.View.Page"
d:DataContext="{d:DesignInstance Type=shvm:MainViewModel}"
mc:Ignorable="d">

<mxi:Interaction.Behaviors>
<shcb:PeriodicInvokeCommandOrOnActualThemeChangedBehavior
Command="{Binding UpdateBackgroundCommand}"
CommandParameter="{x:Bind BackdroundImagePresenter}"
Period="0:5:0"/>
</mxi:Interaction.Behaviors>

<UserControl.Resources>
<Thickness x:Key="NavigationViewContentMargin">0,44,0,0</Thickness>
<Thickness x:Key="NavigationViewContentGridBorderThickness">0,1,0,0</Thickness>
Expand Down
Loading

0 comments on commit 30c055e

Please sign in to comment.