Use better binding for SettingsPage.xaml

This commit is contained in:
dylech30th 2021-07-28 12:19:34 +08:00
parent 459c8596a4
commit 2e9d94eee7
18 changed files with 373 additions and 153 deletions

View File

@ -1,6 +1,9 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Mako;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Pixeval.Events;
using Pixeval.Util;
@ -43,32 +46,23 @@ namespace Pixeval
args.Handled = true;
UncaughtExceptionHandler(args.Exception);
};
TaskScheduler.UnobservedTaskException += (_, args) =>
{
UncaughtExceptionHandler(args.Exception);
args.SetObserved();
};
AppDomain.CurrentDomain.UnhandledException += async (_, args) =>
{
if (args.ExceptionObject is Exception e)
{
UncaughtExceptionHandler(e);
}
if (args.IsTerminating)
{
await ExitWithPushedNotification();
}
UncaughtExceptionHandler(args.Exception);
};
static void UncaughtExceptionHandler(Exception e)
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
Window.DispatcherQueue.TryEnqueue(async () =>
{
await MessageDialogBuilder
.CreateAcknowledgement(Window, MiscResources.ExceptionEncountered, e.ToString()).ShowAsync();
await ExitWithPushedNotification();
});
Debugger.Break();
};
static async void UncaughtExceptionHandler(Exception e)
{
await MessageDialogBuilder
.CreateAcknowledgement(Window, MiscResources.ExceptionEncountered, e.ToString()).ShowAsync();
await ExitWithPushedNotification();
}
}

View File

@ -180,7 +180,7 @@ namespace Pixeval
ConfigurationContainer.Values[nameof(AppSetting.ExcludeTags)] = appSetting.ExcludeTags.ToJson();
ConfigurationContainer.Values[nameof(AppSetting.DisableDomainFronting)] = appSetting.DisableDomainFronting;
ConfigurationContainer.Values[nameof(AppSetting.DefaultSortOption)] = appSetting.DefaultSortOption.CastOrThrow<int>();
ConfigurationContainer.Values[nameof(AppSetting.SearchTagMatchOption)] = appSetting.SearchTagMatchOption.CastOrThrow<int>();
ConfigurationContainer.Values[nameof(AppSetting.TagMatchOption)] = appSetting.TagMatchOption.CastOrThrow<int>();
ConfigurationContainer.Values[nameof(AppSetting.PageLimitForKeywordSearch)] = appSetting.PageLimitForKeywordSearch;
ConfigurationContainer.Values[nameof(AppSetting.SearchStartingFromPageNumber)] = appSetting.SearchStartingFromPageNumber;
ConfigurationContainer.Values[nameof(AppSetting.PageLimitForSpotlight)] = appSetting.PageLimitForSpotlight;
@ -223,7 +223,7 @@ namespace Pixeval
(ConfigurationContainer.Values[nameof(AppSetting.ExcludeTags)].CastOrThrow<string>().FromJson<string[]>() ?? Array.Empty<string>()).ToObservableCollection(),
ConfigurationContainer.Values[nameof(AppSetting.DisableDomainFronting)].CastOrThrow<bool>(),
ConfigurationContainer.Values[nameof(AppSetting.DefaultSortOption)].CastOrThrow<IllustrationSortOption>(),
ConfigurationContainer.Values[nameof(AppSetting.SearchTagMatchOption)].CastOrThrow<SearchTagMatchOption>(),
ConfigurationContainer.Values[nameof(AppSetting.TagMatchOption)].CastOrThrow<SearchTagMatchOption>(),
ConfigurationContainer.Values[nameof(AppSetting.PageLimitForKeywordSearch)].CastOrThrow<int>(),
ConfigurationContainer.Values[nameof(AppSetting.SearchStartingFromPageNumber)].CastOrThrow<int>(),
ConfigurationContainer.Values[nameof(AppSetting.PageLimitForSpotlight)].CastOrThrow<int>(),

View File

@ -37,7 +37,7 @@ namespace Pixeval
/// <summary>
/// The tag match option for keyword search
/// </summary>
public SearchTagMatchOption SearchTagMatchOption { get; set; }
public SearchTagMatchOption TagMatchOption { get; set; }
/// <summary>
/// Indicates the maximum page count that are allowed to be retrieved during
@ -82,7 +82,7 @@ namespace Pixeval
ExcludeTags = excludeTags;
DisableDomainFronting = disableDomainFronting;
DefaultSortOption = defaultSortOption;
SearchTagMatchOption = searchTagMatchOption;
TagMatchOption = searchTagMatchOption;
PageLimitForKeywordSearch = pageLimitForKeywordSearch;
SearchStartingFromPageNumber = searchStartingFromPageNumber;
PageLimitForSpotlight = pageLimitForSpotlight;

View File

@ -1,7 +1,16 @@
namespace Pixeval
using Pixeval.Util;
namespace Pixeval
{
public enum ApplicationTheme
{
Dark, Light, SystemDefault
[LocalizedResource(typeof(MiscResources), nameof(MiscResources.ApplicationThemeDark))]
Dark,
[LocalizedResource(typeof(MiscResources), nameof(MiscResources.ApplicationThemeLight))]
Light,
[LocalizedResource(typeof(MiscResources), nameof(MiscResources.ApplicationThemeSystemDefault))]
SystemDefault
}
}

View File

@ -117,12 +117,39 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ApplicationThemeDark" xml:space="preserve">
<value>暗色</value>
</data>
<data name="ApplicationThemeLight" xml:space="preserve">
<value>亮色</value>
</data>
<data name="ApplicationThemeSystemDefault" xml:space="preserve">
<value>跟随系统主题</value>
</data>
<data name="CannotFindLoginProxyServerExecutable" xml:space="preserve">
<value>找不到用于登录的可执行文件,请重新打开应用或者联系开发者</value>
</data>
<data name="ExceptionEncountered" xml:space="preserve">
<value>应用程序遇到了异常,即将关闭</value>
</data>
<data name="IllustrationSortOptionPopularityDescending" xml:space="preserve">
<value>收藏数降序</value>
</data>
<data name="IllustrationSortOptionPublishDateAscending" xml:space="preserve">
<value>发布日期升序</value>
</data>
<data name="IllustrationSortOptionPublishDateDescending" xml:space="preserve">
<value>发布日期降序</value>
</data>
<data name="SearchTagMatchOptionExactMatchForTags" xml:space="preserve">
<value>与关键字完全一致</value>
</data>
<data name="SearchTagMatchOptionPartialMatchForTags" xml:space="preserve">
<value>与关键字部分一致</value>
</data>
<data name="SearchTagMatchOptionTitleAndCaption" xml:space="preserve">
<value>匹配标题与说明文</value>
</data>
<data name="SortOptionComboBox.PlaceholderText" xml:space="preserve">
<value>排序选项</value>
</data>

View File

@ -123,18 +123,9 @@
<data name="AboutSNIBypassHyperlinkButton.Content" xml:space="preserve">
<value>了解更多关于域前置直连的信息</value>
</data>
<data name="ApplicationThemeDarkRadioButton.Content" xml:space="preserve">
<value>暗色</value>
</data>
<data name="ApplicationThemeLightRadioButton.Content" xml:space="preserve">
<value>亮色</value>
</data>
<data name="ApplicationThemeOpenSystemThemeSettingHyperlinkButton.Content" xml:space="preserve">
<value>打开Windows系统主题设置</value>
</data>
<data name="ApplicationThemeSystemDefaultRadioButton.Content" xml:space="preserve">
<value>跟随系统主题</value>
</data>
<data name="ApplicationThemeTextBlock.Text" xml:space="preserve">
<value>应用程序主题(重启应用后生效)</value>
</data>

View File

@ -1,4 +1,5 @@
using Microsoft.UI.Xaml;
using System;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Navigation;
using Pixeval.Pages;

View File

@ -3,6 +3,7 @@
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:util="using:Pixeval.Util"
xmlns:viewModel="using:Pixeval.ViewModel"
d:DataContext="{d:DesignInstance viewModel:LoginPageViewModel}"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
@ -19,7 +20,7 @@
<TextBlock Margin="0,15,0,0"
HorizontalAlignment="Center"
FontSize="10"
Text="{x:Bind viewModel:LoginPageViewModel.GetLoginPhaseString(_viewModel.LoginPhase), Mode=OneWay}" />
Text="{x:Bind util:AttributeHelper.GetLocalizedResources(_viewModel.LoginPhase), Mode=OneWay}" />
</StackPanel>
</Grid>
</Page>

View File

@ -3,12 +3,13 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mako="using:Mako.Global.Enum"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:numberFormatting="using:Windows.Globalization.NumberFormatting"
xmlns:pixeval="using:Pixeval"
xmlns:system="using:System"
xmlns:ui="using:CommunityToolkit.WinUI.UI"
xmlns:util="using:Pixeval.Util"
xmlns:viewModel="using:Pixeval.ViewModel"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<!-- ReSharper disable UnusedMember.Local -->
@ -87,19 +88,16 @@
<TextBlock x:Uid="/SettingsPage/ApplicationThemeTextBlock"
Style="{StaticResource SectionHeaderTextBlockStyle}" />
<RadioButtons x:Name="ApplicationThemeRadioButtons"
ItemsSource="{x:Bind viewModel:SettingsPageViewModel.AvailableApplicationThemes}"
MaxColumns="1"
SelectedItem="{x:Bind GetApplicationThemeRadioButtonsSelectedItem()}">
<RadioButtons.Items>
<RadioButton x:Uid="/SettingsPage/ApplicationThemeSystemDefaultRadioButton"
Checked="ApplicationThemeSystemDefaultRadioButton_OnChecked"
Tag="{x:Bind pixeval:ApplicationTheme.SystemDefault}" />
<RadioButton x:Uid="/SettingsPage/ApplicationThemeLightRadioButton"
Checked="ApplicationThemeLightRadioButton_OnChecked"
Tag="{x:Bind pixeval:ApplicationTheme.Light}" />
<RadioButton x:Uid="/SettingsPage/ApplicationThemeDarkRadioButton"
Checked="ApplicationThemeDarkRadioButton_OnChecked"
Tag="{x:Bind pixeval:ApplicationTheme.Dark}" />
</RadioButtons.Items>
SelectedItem="{x:Bind _viewModel.Theme}">
<RadioButtons.ItemTemplate>
<DataTemplate x:DataType="pixeval:ApplicationTheme">
<RadioButton Checked="ApplicationThemeRadioButton_OnChecked"
Content="{x:Bind util:AttributeHelper.GetLocalizedResources((pixeval:ApplicationTheme))}"
DataContext="{x:Bind}" />
</DataTemplate>
</RadioButtons.ItemTemplate>
</RadioButtons>
<HyperlinkButton x:Uid="/SettingsPage/ApplicationThemeOpenSystemThemeSettingHyperlinkButton"
Click="ApplicationThemeOpenSystemThemeSettingHyperlinkButton_OnClick"
@ -141,16 +139,13 @@
<ComboBox x:Name="DefaultSortOptionComboBox"
x:Uid="/Misc/SortOptionComboBox"
Margin="{StaticResource SettingsEntryPadding}"
SelectedItem="{x:Bind GetDefaultSortOptionComboBoxSelectedItem()}"
SelectionChanged="DefaultSortOptionComboBox_OnSelectionChanged">
<ComboBox.Items>
<ComboBoxItem x:Uid="/Misc/SortOptionComboBoxPublishDateDescendingComboBoxItem"
Tag="{x:Bind mako:IllustrationSortOption.PublishDateDescending}" />
<ComboBoxItem x:Uid="/Misc/SortOptionComboBoxPublishDateAscendingComboBoxItem"
Tag="{x:Bind mako:IllustrationSortOption.PublishDateAscending}" />
<ComboBoxItem x:Uid="/Misc/SortOptionComboBoxBookmarkDescendingComboBoxItem"
Tag="{x:Bind mako:IllustrationSortOption.PopularityDescending}" />
</ComboBox.Items>
ItemsSource="{x:Bind viewModel:SettingsPageViewModel.AvailableIllustrationSortOptions}"
SelectedItem="{x:Bind _viewModel.BoxSortOption(), BindBack=_viewModel.UnboxSortOption, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="viewModel:IllustrationSortOptionWrapper">
<TextBlock Text="{x:Bind LocalizedString}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<InfoBar x:Uid="/SettingsPage/DefaultSortOptionInfoBar"
Style="{StaticResource SectionInfoBarStyle}" />
@ -160,16 +155,13 @@
<ComboBox x:Name="SearchKeywordMatchOptionComboBox"
x:Uid="/SettingsPage/SearchKeywordMatchOptionComboBox"
Margin="{StaticResource SettingsEntryPadding}"
SelectedItem="{x:Bind GetSearchKeywordMatchOptionComboBoxSelectedItem()}"
SelectionChanged="SearchKeywordMatchOptionComboBox_OnSelectionChanged">
<ComboBox.Items>
<ComboBoxItem x:Uid="/SettingsPage/SearchKeywordMatchOptionComboBoxPartialMatchComboBoxItem"
Tag="{x:Bind mako:SearchTagMatchOption.PartialMatchForTags}" />
<ComboBoxItem x:Uid="/SettingsPage/SearchKeywordMatchOptionComboBoxExactMatchComboBoxItem"
Tag="{x:Bind mako:SearchTagMatchOption.ExactMatchForTags}" />
<ComboBoxItem x:Uid="/SettingsPage/SearchKeywordMatchOptionComboBoxTitleAndCaptionMatchComboBoxItem"
Tag="{x:Bind mako:SearchTagMatchOption.TitleAndCaption}" />
</ComboBox.Items>
ItemsSource="{x:Bind viewModel:SettingsPageViewModel.AvailableSearchTagMatchOption}"
SelectedItem="{x:Bind _viewModel.BoxSearchTagMatchOption(), BindBack=_viewModel.UnboxSearchTagMatchOption, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="viewModel:SearchTagMatchOptionWrapper">
<TextBlock Text="{x:Bind LocalizedString}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<InfoBar x:Uid="/SettingsPage/SearchKeywordMatchOptionInfoBar"
Style="{StaticResource SectionInfoBarStyle}" />

View File

@ -5,7 +5,6 @@ using System;
using System.Linq;
using Windows.System;
using CommunityToolkit.WinUI.UI.Controls;
using Mako.Global.Enum;
using Pixeval.Util;
using Pixeval.ViewModel;
@ -76,23 +75,6 @@ namespace Pixeval.Pages
await Launcher.LaunchUriAsync(new Uri("ms-settings:themes"));
}
// The RadioButtons.SelectionChanged always returns null, so we have to handle them separately
// See issue https://github.com/microsoft/microsoft-ui-xaml/issues/3268
private void ApplicationThemeSystemDefaultRadioButton_OnChecked(object sender, RoutedEventArgs e)
{
_viewModel.Theme = ApplicationTheme.SystemDefault;
}
private void ApplicationThemeLightRadioButton_OnChecked(object sender, RoutedEventArgs e)
{
_viewModel.Theme = ApplicationTheme.Light;
}
private void ApplicationThemeDarkRadioButton_OnChecked(object sender, RoutedEventArgs e)
{
_viewModel.Theme = ApplicationTheme.Dark;
}
#endregion
#region Sensitive Tags
@ -163,33 +145,15 @@ namespace Pixeval.Pages
await Launcher.LaunchUriAsync(new Uri("mailto:decem0730@hotmail.com"));
}
private void DefaultSortOptionComboBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
// We cannot use RadioButtons.SelectionChanged since it always returns null
// see https://github.com/microsoft/microsoft-ui-xaml/issues/3268
private void ApplicationThemeRadioButton_OnChecked(object sender, RoutedEventArgs e)
{
_viewModel.DefaultSortOption = (IllustrationSortOption) ((ComboBoxItem) DefaultSortOptionComboBox.SelectedItem).Tag;
}
private void SearchKeywordMatchOptionComboBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
_viewModel.SearchTagMatchOption = (SearchTagMatchOption) ((ComboBoxItem) SearchKeywordMatchOptionComboBox.SelectedItem).Tag;
_viewModel.Theme = sender.GetDataContext<ApplicationTheme>();
}
#region Helper Functions
private object GetApplicationThemeRadioButtonsSelectedItem()
{
return ApplicationThemeRadioButtons.Items.Cast<RadioButton>().First(i => i.Tag.Equals(_viewModel.Theme));
}
private object GetDefaultSortOptionComboBoxSelectedItem()
{
return DefaultSortOptionComboBox.Items.Cast<ComboBoxItem>().First(i => i.Tag.Equals(_viewModel.DefaultSortOption));
}
private object GetSearchKeywordMatchOptionComboBoxSelectedItem()
{
return SearchKeywordMatchOptionComboBox.Items.Cast<ComboBoxItem>().First(i => i.Tag.Equals(_viewModel.SearchTagMatchOption));
}
// TODO: use attached property
private static void NumberBoxCoerceValueInAndShowTeachingTip(object sender, TeachingTip teachingTip, double startInclusive, double endInclusive)
{

View File

@ -0,0 +1,142 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows10.0.19041</TargetFramework>
<TargetPlatformMinVersion>10.0.18362.0</TargetPlatformMinVersion>
<RootNamespace>Pixeval</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Platforms>x86;x64;arm64</Platforms>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<Nullable>enable</Nullable>
<DefaultLanguage>zh-CN</DefaultLanguage>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DefineConstants></DefineConstants>
</PropertyGroup>
<ItemGroup>
<Content Remove="Assets\Images\logo-no-caption.png" />
<Content Remove="Assets\Strings\zh-CN\BookmarksPage.resw" />
<Content Remove="Assets\Strings\zh-CN\IllustrationGridCommandBar.resw" />
<Content Remove="Assets\Strings\zh-CN\RecommendsPage.resw" />
<Content Remove="Assets\Strings\zh-CN\SettingsPage.resw" />
</ItemGroup>
<ItemGroup>
<None Remove="Assets\Certs\pixeval_server_cert.pfx" />
<None Remove="Assets\Strings\zh-CN\BookmarksPage.resw" />
<None Remove="Pages\BookMarksPage.xaml" />
<None Remove="Pages\DownloadListPage.xaml" />
<None Remove="Pages\IllustrationGridPage.xaml" />
<None Remove="Pages\IllustrationViewerPage.xaml" />
<None Remove="Pages\RecommendsPage.xaml" />
<None Remove="Pages\SettingsPage.xaml" />
<None Remove="UserControls\IllustrationGridCommandBar.xaml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.WinUI.UI.Animations" Version="7.0.3" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.0.3" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Media" Version="7.0.3" />
<PackageReference Include="Mako" Version="1.0.11" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.1.3" />
<PackageReference Include="Microsoft.ProjectReunion" Version="0.8.1" />
<PackageReference Include="Microsoft.ProjectReunion.Foundation" Version="0.8.1" />
<PackageReference Include="Microsoft.ProjectReunion.WinUI" Version="0.8.1" />
<PackageReference Include="Microsoft.Toolkit.Mvvm" Version="7.0.2" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.0.2" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.4" />
<PackageReference Include="PInvoke.User32" Version="0.7.104" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
<PackageReference Include="System.Linq.Async" Version="5.0.0" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<!-- region Exposes PRI resources to the source generator -->
<Target Name="InjectAdditionalFiles" BeforeTargets="GenerateMSBuildEditorConfigFileShouldRun">
<ItemGroup>
<AdditionalFiles Include="@(PRIResource)" SourceItemGroup="PRIResource" />
</ItemGroup>
</Target>
<ItemGroup>
<CompilerVisibleItemMetadata Include="AdditionalFiles" MetadataName="SourceItemGroup" />
</ItemGroup>
<!-- endregion Exposes PRI resources to the source generator -->
<ItemGroup>
<ProjectReference Include="..\Pixeval.SourceGen\Pixeval.SourceGen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>
<Page Update="Pages\SettingsPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Pages\DownloadListPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Pages\IllustrationViewerPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="UserControls\IllustrationGridCommandBar.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Pages\RecommendsPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="UserControls\IllustrationGrid.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Pages\MessageDialogContent.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Pages\MainPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Pages\LoginPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Folder Include="Assets\Binary\" />
</ItemGroup>
<ItemGroup>
<PRIResource Include="Assets\Strings\zh-CN\BookmarksPage.resw" />
</ItemGroup>
<ItemGroup>
<Page Update="Pages\BookMarksPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
</Project>

View File

@ -12,6 +12,9 @@
<DefaultLanguage>zh-CN</DefaultLanguage>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DefineConstants>TRACE;DEBUG</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Content Remove="Assets\Images\logo-no-caption.png" />
<Content Remove="Assets\Strings\zh-CN\BookmarksPage.resw" />
@ -35,7 +38,7 @@
<PackageReference Include="CommunityToolkit.WinUI.UI.Animations" Version="7.0.3" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.0.3" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Media" Version="7.0.3" />
<PackageReference Include="Mako" Version="1.0.10" />
<PackageReference Include="Mako" Version="1.0.11" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.1.3" />
<PackageReference Include="Microsoft.ProjectReunion" Version="0.8.1" />
<PackageReference Include="Microsoft.ProjectReunion.Foundation" Version="0.8.1" />

View File

@ -0,0 +1,49 @@
using System;
using System.Reflection;
namespace Pixeval.Util
{
[AttributeUsage(AttributeTargets.Field)]
public class Metadata : Attribute
{
public Metadata(string key)
{
Key = key;
}
public string Key { get; }
}
[AttributeUsage(AttributeTargets.Field)]
public class LocalizedResource : Attribute
{
public Type ResourceLoader { get; set; }
public string Key { get; set; }
public LocalizedResource(Type resourceLoader, string key)
{
ResourceLoader = resourceLoader;
Key = key;
}
}
public static class AttributeHelper
{
public static TAttribute? GetCustomAttribute<TAttribute>(this Enum e) where TAttribute : Attribute
{
return e.GetType().GetField(e.ToString())?.GetCustomAttribute(typeof(TAttribute), false) as TAttribute;
}
public static string? GetMetadataOnEnumMember(this Enum e)
{
return e.GetCustomAttribute<Metadata>()?.Key;
}
public static string? GetLocalizedResources(this Enum e)
{
var attribute = e.GetCustomAttribute<LocalizedResource>();
return attribute?.ResourceLoader?.GetField(attribute?.Key ?? string.Empty)?.GetValue(null) as string;
}
}
}

View File

@ -1,24 +0,0 @@
using System;
using System.Reflection;
namespace Pixeval.Util
{
[AttributeUsage(AttributeTargets.Field)]
public class Metadata : Attribute
{
public Metadata(string key)
{
Key = key;
}
public string Key { get; }
}
public static class MetadataHelper
{
public static string? GetMetadataOnEnumMember(this Enum e)
{
return (e.GetType().GetField(e.ToString())?.GetCustomAttribute(typeof(Metadata), false) as Metadata)?.Key;
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
@ -58,5 +59,10 @@ namespace Pixeval.Util
var (startInclusive, endInclusive) = range;
return Math.Max(startInclusive, Math.Min(i, endInclusive));
}
public static IEnumerable<TEnum> GetEnumValues<TEnum>(this Type type)
{
return type.GetEnumValues().Cast<TEnum>();
}
}
}

View File

@ -39,12 +39,12 @@ namespace Pixeval.Util
}
}
public static T? GetDataContext<T>(this FrameworkElement element) where T : class
public static T GetDataContext<T>(this FrameworkElement element)
{
return element.DataContext as T;
return (T) element.DataContext;
}
public static T? GetDataContext<T>(this object element) where T : class
public static T GetDataContext<T>(this object element)
{
return ((FrameworkElement) element).GetDataContext<T>(); // direct cast will throw exception if the type check failed, and that's exactly what we want
}

View File

@ -54,25 +54,25 @@ namespace Pixeval.ViewModel
public enum LoginPhaseEnum
{
[Metadata(nameof(LoginPageResources.LoginPhaseCheckingRefreshAvailable))]
[LocalizedResource(typeof(LoginPageResources), nameof(LoginPageResources.LoginPhaseCheckingRefreshAvailable))]
CheckingRefreshAvailable,
[Metadata(nameof(LoginPageResources.LoginPhaseRefreshing))]
[LocalizedResource(typeof(LoginPageResources), nameof(LoginPageResources.LoginPhaseRefreshing))]
Refreshing,
[Metadata(nameof(LoginPageResources.LoginPhaseNegotiatingPort))]
[LocalizedResource(typeof(LoginPageResources), nameof(LoginPageResources.LoginPhaseNegotiatingPort))]
NegotiatingPort,
[Metadata(nameof(LoginPageResources.LoginPhaseExecutingLoginProxy))]
[LocalizedResource(typeof(LoginPageResources), nameof(LoginPageResources.LoginPhaseExecutingLoginProxy))]
ExecutingLoginProxy,
[Metadata(nameof(LoginPageResources.LoginPhaseCheckingCertificateInstallation))]
[LocalizedResource(typeof(LoginPageResources), nameof(LoginPageResources.LoginPhaseCheckingCertificateInstallation))]
CheckingCertificateInstallation,
[Metadata(nameof(LoginPageResources.LoginPhaseInstallingCertificate))]
[LocalizedResource(typeof(LoginPageResources), nameof(LoginPageResources.LoginPhaseInstallingCertificate))]
InstallingCertificate,
[Metadata(nameof(LoginPageResources.LoginPhaseCheckingWebView2Installation))]
[LocalizedResource(typeof(LoginPageResources), nameof(LoginPageResources.LoginPhaseCheckingWebView2Installation))]
CheckingWebView2Installation
}
@ -94,11 +94,6 @@ namespace Pixeval.ViewModel
LoginPhase = newPhase;
}
public static string GetLoginPhaseString(LoginPhaseEnum loginPhase)
{
return (string) typeof(LoginPageResources).GetField(loginPhase.GetMetadataOnEnumMember()!)?.GetValue(null)!;
}
public bool CheckWebView2Installation()
{
AdvancePhase(LoginPhaseEnum.CheckingWebView2Installation);

View File

@ -1,12 +1,39 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Mako.Global.Enum;
using Microsoft.Toolkit.Mvvm.ComponentModel;
using Pixeval.Util;
namespace Pixeval.ViewModel
{
public record IllustrationSortOptionWrapper
{
public IllustrationSortOption Option { get; }
public string LocalizedString { get; }
public IllustrationSortOptionWrapper(IllustrationSortOption option, string localizedString)
{
Option = option;
LocalizedString = localizedString;
}
}
public record SearchTagMatchOptionWrapper
{
public SearchTagMatchOption Option { get; }
public string LocalizedString { get; }
public SearchTagMatchOptionWrapper(SearchTagMatchOption option, string localizedString)
{
Option = option;
LocalizedString = localizedString;
}
}
public class SettingsPageViewModel : ObservableObject
{
private readonly AppSetting _appSetting;
@ -16,6 +43,49 @@ namespace Pixeval.ViewModel
_appSetting = appSetting;
}
public static readonly IEnumerable<ApplicationTheme> AvailableApplicationThemes = typeof(ApplicationTheme).GetEnumValues<ApplicationTheme>();
/// <summary>
/// The <see cref="IllustrationSortOption"/> is welded into Mako, we cannot add <see cref="LocalizedResource"/> on its fields
/// so an alternative way is chosen
/// </summary>
public static readonly IEnumerable<IllustrationSortOptionWrapper> AvailableIllustrationSortOptions = new IllustrationSortOptionWrapper[]
{
new(IllustrationSortOption.PublishDateDescending, MiscResources.IllustrationSortOptionPublishDateDescending),
new(IllustrationSortOption.PublishDateAscending, MiscResources.IllustrationSortOptionPublishDateAscending),
new(IllustrationSortOption.PopularityDescending, MiscResources.IllustrationSortOptionPopularityDescending)
};
/// <summary>
/// The same reason as <see cref="AvailableIllustrationSortOptions"/>
/// </summary>
public static readonly IEnumerable<SearchTagMatchOptionWrapper> AvailableSearchTagMatchOption = new SearchTagMatchOptionWrapper[]
{
new(SearchTagMatchOption.PartialMatchForTags, MiscResources.SearchTagMatchOptionPartialMatchForTags),
new(SearchTagMatchOption.ExactMatchForTags, MiscResources.SearchTagMatchOptionExactMatchForTags),
new(SearchTagMatchOption.TitleAndCaption, MiscResources.SearchTagMatchOptionTitleAndCaption)
};
public IllustrationSortOptionWrapper BoxSortOption()
{
return AvailableIllustrationSortOptions.First(s => s.Option == DefaultSortOption);
}
public void UnboxSortOption(object wrapper)
{
DefaultSortOption = ((IllustrationSortOptionWrapper) wrapper).Option;
}
public SearchTagMatchOptionWrapper BoxSearchTagMatchOption()
{
return AvailableSearchTagMatchOption.First(s => s.Option == TagMatchOption);
}
public void UnboxSearchTagMatchOption(object wrapper)
{
TagMatchOption = ((SearchTagMatchOptionWrapper) wrapper).Option;
}
public ApplicationTheme Theme
{
get => _appSetting.Theme;
@ -44,10 +114,10 @@ namespace Pixeval.ViewModel
set => SetProperty(_appSetting.DefaultSortOption, value, _appSetting, (setting, value) => setting.DefaultSortOption = value);
}
public SearchTagMatchOption SearchTagMatchOption
public SearchTagMatchOption TagMatchOption
{
get => _appSetting.SearchTagMatchOption;
set => SetProperty(_appSetting.SearchTagMatchOption, value, _appSetting, (setting, value) => setting.SearchTagMatchOption = value);
get => _appSetting.TagMatchOption;
set => SetProperty(_appSetting.TagMatchOption, value, _appSetting, (setting, value) => setting.TagMatchOption = value);
}
public int PageLimitForKeywordSearch