Compare commits

..
Author SHA1 Message Date
Aigio Liu dfb64175de 🚧 Misc 2023-08-29 17:30:43 +08:00
Aigio Liu 15dede2c6b 🐛 NRE CompiledAvaloniaXaml.XamlIlHelpers.BD.WTTS.Settings.SettingsStructProperty`2,BD.WTTS.Client.Value_296!Setter(Object , Object )
static void BD.WTTS.Settings.SettingsStructProperty`2,BD.WTTS.Client.Value_296!Setter(object P_0, object P_1)
 {
     ((SettingsStructProperty<UpdateChannelType, GeneralSettings_>)P_0).Value = (UpdateChannelType)P_1;
 }
2023-08-29 15:48:57 +08:00
Aigio Liu 65c647d704 🐛 菜单排序 2023-08-29 14:23:48 +08:00
ff m 76ce429aa9 🐛 更新脚本 自动检测 jq 工具依赖安装
🐛 修复令牌下移判断 Count 问题
2023-08-29 13:57:50 +08:00
RMBGAME b82bada2e9 🐛 刷新限制 2023-08-29 08:40:00 +08:00
RMBGAME e7dbf43c48 ⬆️ Update Submodules 2023-08-29 07:39:38 +08:00
RMBGAME 9f981c2ef5 🐛 首次通知不显示修复 2023-08-29 07:39:38 +08:00
RMBGAME 89e957be3a 🐛 玄学的 ItemsRepeater 虚拟化 Bug 修复 2023-08-29 07:39:38 +08:00
RMBGAME 5e508aa9d2 🐛 Fixed Bugs 2023-08-29 07:39:38 +08:00
RMBGAME 3d261c3603 🚧 Test Fixed 2023-08-29 07:39:38 +08:00
ff m d4bd07364a 🐛 更新子模块 2023-08-29 02:01:21 +08:00
ff m 25f6f108c6 🐛修复保存脚本状态 SaveScriptEnableAsync 中使用 !item.Disable Mapper 与旧 sqlite 反向映射 2023-08-29 01:49:36 +08:00
ff m e908592859 🐛 不使用 *.steampp.net 避免 系统代理模式脚本失效 2023-08-29 01:29:09 +08:00
Aigio Liu 3464437385 🐛 AvaloniaFilePickerPlatformService 2023-08-29 00:23:09 +08:00
Aigio Liu a06002ee72 🐛 AvaloniaFilePickerFileType Convert 2023-08-28 23:36:19 +08:00
Aigio Liu d6b01686f2 🐛 FilePicker 2023-08-28 23:22:41 +08:00
Aigio Liu a0706b9015 🐛 FilePicker 2023-08-28 23:11:52 +08:00
ff m 1d5e64498f 🎨 优化脚本 启动程序退出顺序 2023-08-28 22:57:04 +08:00
Aigio Liu bb2bb9fc26 🐛 FilePicker2 2023-08-28 22:14:21 +08:00
ff m f76005378c misc 2023-08-28 20:52:08 +08:00
RMBGAME f0a87611af 🐛 Fixed Bugs 2023-08-28 20:27:44 +08:00
ff m 0feeeeddd1 修复 脚本 下载地址等保存 Sqlite 2023-08-28 20:16:04 +08:00
Aigio Liu 0ddef23c2f 🚧 Misc 2023-08-28 20:07:25 +08:00
john.jiang 623f892810 🎨 Idle Fixs 2023-08-28 17:36:55 +08:00
RMBGAME b29d826c1e 🐛 Fix CI 2023-08-28 07:56:02 +08:00
RMBGAME cae9f82372 🐛 Misc 2023-08-28 06:36:44 +08:00
ff m 33f780d6fb 修改 Windows Linux macOS 系统代理忽略 地址 2023-08-28 04:25:32 +08:00
ff m 36740cd639 更换清晰的Logo 2023-08-28 01:51:53 +08:00
RMBGAME b9ba4a8239 🐛 登录成功处理 2023-08-28 00:46:09 +08:00
Aigio Liu c4d9622ef0 🚧 3.0.0-preview.3 2023-08-27 23:58:56 +08:00
93 changed files with 982 additions and 585 deletions
+3 -3
View File
@@ -54,9 +54,9 @@ See details [./doc/download-guide.en.md](./doc/download-guide.en.md)
- Windows 11
- Windows 10 version 1809(OS internal version 17763) or higher is required
- macOS 10.15 or higher is required
- Ubuntu 18.04 or higher is required
- Debian 10 or higher is required
- CentOS 7 or higher is required
- Ubuntu 20.04 or higher is required
- Debian 11 or higher is required
- Fedora 37 or higher is required
- Deepin(UOS) 20 or higher is required
- ~~iOS 11 or higher is required~~(In development…)
- Android 5.0(API 21) or higher is required
+3 -3
View File
@@ -64,9 +64,9 @@
- Windows 11
- Windows 10 版本 1809(OS 内部版本 17763)或更高版本
- macOS 10.15 或更高版本
- Ubuntu 18.04 或更高版本
- Debian 10 或更高版本
- CentOS 7 或更高版本
- Ubuntu 20.04 或更高版本
- Debian 11 或更高版本
- Fedora 37 或更高版本
- Deepin(UOS) 20 或更高版本
- ~~iOS 11 或更高版本~~(开发中…)
- Android 5.0(API 21) 或更高版本
+31 -3
View File
@@ -40,6 +40,33 @@ Install_certutil() {
echo "certutil 工具已安装。"
fi
}
Install_jq() {
# Check if jq is already installed
if command -v jq &>/dev/null; then
echo "jq 工具已安装。"
else
echo "jq 用来解析版本更新。"
# Check the package manager
if command -v apt &>/dev/null; then
# Using apt (Debian/Ubuntu)
sudo apt update
sudo apt install -y jq
elif command -v dnf &>/dev/null; then
# Using dnf (Fedora)
sudo dnf install -y jq
elif command -v yum &>/dev/null; then
# Using yum (CentOS/Red Hat)
sudo yum install -y jq
elif command -v pacman &>/dev/null; then
# Using pacman (Arch Linux)
sudo pacman -S jq
else
echo "请手动安装 jq 工具。"
exit 1
fi
echo "请手动安装 jq 工具。"
fi
}
#精简版系统可能没有该工具
Install_zenity() {
# 判断发行版类型
@@ -80,10 +107,10 @@ Show_Run() {
response=$?
if [ $response -eq 0 ]; then
# 用户点击了 "运行" 按钮,启动程序
/bin/sh -c "$base_path/$exec_name.sh" &
echo "程序已启动。"
exit 0
exit 0 &
# 用户点击了 "运行" 按钮,启动程序
/bin/sh -c "$base_path/$exec_name.sh"
else
# 用户点击了 "关闭" 按钮,退出脚本
exit 0
@@ -266,6 +293,7 @@ exit" >"$base_path/$exec_name.sh"
#先安装依赖;
Install_certutil
Install_zenity
Install_jq
#版本检查更新;
Get_NewVer
Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 895 B

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 867 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

+1 -1
View File
@@ -14,7 +14,7 @@ public static partial class AssemblyInfo
/// <summary>
/// 预览版本号,范围 1~9
/// </summary>
const string ver_for_preview = "2";
const string ver_for_preview = "3";
/// <summary>
/// RC 版本号,范围 0~9
Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 628 B

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -2,15 +2,18 @@ namespace BD.WTTS.UI;
public static class ControlExtensions
{
public static void SetViewModel<T>(this Control c) where T : ViewModelBase
public static void SetViewModel<T>(this Control c, bool useCache = true) where T : ViewModelBase
{
if (c.DataContext == null)
if (c.DataContext == null || c.DataContext.GetType() != typeof(T))
{
Task2.InBackground(() =>
{
var cx = IViewModelManager.Instance.Get<T>();
Dispatcher.UIThread.Post(() => c.DataContext = cx);
});
T cx;
if (useCache)
cx = IViewModelManager.Instance.Get<T>();
else
cx = Activator.CreateInstance<T>();
c.DataContext = cx;
}
}
}
@@ -113,10 +113,12 @@ sealed class AvaloniaWindowManagerImpl : IWindowManagerImpl
if (viewModel != null)
{
if (viewModel is IWindowViewModel window)
{
window.Close = () => td?.Hide(TaskDialogStandardResult.Close);
}
td.DataContext = viewModel;
td.Content = pageContent ?? INavigationService.Instance.GetViewModelToPageContent(viewModel);
}
@@ -1,7 +1,7 @@
#if IOS || MACOS || MACCATALYST
using MobileCoreServices;
#endif
using AvaloniaFilePickerFileType = Avalonia.Platform.Storage.FilePickerFileType;
using APS_FilePickerFileType = Avalonia.Platform.Storage.FilePickerFileType;
using BaseService = BD.Common.Services.IFilePickerPlatformService;
using FilePickerFileType = BD.Common.Models.FilePickerFileType;
using IOpenFileDialogService = BD.Common.Services.IFilePickerPlatformService.IOpenFileDialogService;
@@ -19,77 +19,188 @@ sealed class AvaloniaFilePickerPlatformService : BaseService, IServiceBase, IOpe
// https://github.com/xamarin/Essentials/blob/1.7.3/Xamarin.Essentials/FilePicker/FilePicker.uwp.cs
// https://github.com/xamarin/Essentials/blob/1.7.3/Xamarin.Essentials/FileSystem/FileSystem.shared.cs
IFilePickerFileType IInternalFilePickerPlatformService.Images =>
#if IOS || MACOS || MACCATALYST
FilePickerFileType.Parse(new string[] {
UTType.PNG, UTType.JPEG, "jpeg",
});
#else
FilePickerFileType.Parse(FileEx.Images);
#endif
sealed class FilePickerFileTypeWrapper : IFilePickerFileType
{
public required IReadOnlyList<APS_FilePickerFileType> Values { get; init; }
IFilePickerFileType IInternalFilePickerPlatformService.Png => FilePickerFileType.Parse(new string[] {
#if IOS || MACOS || MACCATALYST
UTType.PNG,
#else
FileEx.PNG,
#endif
});
public IEnumerable<string>? GetPlatformFileType(Platform platform)
{
foreach (var value in Values)
{
if (value.Patterns != null)
foreach (var item in value.Patterns)
yield return item;
if (value.MimeTypes != null)
foreach (var item in value.MimeTypes)
yield return item;
if (value.AppleUniformTypeIdentifiers != null)
foreach (var item in value.AppleUniformTypeIdentifiers)
yield return item;
}
}
}
IFilePickerFileType IInternalFilePickerPlatformService.Jpeg => FilePickerFileType.Parse(new string[] {
#if IOS || MACOS || MACCATALYST
UTType.JPEG,
"jpeg",
#else
FileEx.JPG,
FileEx.JPEG,
#endif
});
IFilePickerFileType IInternalFilePickerPlatformService.Images { get; } = new FilePickerFileTypeWrapper
{
Values = new[]
{
new APS_FilePickerFileType("All Images")
{
Patterns = new[] { "*.webp", "*.png", "*.jpg", "*.jpeg", "*.gif", "*.bmp" },
AppleUniformTypeIdentifiers = new[] { "public.png", "public.jpeg", "public.jpeg-2000", "com.compuserve.gif", "com.microsoft.bmp", },
MimeTypes = new[] { "image/webp", "image/png", "image/jpeg", "image/gif", "image/bmp", },
},
},
};
IFilePickerFileType IInternalFilePickerPlatformService.Videos => FilePickerFileType.Parse(new string[] {
#if IOS || MACOS || MACCATALYST
UTType.MPEG4, UTType.Video, UTType.AVIMovie, UTType.AppleProtectedMPEG4Video, "mp4", "m4v", "mpg", "mpeg", "mp2", "mov", "avi", "mkv", "flv", "gifv", "qt",
#else
FileEx.Mp4,
FileEx.Mov,
FileEx.Avi,
FileEx.Wmv,
FileEx.M4v,
FileEx.Mpg,
FileEx.Mpeg,
FileEx.Mp2,
FileEx.Mkv,
FileEx.Flv,
FileEx.Gifv,
FileEx.Qt,
#endif
});
IFilePickerFileType IInternalFilePickerPlatformService.Png { get; } = new FilePickerFileTypeWrapper
{
Values = new[]
{
new APS_FilePickerFileType("PNG image")
{
Patterns = new[] { "*.png", },
AppleUniformTypeIdentifiers = new[] { "public.png", },
MimeTypes = new[] { "image/png", },
},
},
};
IFilePickerFileType IInternalFilePickerPlatformService.Pdf => FilePickerFileType.Parse(new string[] {
#if IOS || MACOS || MACCATALYST
UTType.PDF,
#else
FileEx.PDF,
#endif
});
IFilePickerFileType IInternalFilePickerPlatformService.Jpeg { get; } = new FilePickerFileTypeWrapper
{
Values = new[]
{
new APS_FilePickerFileType("JPEG image")
{
Patterns = new[] { "*.jpg", "*.jpeg", },
AppleUniformTypeIdentifiers = new[] { "public.jpeg", "public.jpeg-2000", },
MimeTypes = new[] { "image/jpeg", },
},
},
};
IFilePickerFileType IInternalFilePickerPlatformService.Videos => throw new NotImplementedException();
IFilePickerFileType IInternalFilePickerPlatformService.Pdf { get; } = new FilePickerFileTypeWrapper
{
Values = new[]
{
new APS_FilePickerFileType("PDF document")
{
Patterns = new[] { "*.pdf", },
AppleUniformTypeIdentifiers = new[] { "com.adobe.pdf", },
MimeTypes = new[] { "application/pdf", },
},
},
};
//#if IOS || MACOS || MACCATALYST
// static readonly Lazy<string?[]> _UTTypes = new(() =>
// {
// var fields = typeof(UTType).GetProperties(BindingFlags.Public | BindingFlags.Static);
// var result = fields.Where(x => x.PropertyType == typeof(string) || x.PropertyType == typeof(NSString)).Select(x => x.GetValue(null)?.ToString()).ToArray();
// return result;
// });
//#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static AvaloniaFilePickerFileType Convert(string name, IEnumerable<string>? extensions)
static bool IsAppleUniformTypeIdentifier(string s)
{
var result = new AvaloniaFilePickerFileType(name)
if (s == "jpeg" || s.StartsWith("public.") || s.StartsWith("com."))
{
return true;
}
//#if IOS || MACOS || MACCATALYST
// if (_UTTypes.Value.Contains(s))
// {
// return true;
// }
//#endif
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static APS_FilePickerFileType Convert(string name, IEnumerable<string>? extensions)
{
// https://docs.avaloniaui.net/docs/next/concepts/services/storage-provider/file-picker-options
var result = new APS_FilePickerFileType(name);
if (extensions != null)
{
#if IOS || MACOS || MACCATALYST
AppleUniformTypeIdentifiers = extensions?.ToArray(),
HashSet<string> appleUniformTypeIdentifiers = new();
#else
Patterns = IServiceBase.FormatExtensions(extensions, trimLeadingPeriod: true).ToArray(),
#if !WINDOWS
HashSet<string> mimeTypes = new();
#endif
};
HashSet<string> patterns = new();
#endif
foreach (var x in extensions)
{
if (x.Contains('/'))
{
#if !WINDOWS && !(IOS || MACOS || MACCATALYST)
mimeTypes.Add(x);
#endif
continue;
}
else if (IsAppleUniformTypeIdentifier(x))
{
#if IOS || MACOS || MACCATALYST
appleUniformTypeIdentifiers.Add(x);
#endif
continue;
}
else if (x.StartsWith('.'))
{
#if !(IOS || MACOS || MACCATALYST)
patterns.Add($"*{x}");
#endif
continue;
}
else if (x.StartsWith('*'))
{
#if !(IOS || MACOS || MACCATALYST)
patterns.Add(x);
#endif
continue;
}
else
{
#if !(IOS || MACOS || MACCATALYST)
patterns.Add($"*.{x}");
#endif
continue;
}
}
#if IOS || MACOS || MACCATALYST
if (appleUniformTypeIdentifiers.Any())
{
result.AppleUniformTypeIdentifiers = appleUniformTypeIdentifiers.ToArray();
}
#else
#if !WINDOWS
if (mimeTypes.Any())
{
result.MimeTypes = mimeTypes.ToArray();
}
#endif
if (patterns.Any())
{
result.Patterns = patterns.ToArray();
}
#endif
}
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static List<AvaloniaFilePickerFileType>? Convert(IFilePickerFileType? fileTypes)
static IReadOnlyList<APS_FilePickerFileType>? Convert(IFilePickerFileType? fileTypes)
{
if (fileTypes is FilePickerFileTypeWrapper impl)
{
return impl.Values;
}
if (fileTypes is FilePickerFileType.IFilePickerFileTypeWithName @interface)
{
var values = @interface.GetFileTypes();
@@ -103,7 +214,7 @@ sealed class AvaloniaFilePickerPlatformService : BaseService, IServiceBase, IOpe
var extensions = fileTypes?.GetPlatformFileType(DeviceInfo2.Platform());
if (extensions.Any_Nullable())
{
return new()
return new APS_FilePickerFileType[]
{
Convert(string.Empty, extensions),
};
@@ -9,6 +9,8 @@ namespace BD.WTTS.Services.Implementation;
[PseudoClasses(":topleft", ":topright", ":bottomleft", ":bottomright")]
sealed class AvaloniaToastServiceImpl : IToastService
{
public static AvaloniaToastServiceImpl Instance { get; } = (AvaloniaToastServiceImpl)Ioc.Get<IToastService>();
public SnackbarManager? NotificationManager { get; set; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -21,6 +23,17 @@ sealed class AvaloniaToastServiceImpl : IToastService
_ => AvaNotificationType.Information,
};
public void SetSnackbarManager(TopLevel host)
{
NotificationManager ??= new SnackbarManager(host)
{
Position = NotificationPosition.BottomRight,
MaxItems = 5,
};
//NotificationManager.ApplyTemplate();
}
/// <summary>
/// 显示Toast
/// </summary>
@@ -28,13 +41,16 @@ sealed class AvaloniaToastServiceImpl : IToastService
/// <param name="duration"></param>
public void Show(ToastIcon icon, string text, int? duration = null)
{
var host = AvaloniaWindowManagerImpl.GetWindowTopLevel();
NotificationManager ??= new SnackbarManager(host)
if (NotificationManager == null)
{
Position = NotificationPosition.BottomRight,
MaxItems = 5,
};
var host = AvaloniaWindowManagerImpl.GetWindowTopLevel();
NotificationManager = new SnackbarManager(host)
{
Position = NotificationPosition.BottomRight,
MaxItems = 5,
};
}
var notificationType = GetNotificationType(icon);
NotificationManager.Show(text, notificationType: notificationType);
@@ -1,3 +1,4 @@
using AngleSharp.Dom;
using BD.WTTS.Client.Resources;
using LiveChartsCore;
using LiveChartsCore.SkiaSharpView;
@@ -189,6 +190,11 @@ public sealed partial class App : Application
{
try
{
if (url == null || string.IsNullOrEmpty(url.ToString()))
{
Toast.Show(ToastIcon.Warning, "打开链接失败");
return;
}
Browser2.Open(url.ToString());
}
catch (Exception ex)
@@ -201,6 +207,11 @@ public sealed partial class App : Application
{
try
{
if (text == null || string.IsNullOrEmpty(text.ToString()))
{
Toast.Show(ToastIcon.Warning, "复制内容失败");
return;
}
await Clipboard2.SetTextAsync(text.ToString());
Toast.Show(ToastIcon.Success, Strings.CopyToClipboard);
}
@@ -428,7 +428,8 @@
Name="IconHost"
Margin="{TemplateBinding ImageMargin}"
HorizontalAlignment="Left"
VerticalAlignment="Center">
VerticalAlignment="Center"
Stretch="{TemplateBinding ImageStretch}">
<ContentPresenter Content="{TemplateBinding Image}" />
</Viewbox>
@@ -15,7 +15,6 @@
<ControlTemplate>
<Panel>
<StackPanel
Name="LoadedContentPresenter"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{TemplateBinding IsLoading}"
@@ -46,13 +45,6 @@
</Border>
</StackPanel>
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding NoResultMessage}"
Foreground="{TemplateBinding Foreground}"
IsVisible="{TemplateBinding IsShowNoResultText}"
Theme="{StaticResource SubtitleTextBlockStyle}" />
<!--<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
@@ -66,8 +58,28 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
Foreground="{TemplateBinding Foreground}">
<!--<ContentPresenter.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="!IsShowNoResultText" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="!IsLoading" RelativeSource="{RelativeSource TemplatedParent}" />
</MultiBinding>
</ContentPresenter.IsVisible>-->
</ContentPresenter>
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding NoResultMessage}"
Foreground="{TemplateBinding Foreground}"
IsVisible="{Binding !#LoadedContentPresenter.IsVisible}" />
Theme="{StaticResource SubtitleTextBlockStyle}">
<ContentPresenter.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="IsShowNoResultText" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="!IsLoading" RelativeSource="{RelativeSource TemplatedParent}" />
</MultiBinding>
</ContentPresenter.IsVisible>
</ContentPresenter>
</Panel>
</ControlTemplate>
</Setter>
@@ -21,6 +21,9 @@ public class AppItem : TemplatedControl
public static readonly StyledProperty<Thickness> ImageMarginProperty =
AvaloniaProperty.Register<AppItem, Thickness>(nameof(ImageMargin));
public static readonly StyledProperty<Stretch> ImageStretchProperty =
AvaloniaProperty.Register<AppItem, Stretch>(nameof(ImageStretch), Stretch.UniformToFill);
public static readonly StyledProperty<bool> ExpandsProperty =
AvaloniaProperty.Register<OptionsDisplayItem, bool>(nameof(Expands));
@@ -72,6 +75,12 @@ public class AppItem : TemplatedControl
set => SetValue(ImageMarginProperty, value);
}
public Stretch ImageStretch
{
get => GetValue(ImageStretchProperty);
set => SetValue(ImageStretchProperty, value);
}
public bool Expands
{
get => GetValue(ExpandsProperty);
@@ -99,31 +99,41 @@ public class ContentLoader : ContentControl
set => SetValue(MaximumProperty, value);
}
//ContentPresenter? contentPresenter;
//protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
//{
// base.OnApplyTemplate(e);
// contentPresenter = e.NameScope.Find<ContentPresenter>("ContentPresenter");
//}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == ProgressValueProperty)
{
if (e.NewValue is double value)
{
if (value > 0)
{
IsIndeterminate = false;
IsLoading = true;
}
else if (value == Maximum)
{
IsLoading = false;
}
}
}
else if (e.Property == IsLoadingProperty || e.Property == IsShowNoResultTextProperty)
{
if (IsLoading && IsShowNoResultText)
{
IsShowNoResultText = false;
}
}
//if (e.Property == IsLoadingProperty || e.Property == IsShowNoResultTextProperty)
//{
// if (!IsLoading && !IsShowNoResultText && contentPresenter != null && contentPresenter.Content is ItemsPresenter itemsPresenter)
// {
// itemsPresenter.InvalidateMeasure();
// itemsPresenter.InvalidateArrange();
// itemsPresenter.UpdateLayout();
// }
//}
//if (e.Property == ProgressValueProperty)
//{
// if (e.NewValue is double value)
// {
// if (value > 0)
// {
// IsIndeterminate = false;
// IsLoading = true;
// }
// else if (value == Maximum)
// {
// IsLoading = false;
// }
// }
//}
}
}
@@ -74,7 +74,7 @@ public sealed class CustomFilePicker : ContentControl
this.Tapped += CustomFilePicker_Tapped;
}
private async void CustomFilePicker_Tapped(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
private async void CustomFilePicker_Tapped(object? sender, RoutedEventArgs e)
{
//if (string.IsNullOrEmpty(FileExtensions) || FileExtensions == "*")
//{
@@ -14,7 +14,7 @@
<Grid
Height="{StaticResource TitleBarHeight}"
Background="Transparent"
ColumnDefinitions="Auto,Auto,*,Auto">
ColumnDefinitions="Auto,*,Auto,Auto">
<Grid.Margin>
<OnPlatform Default="0,0,22,0">
<On Content="0,0,140,0" Options="Windows" />
@@ -39,7 +39,8 @@
IsHitTestVisible="False"
Text="{ReflectionBinding Title,
RelativeSource={RelativeSource FindAncestor,
AncestorType=Window}}" />
AncestorType=Window}}"
TextWrapping="Wrap" />
<Border
Grid.Column="2"
@@ -8,7 +8,7 @@ public partial class AboutPage : ReactiveUserControl<AboutPageViewModel>
public AboutPage()
{
InitializeComponent();
this.SetViewModel<AboutPageViewModel>();
this.SetViewModel<AboutPageViewModel>(false);
}
/// <summary>
@@ -7,7 +7,7 @@ public partial class DebugPage : PageBase<DebugPageViewModel>
public DebugPage()
{
InitializeComponent();
this.SetViewModel<DebugPageViewModel>();
this.SetViewModel<DebugPageViewModel>(false);
CommandTextBox.KeyUp += (s, e) =>
{
@@ -7,7 +7,7 @@ public partial class HomePage : ReactiveUserControl<HomePageViewModel>
public HomePage()
{
InitializeComponent();
this.SetViewModel<HomePageViewModel>();
this.SetViewModel<HomePageViewModel>(false);
ShopsScrollViewer.PointerWheelChanged += ShopsScrollViewer_PointerWheelChanged;
}
@@ -179,7 +179,11 @@
Background="{DynamicResource ControlOnImageFillColorDefaultBrush}"
ClipToBounds="True"
CornerRadius="30">
<spp:Image2 RenderOptions.BitmapInterpolationMode="HighQuality" Source="{StaticResource DefaultAvatar}" />
<spp:Image2
DecodeWidth="32"
FallbackSource="{StaticResource DefaultAvatar}"
RenderOptions.BitmapInterpolationMode="HighQuality"
Source="{Binding Path=AvatarPath, Mode=OneWay, Source={x:Static s:UserService.Current}}" />
</Border>
<DropDownButton.Flyout>
<MenuFlyout FlyoutPresenterClasses="NoPadding" Placement="BottomEdgeAlignedRight">
@@ -198,7 +202,11 @@
ClipToBounds="True"
CornerRadius="64"
DockPanel.Dock="Left">
<spp:Image2 RenderOptions.BitmapInterpolationMode="HighQuality" Source="{StaticResource DefaultAvatar}" />
<spp:Image2
DecodeWidth="64"
FallbackSource="{StaticResource DefaultAvatar}"
RenderOptions.BitmapInterpolationMode="HighQuality"
Source="{Binding Path=AvatarPath, Mode=OneWay, Source={x:Static s:UserService.Current}}" />
</Border>
<Panel>
<TextBlock
@@ -269,7 +277,7 @@
<MenuItem
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
CommandParameter="https://steampp.net/account/center/safe"
Header="{Binding Path=Res.ChangePassword, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Header="{Binding Path=Res.PersonalSafe, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
<MenuItem Header="-" IsVisible="{Binding Path=User, Mode=OneWay, Source={x:Static s:UserService.Current}, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}" />
<MenuItem
@@ -109,6 +109,9 @@ public sealed partial class MainView : ReactiveUserControl<MainWindowViewModel>
{
base.OnAttachedToVisualTree(e);
var top = TopLevel.GetTopLevel(this);
if (top != null)
AvaloniaToastServiceImpl.Instance.SetSnackbarManager(top);
NavigationService.Instance.SetFrame(FrameView);
}
@@ -7,7 +7,7 @@ public partial class SettingsPage : PageBase<SettingsPageViewModel>
public SettingsPage()
{
InitializeComponent();
this.SetViewModel<SettingsPageViewModel>();
this.SetViewModel<SettingsPageViewModel>(false);
//SettingsScrollTab.SelectionChanged += SettingsScrollTab_SelectionChanged;
@@ -7,6 +7,7 @@
xmlns:ms="https://steampp.net/settings"
xmlns:s="https://steampp.net/services"
xmlns:spp="https://steampp.net/ui"
xmlns:spp_enums="using:BD.WTTS.Enums"
xmlns:ui="using:FluentAvalonia.UI.Controls"
d:DesignHeight="680"
d:DesignWidth="1080"
@@ -59,7 +60,18 @@
<ui:FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="&#xE71D;" />
</spp:OptionsDisplayItem.Icon>
<spp:OptionsDisplayItem.ActionButton>
<ComboBox ItemsSource="{Binding UpdateChannels}" SelectedValue="{Binding Source={x:Static ms:GeneralSettings.UpdateChannel}, Path=Value, Mode=TwoWay}" />
<ComboBox SelectedItem="{Binding Source={x:Static ms:GeneralSettings.UpdateChannel}, Path=Value, Mode=TwoWay, Converter={StaticResource EnumConverter}, ConverterParameter={x:Type spp_enums:UpdateChannelType}}">
<ComboBox.Items>
<spp_enums:UpdateChannelType>Auto</spp_enums:UpdateChannelType>
<spp_enums:UpdateChannelType>GitHub</spp_enums:UpdateChannelType>
<spp_enums:UpdateChannelType>Official</spp_enums:UpdateChannelType>
</ComboBox.Items>
<ComboBox.ItemTemplate>
<DataTemplate DataType="spp_enums:UpdateChannelType">
<TextBlock Text="{Binding ., Converter={StaticResource EnumLocalizationNameConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</spp:OptionsDisplayItem.ActionButton>
</spp:OptionsDisplayItem>
@@ -5,7 +5,6 @@ public partial class LoginOrRegisterPage : ReactiveUserControl<LoginOrRegisterWi
public LoginOrRegisterPage()
{
InitializeComponent();
this.SetViewModel<LoginOrRegisterWindowViewModel>();
TbPhoneNumber.KeyUp += (_, e) =>
{
@@ -32,6 +31,12 @@ public partial class LoginOrRegisterPage : ReactiveUserControl<LoginOrRegisterWi
};
}
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
this.SetViewModel<LoginOrRegisterWindowViewModel>(false);
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
@@ -9,6 +9,8 @@
Title="{Binding Title}"
Width="960"
Height="680"
MinWidth="528"
MinHeight="508"
d:DesignHight="450"
d:DesignWidth="800"
x:CompileBindings="True"
@@ -10,12 +10,14 @@ sealed class AcceleratorAutoMapperProfile : Profile
.ForMember(x => x.Author, d => d.MapFrom(x => x.AuthorName))
.ForMember(x => x.Icon, d => d.MapFrom(x => x.IconUrl))
.ForMember(x => x.Description, d => d.MapFrom(x => x.Describe))
.ForMember(x => x.Enable, d => d.MapFrom(x => !x.Disable))
.ForMember(x => x.Id, d => d.MapFrom(x => x.LocalId));
CreateMap<Script, ScriptDTO>()
.ForMember(x => x.LocalId, d => d.MapFrom(x => x.Id))
.ForMember(x => x.AuthorName, d => d.MapFrom(x => x.Author))
.ForMember(x => x.Describe, d => d.MapFrom(x => x.Description))
.ForMember(x => x.IconUrl, d => d.MapFrom(x => x.Icon))
.ForMember(x => x.Disable, d => d.MapFrom(x => !x.Enable))
.ForMember(x => x.Id, d => d.MapFrom(x => x.Pid));
}
}
@@ -140,19 +140,39 @@ public sealed class ScriptManager : GeneralHttpClientFactory, IScriptManager
{
fileInfo.CopyTo(jsSavePath);
}
if (oldInfo != null && oldInfo.LocalId > 0)
if (oldInfo != null)
{
info.LocalId = oldInfo.LocalId;
info.Id = oldInfo.Id;
info.Order = oldInfo.Order;
if (isNoRepeat)
//本地DTO
if (oldInfo.LocalId > 0)
{
var state = await DeleteScriptAsync(oldInfo, false);
if (!state.IsSuccess)
info.LocalId = oldInfo.LocalId;
info.Id = oldInfo.Id;
info.Order = oldInfo.Order;
if (isNoRepeat)
{
return ApiRspHelper.Fail<ScriptDTO?>(AppResources.Script_FileDeleteError_.Format(oldInfo.FilePath));
var state = await DeleteScriptAsync(oldInfo, false);
if (!state.IsSuccess)
{
return ApiRspHelper.Fail<ScriptDTO?>(AppResources.Script_FileDeleteError_.Format(oldInfo.FilePath));
}
}
}
else
{
//在线 DTO 返回值
info.SourceLink = oldInfo.SourceLink;
info.UpdateLink = oldInfo.UpdateLink;
info.DownloadLink = oldInfo.DownloadLink;
info.IconUrl = oldInfo.IconUrl;
info.AuthorName = oldInfo.AuthorName;
info.Describe = oldInfo.Describe;
info.UpdateTime = oldInfo.UpdateTime;
info.AccelerateProjects = oldInfo.AccelerateProjects;
info.Version = oldInfo.Version;
info.ExcludeDomainNames = oldInfo.ExcludeDomainNames;
info.MatchDomainNames = oldInfo.MatchDomainNames;
info.Name = oldInfo.Name;
}
}
if (pid.HasValue)
info.Id = pid.Value;
@@ -251,6 +251,14 @@ partial class ProxyService
/// </summary>
async Task<OperateProxyServiceResult> StopProxyServiceAsync(bool isExit = false)
{
bool callSet = true;
#if WINDOWS
if (isExit && !platformService.IsAdministrator)
{
// Windows 平台非管理员权限进程退出时候忽略,由管理员进程退出时执行清空系统代理配置
callSet = false;
}
#endif
switch (proxyMode) // 先停止接入代理流量
{
case ProxyMode.Hosts:
@@ -276,10 +284,16 @@ partial class ProxyService
}
break;
case ProxyMode.System:
await platformService.SetAsSystemProxyAsync(false);
if (callSet)
{
await platformService.SetAsSystemProxyAsync(false);
}
break;
case ProxyMode.PAC:
await platformService.SetAsSystemPACProxyAsync(false);
if (callSet)
{
await platformService.SetAsSystemPACProxyAsync(false);
}
break;
}
StopTimer(); // 停止 UI 计时器
@@ -75,7 +75,6 @@ public sealed partial class ProxyService
//ProxySettings.ScriptsStatus.Value = ProxyScripts.Items.Where(x => x?.LocalId > 0).Select(k => k.LocalId).ToImmutableHashSet();
if (reverseProxyService.ProxyRunning)
{
//await EnableProxyScripts.ContinueWith(e =>
//{
// reverseProxyService.Scripts = e.Result?.ToImmutableArray();
@@ -399,7 +398,7 @@ public sealed partial class ProxyService
var jspath = await scriptManager.DownloadScriptAsync(basicsInfo.Content.UpdateLink);
if (jspath.IsSuccess)
{
var build = await scriptManager.AddScriptAsync(jspath.Content!, isCompile: false, order: 1, deleteFile: true, pid: basicsInfo.Content.Id, ignoreCache: true);
var build = await scriptManager.AddScriptAsync(jspath.Content!, basicsInfo.Content, isCompile: false, order: 1, deleteFile: true, pid: basicsInfo.Content.Id, ignoreCache: true);
if (build.IsSuccess)
{
if (build.Content != null)
@@ -478,13 +477,13 @@ public sealed partial class ProxyService
}
}
Toast.Show(item.IsSuccess ? ToastIcon.Success : ToastIcon.Error, item.Message);
RefreshScript();
await RefreshScript();
}
/// <summary>
/// 刷新脚本列表
/// </summary>
public async void RefreshScript()
public async Task RefreshScript()
{
var scriptList = await scriptManager.GetAllScriptAsync();
ProxyScripts.Clear();
@@ -7,6 +7,7 @@ namespace BD.WTTS.UI.ViewModels;
public sealed partial class AcceleratorPageViewModel
{
DateTime _initializeTime;
readonly IHostsFileService? hostsFileService;
readonly IPlatformService platformService = IPlatformService.Instance;
readonly IReverseProxyService reverseProxyService = IReverseProxyService.Constants.Instance;
@@ -21,6 +22,14 @@ public sealed partial class AcceleratorPageViewModel
RefreshCommand = ReactiveCommand.Create(async () =>
{
if (_initializeTime > DateTime.Now.AddSeconds(-2))
{
Toast.Show(ToastIcon.Warning, Strings.Warning_DoNotOperateFrequently);
return;
}
_initializeTime = DateTime.Now;
if (ProxyService.Current.ProxyStatus == false)
await ProxyService.Current.InitializeAccelerateAsync();
else
@@ -6,19 +6,10 @@ namespace BD.WTTS.UI.ViewModels;
public partial class ScriptPageViewModel : TabItemViewModel
{
DateTime _initializeTime;
public ReactiveCommand<Unit, Unit>? EnableScriptAutoUpdateCommand { get; }
//public MenuItemViewModel? ScriptAutoUpdate { get; }
//public MenuItemViewModel? OnlySteamBrowser { get; }
//public ReactiveCommand<Unit, Unit>? OnlySteamBrowserCommand { get; }
//public ReactiveCommand<Unit, Unit>? ScriptStoreCommand { get; }
//public ReactiveCommand<Unit, Unit>? AllEnableScriptCommand { get; }
public ScriptPageViewModel()
{
//AllEnableScriptCommand = ReactiveCommand.Create(AllEnableScript);
@@ -51,17 +42,23 @@ public partial class ScriptPageViewModel : TabItemViewModel
RefreshScriptItemCommand = ReactiveCommand.Create<ScriptDTO>(RefreshScriptItem);
OpenHomeScriptItemCommand = ReactiveCommand.Create<ScriptDTO>(OpenHomeScriptItem);
ScriptStoreCommand = ReactiveCommand.Create(OpenScriptStoreWindow);
DeleteScriptItemCommand = ReactiveCommand.Create<ScriptDTO>(DeleteScriptItemButton);
DownloadScriptItemCommand = ReactiveCommand.Create<ScriptDTO>(DownloadScriptItem);
RefreshALLScriptCommand = ReactiveCommand.Create(() =>
RefreshALLScriptCommand = ReactiveCommand.Create(async () =>
{
ProxyService.Current.RefreshScript();
if (_initializeTime > DateTime.Now.AddSeconds(-2))
{
Toast.Show(ToastIcon.Warning, Strings.Warning_DoNotOperateFrequently);
return;
}
_initializeTime = DateTime.Now;
await ProxyService.Current.RefreshScript();
Toast.Show(ToastIcon.Success, Strings.RefreshOK);
});
@@ -161,11 +158,6 @@ public partial class ScriptPageViewModel : TabItemViewModel
}
public async void OpenHomeScriptItem(ScriptDTO script)
{
await Browser2.OpenAsync(script.SourceLink);
}
public async void RefreshScriptItem(ScriptDTO script)
{
if (script?.FilePath != null)
@@ -16,8 +16,6 @@ public partial class ScriptPageViewModel : TabItemViewModel
public ICommand RefreshScriptItemCommand { get; }
public ICommand OpenHomeScriptItemCommand { get; }
public ICommand ScriptStoreCommand { get; }
public ICommand? AddNewScriptCommand { get; }
@@ -18,8 +18,6 @@ public partial class ScriptStorePageViewModel : ViewModelBase
DownloadScriptItemCommand = ReactiveCommand.Create<ScriptDTO>(DownloadScriptItem);
OpenHomeScriptItemCommand = ReactiveCommand.Create<ScriptDTO>(OpenHomeScriptItem);
this.WhenAnyValue(x => x.SearchText)
.Subscribe(x =>
{
@@ -32,11 +30,6 @@ public partial class ScriptStorePageViewModel : ViewModelBase
ProxyService.Current.DownloadScript(model);
}
public async void OpenHomeScriptItem(ScriptDTO script)
{
await Browser2.OpenAsync(script.SourceLink);
}
async void InitializeScriptList()
{
var client = IMicroServiceClient.Instance.Script;
@@ -19,6 +19,4 @@ public partial class ScriptStorePageViewModel : ViewModelBase
public bool IsScriptsEmpty => !Scripts.Any_Nullable();
public ICommand? DownloadScriptItemCommand { get; }
public ICommand? OpenHomeScriptItemCommand { get; }
}
@@ -7,6 +7,6 @@ public partial class AcceleratorPage : PageBase<AcceleratorPageViewModel>
public AcceleratorPage()
{
InitializeComponent();
this.SetViewModel<AcceleratorPageViewModel>();
this.SetViewModel<AcceleratorPageViewModel>(false);
}
}
@@ -140,7 +140,8 @@
Title="{Binding Name}"
MaxHeight="200"
Description="{Binding Describe}"
ImageMargin="25">
ImageMargin="30"
ImageStretch="None">
<spp:AppItem.Status>
<ToggleSwitch Classes="LeftContent" IsChecked="{Binding !Disable, Mode=TwoWay}" />
</spp:AppItem.Status>
@@ -187,8 +188,8 @@
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}" />-->
<ui:MenuFlyoutItem
Command="{Binding $parent[spp:PageBase].((spp:ScriptPageViewModel)DataContext).OpenHomeScriptItemCommand}"
CommandParameter="{Binding}"
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
CommandParameter="{Binding SourceLink}"
Text="{Binding Path=Res.Script_Home, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:MenuFlyoutItem
Command="{Binding $parent[spp:PageBase].((spp:ScriptPageViewModel)DataContext).DownloadScriptItemCommand}"
@@ -213,10 +214,8 @@
</ui:FAMenuFlyout>
</spp:AppItem.MoreFlyout>
<spp:AppItem.Image>
<Panel>
<Panel Width="64">
<spp:Image2
Width="64"
DecodeWidth="64"
FallbackSource="avares://BD.WTTS.Client.Plugins.Accelerator/UI/Assets/script.ico"
IsVisible="{Binding !IsLoading}"
RenderOptions.BitmapInterpolationMode="HighQuality"
@@ -7,7 +7,7 @@ public partial class ScriptPage : PageBase<ScriptPageViewModel>
public ScriptPage()
{
InitializeComponent();
this.SetViewModel<ScriptPageViewModel>();
this.SetViewModel<ScriptPageViewModel>(false);
//StoreButton.Click += StoreButton_Click;
}
@@ -13,76 +13,77 @@
x:CompileBindings="True"
x:DataType="spp:ScriptStorePageViewModel"
mc:Ignorable="d">
<StackPanel Margin="{StaticResource PageMargin}" Spacing="{StaticResource SettingsSpacing}">
<AutoCompleteBox
<StackPanel Margin="{StaticResource PageMargin}" Spacing="{StaticResource SettingsSpacing}">
<AutoCompleteBox
MinWidth="300"
Margin="0,0,0,10"
VerticalAlignment="Center"
Classes="Search"
Text="{Binding SearchText}" />
<!--<TextBlock Margin="0,0,0,5" Text="脚本工坊" />-->
<spp:ContentLoader IsLoading="{Binding !!!Scripts.Count, Mode=OneWay}">
<ItemsRepeater ItemsSource="{Binding Scripts}">
<ItemsRepeater.Layout>
<WrapLayout
<!--<TextBlock Margin="0,0,0,5" Text="脚本工坊" />-->
<spp:ContentLoader IsLoading="{Binding !!!Scripts.Count, Mode=OneWay}">
<ItemsRepeater ItemsSource="{Binding Scripts}">
<ItemsRepeater.Layout>
<WrapLayout
HorizontalSpacing="10"
Orientation="Horizontal"
VerticalSpacing="10" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate DataType="spp:ScriptDTO">
<Border spp:Animations.EnableAnimations="True">
<spp:AppItem
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate DataType="spp:ScriptDTO">
<Border spp:Animations.EnableAnimations="True">
<spp:AppItem
Title="{Binding Name}"
MaxWidth="135"
Classes="Vertical"
Description="{Binding AuthorName}"
ImageMargin="20"
ImageStretch="None"
ToolTip.Tip="{Binding Describe}">
<spp:AppItem.Status>
<Panel>
<Border Classes="Status" IsVisible="{Binding IsExist}">
<TextBlock Text="{Binding Path=Res.Installed, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
</Border>
<Button
<spp:AppItem.Status>
<Panel>
<Border Classes="Status" IsVisible="{Binding IsExist}">
<TextBlock Text="{Binding Path=Res.Installed, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
</Border>
<Button
Classes="accent"
Command="{Binding $parent[UserControl].((spp:ScriptStorePageViewModel)DataContext).DownloadScriptItemCommand}"
CommandParameter="{Binding}"
Content="获取"
IsVisible="{Binding DownloadButtonLoading}" />
<ProgressBar Width="30"
Height="30"
Classes="Circle"
IsIndeterminate="True"
IsVisible="{Binding DownloadLoading}" />
</Panel>
</spp:AppItem.Status>
<spp:AppItem.MoreFlyout>
<ui:FAMenuFlyout>
<ui:MenuFlyoutItem
<ProgressBar
Width="30"
Height="30"
Classes="Circle"
IsIndeterminate="True"
IsVisible="{Binding DownloadLoading}" />
</Panel>
</spp:AppItem.Status>
<spp:AppItem.MoreFlyout>
<ui:FAMenuFlyout>
<ui:MenuFlyoutItem
Command="{Binding $parent[UserControl].((spp:ScriptStorePageViewModel)DataContext).DownloadScriptItemCommand}"
CommandParameter="{Binding}"
IsVisible="{Binding IsUpdate}"
Text="更新" />
<ui:MenuFlyoutItem
Command="{Binding $parent[UserControl].((spp:ScriptStorePageViewModel)DataContext).OpenHomeScriptItemCommand}"
CommandParameter="{Binding}"
<ui:MenuFlyoutItem
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
CommandParameter="{Binding SourceLink}"
Text="{Binding Path=Res.Script_Home, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
</ui:FAMenuFlyout>
</spp:AppItem.MoreFlyout>
<spp:AppItem.Image>
<spp:Image2
Width="80"
Height="80"
</ui:FAMenuFlyout>
</spp:AppItem.MoreFlyout>
<spp:AppItem.Image>
<spp:Image2
FallbackSource="avares://BD.WTTS.Client.Plugins.Accelerator/UI/Assets/script.ico"
RenderOptions.BitmapInterpolationMode="HighQuality"
Source="{Binding IconUrl}"
Stretch="Uniform" />
</spp:AppItem.Image>
</spp:AppItem>
</Border>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</spp:ContentLoader>
</StackPanel>
</spp:AppItem.Image>
</spp:AppItem>
</Border>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</spp:ContentLoader>
</StackPanel>
</UserControl>
@@ -366,99 +366,7 @@ public static class AuthenticatorHelper
return valueDto;
}
//TODO: 待完善
public static async Task<string?> DecodePrivateKey(string secretCode)
{
if (AuthenticatorRegexHelper.SecretCodeHttpRegex().Match(secretCode) is { Success: true })
{
//url图片二维码解码
var handler = new HttpClientHandler
{
AllowAutoRedirect = true,
MaxAutomaticRedirections = 1000,
};
using var httpClient = new HttpClient(handler);
httpClient.DefaultRequestHeaders.Add("User-Agent", UserAgent.Default);
httpClient.Timeout = new TimeSpan(0, 0, 20);
try
{
var response = await httpClient.GetAsync(secretCode);
if (response.StatusCode == HttpStatusCode.OK)
{
//response.Content.Headers.ContentType.;
//using (var bitmap = System.Drawing.Image.FromStream(await response.Content.ReadAsStreamAsync()))
//{
// //二维码解析
// //IBarcodeReader reader = new BarcodeReader();
// //var result = reader.Decode(bitmap);
// //if (result != null)
// //{
// // privatekey = HttpUtility.UrlDecode(result.Text);
// //}
//}
}
}
catch (Exception e)
{
Toast.Show(ToastIcon.Error, AppResources.Error_ScanQRCodeFailed_.Format(e.Message));
Log.Error(nameof(AuthenticatorHelper), e, nameof(DecodePrivateKey));
}
}
else if (AuthenticatorRegexHelper.SecretCodeDataImageRegex().Match(secretCode) is { Success: true } dataImageMatch)
{
var imageData = Convert.FromBase64String(dataImageMatch.Groups[2].Value);
using var ms = new MemoryStream(imageData);
#if WINDOWS
using (var bitmap = (System.Drawing.Bitmap)System.Drawing.Image.FromStream(ms))
{
//二维码解析
}
#endif
}
// else if (SecretCodeOptAuthRegex().Match(secretCode) is { Success: true } optMatch)
// {
// var authType = optMatch.Groups[1].Value;
// var label = optMatch.Groups[2].Value;
// var p = label.IndexOf(":", StringComparison.Ordinal);
// string? issuer;
// string? serial;
// if (p != -1)
// {
// issuer = label[..p];
// label = label[(p + 1)..];
// }
//
// var qs = HttpUtility.ParseQueryString(optMatch.Groups[3].Value);
// secretCode = qs["secret"] ?? secretCode;
// int queryDigits;
// if (int.TryParse(qs["digits"], out queryDigits) && queryDigits != 0)
// {
//
// }
// switch (authType)
// {
// case "totp":
// break;
// case "hotp":
// break;
// default:
// return null;
// }
// }
else
{
var privateKey = AuthenticatorRegexHelper.SecretHexCodeAuthRegex().Replace(secretCode, "");
return privateKey.Length < 1 ? null : privateKey;
}
return null;
}
public static bool ReadXml(ref AuthenticatorDTO authenticatorDto, XmlReader reader, string? password)
public static bool ReadXml(AuthenticatorDTO authenticatorDto, XmlReader reader, string? password)
{
var changed = false;
@@ -512,7 +420,7 @@ public static class AuthenticatorHelper
try
{
// we don't pass the password as they are locked till clicked
changed = authenticatorDto.Value!.ReadXml(reader) || changed;
changed = authenticatorDto.Value.ReadXml(reader) || changed;
}
catch (WinAuthEncryptedSecretDataException)
{
@@ -527,7 +435,9 @@ public static class AuthenticatorHelper
// v2
case "authenticator":
authenticatorDto.Value = AuthenticatorValueDTO.ReadXmlv2(reader, password);
var auth = AuthenticatorValueDTO.ReadXmlv2(reader, password);
if (auth != null)
authenticatorDto.Value = auth;
break;
// v2
case "servertimediff":
@@ -1,4 +1,5 @@
using BD.WTTS.Enums;
using BD.WTTS.Helper;
using System.Diagnostics.Metrics;
using WinAuth;
@@ -22,6 +23,119 @@ public partial class AuthenticatorGeneralImportPageViewModel
Platform = platform;
}
//TODO: 待完善
public async Task<string?> DecodePrivateKey(string secretCode)
{
//if (AuthenticatorRegexHelper.SecretCodeHttpRegex().Match(secretCode) is { Success: true })
//{
// ////url图片二维码解码
// //var handler = new HttpClientHandler
// //{
// // AllowAutoRedirect = true,
// // MaxAutomaticRedirections = 1000,
// //};
// //using var httpClient = new HttpClient(handler);
// //httpClient.DefaultRequestHeaders.Add("User-Agent", UserAgent.Default);
// //httpClient.Timeout = new TimeSpan(0, 0, 20);
// //try
// //{
// // var response = await httpClient.GetAsync(secretCode);
// // if (response.StatusCode == HttpStatusCode.OK)
// // {
// // response.Content.Headers.ContentType.;
// // using (var bitmap = System.Drawing.Image.FromStream(await response.Content.ReadAsStreamAsync()))
// // {
// // //二维码解析
// // //IBarcodeReader reader = new BarcodeReader();
// // //var result = reader.Decode(bitmap);
// // //if (result != null)
// // //{
// // // privatekey = HttpUtility.UrlDecode(result.Text);
// // //}
// // }
// // }
// //}
// //catch (Exception e)
// //{
// // Toast.Show(ToastIcon.Error, AppResources.Error_ScanQRCodeFailed_.Format(e.Message));
// // Log.Error(nameof(AuthenticatorHelper), e, nameof(DecodePrivateKey));
// //}
//}
// else if (AuthenticatorRegexHelper.SecretCodeDataImageRegex().Match(secretCode) is { Success: true } dataImageMatch)
// {
// var imageData = Convert.FromBase64String(dataImageMatch.Groups[2].Value);
// using var ms = new MemoryStream(imageData);
//#if WINDOWS
// using (var bitmap = (System.Drawing.Bitmap)System.Drawing.Image.FromStream(ms))
// {
// //二维码解析
// }
//#endif
// }
if (AuthenticatorRegexHelper.SecretCodeOptAuthRegex().Match(secretCode) is { Success: true } optMatch)
{
var authType = optMatch.Groups[1].Value;
var label = optMatch.Groups[2].Value;
switch (authType)
{
case "totp":
AuthType = AuthType.TOTP;
break;
case "hotp":
AuthType = AuthType.HOTP;
break;
default:
return null;
}
var qs = HttpUtility.ParseQueryString(optMatch.Groups[3].Value);
var privatekey = qs["secret"];
if (qs != null)
{
if (long.TryParse(qs["counter"], out var counter) == true)
{
CurrentCode = counter.ToString();
}
var issuer = qs["issuer"];
if (string.IsNullOrEmpty(issuer) == false)
{
label = issuer + (string.IsNullOrEmpty(label) == false ? " (" + label + ")" : string.Empty);
}
AuthenticatorName = label;
if (int.TryParse(qs["period"], out var period) == true && period > 0)
{
Period = period;
}
if (int.TryParse(qs["digits"], out var digits) == true && digits > 0)
{
CodeDigits = digits;
}
if (Enum.TryParse<HMACTypes>(qs["algorithm"], true, out var hmac) == true)
{
HMACType = hmac;
}
return privatekey;
}
}
else
{
var privateKey = AuthenticatorRegexHelper.SecretHexCodeAuthRegex().Replace(secretCode, "");
return privateKey.Length < 1 ? null : privateKey;
}
return null;
}
public async Task GenerateCode()
{
if (string.IsNullOrEmpty(SecretCode))
@@ -32,7 +146,7 @@ public partial class AuthenticatorGeneralImportPageViewModel
try
{
var privateKey = await AuthenticatorHelper.DecodePrivateKey(SecretCode);
var privateKey = await DecodePrivateKey(SecretCode);
if (string.IsNullOrEmpty(privateKey))
{
@@ -12,14 +12,20 @@ public partial class AuthenticatorGeneralImportPageViewModel : ViewModelBase
public string? CurrentCode { get; set; } = "------";
[Reactive]
public AuthType AuthType { get; set; }
public AuthType AuthType { get; set; } = AuthType.TOTP;
[Reactive]
public HMACTypes HMACType { get; set; }
/// <summary>
/// 刷新间隔时间
/// </summary>
[Reactive]
public int CodeDigits { get; set; } = 30;
public int Period { get; set; } = 30;
/// <summary>
/// 令牌位数
/// </summary>
[Reactive]
public int Period { get; set; } = 6;
public int CodeDigits { get; set; } = 6;
}
@@ -563,7 +563,7 @@ public sealed partial class AuthenticatorHomePageViewModel : ViewModelBase
{
if (sender is not AuthenticatorItemModel authenticatorItemModel) return;
var index = Auths.IndexOf(authenticatorItemModel);
if (index == Auths.Count)
if (index > Auths.Count)
return;
if (authenticatorItemModel.AuthData.ServerId != null)
{
@@ -193,7 +193,7 @@ public class AuthenticatorImportPageViewModel : ViewModelBase
if (reader.Name != "WinAuthAuthenticator") continue;
var authDto = new AuthenticatorDTO();
AuthenticatorHelper.ReadXml(ref authDto, reader, null);
AuthenticatorHelper.ReadXml(authDto, reader, null);
await AuthenticatorHelper.SaveAuthenticator(authDto, password, isLocalProtect);
}
else
@@ -37,8 +37,8 @@
SelectedValue="{Binding AuthType}"
Theme="{StaticResource TabStripStyle}">
<TabStrip.Items>
<spp:AuthType>HOTP</spp:AuthType>
<spp:AuthType>TOTP</spp:AuthType>
<spp:AuthType>HOTP</spp:AuthType>
</TabStrip.Items>
<TabStrip.ItemTemplate>
<DataTemplate DataType="spp:AuthType">
@@ -78,13 +78,13 @@
<Border Classes="Item" IsVisible="{Binding Platform, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static spp:AuthenticatorPlatform.HOTP}}">
<DockPanel>
<TextBlock Text="令牌刷新间隔时间" />
<NumericUpDown Value="{Binding CodeDigits}" />
<NumericUpDown Value="{Binding Period}" />
</DockPanel>
</Border>
<Border Classes="Item" IsVisible="{Binding Platform, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static spp:AuthenticatorPlatform.HOTP}}">
<DockPanel>
<TextBlock Text="令牌位数" />
<NumericUpDown Value="{Binding Period}" />
<NumericUpDown Value="{Binding CodeDigits}" />
</DockPanel>
</Border>
<Border Classes="Item">
@@ -96,18 +96,18 @@
Content="{Binding Path=Res.GeneralAuth_Code, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<TextBlock
HorizontalAlignment="Center"
IsVisible="{Binding AuthType, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static spp:AuthType.HOTP}}"
IsVisible="{Binding AuthType, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static spp:AuthType.TOTP}}"
Text="{Binding Path=Res.GeneralAuth_VerifySuccessfulImport, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<TextBlock
HorizontalAlignment="Center"
IsVisible="{Binding AuthType, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static spp:AuthType.TOTP}}"
IsVisible="{Binding AuthType, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static spp:AuthType.HOTP}}"
Text="如果有初始值,请输入到下面Code框内,单击“验证”按钮后会生成上次使用的代码。" />
<TextBox
HorizontalContentAlignment="Center"
FontSize="24"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
IsReadOnly="{Binding AuthType, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static spp:AuthType.HOTP}}"
IsReadOnly="{Binding AuthType, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static spp:AuthType.TOTP}}"
Text="{Binding CurrentCode}" />
</StackPanel>
</Border>
@@ -225,12 +225,12 @@
</StackPanel>
<spp:ContentLoader IsLoading="{Binding IsLoading}" IsShowNoResultText="{Binding !!!Auths.Count}">
<spp:ContentLoader.IsVisible>
<!--<spp:ContentLoader.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<CompiledBinding Path="IsVerificationPass" />
<CompiledBinding Path="!IsVerificationPass" />
<CompiledBinding Path="!!Auths.Count" />
</MultiBinding>
</spp:ContentLoader.IsVisible>
</spp:ContentLoader.IsVisible>-->
<spp:ContentLoader.NoResultMessage>
<StackPanel
HorizontalAlignment="Center"
@@ -108,17 +108,13 @@
<!-- 绑定手机 -->
<Panel>
<!--<spp:Snackbar
Title="{Binding Path=Res.Notice_Tray_Title,
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}"
<spp:Snackbar
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
VerticalAlignment="Top"
IsClosable="False"
IsOpen="True"
Message="{Binding Path=Res.Steam_EmailConfirmation,
Mode=OneWay,
Source={x:Static s:ResourceService.Current}}"
Severity="Warning" />-->
Message="{Binding Path=Res.Warning_SteamAuthPhoneBind, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
Severity="Warning" />
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
@@ -18,8 +18,13 @@
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding !IsLogged}"
Spacing="10">
<StackPanel.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<CompiledBinding Path="!IsLogged" />
<CompiledBinding Path="!IsLoading" />
</MultiBinding>
</StackPanel.IsVisible>
<PathIcon
Width="88"
Height="88"
@@ -41,11 +41,6 @@ public sealed partial class PlatformAccount
SetAccountAvatarCommand = ReactiveCommand.Create<IAccount>(async acc =>
{
FilePickerFileType fileTypes = new ValueTuple<string, string[]>[]
{
("Image Files", FileEx.Images.Select(s => $"*{s}").ToArray()),
};
await FilePicker2.PickAsync((path) =>
{
if (!string.IsNullOrEmpty(path) && !string.IsNullOrEmpty(acc.AccountName))
@@ -54,7 +49,7 @@ public sealed partial class PlatformAccount
File.Copy(path, imagePath, true);
acc.ImagePath = imagePath;
}
}, fileTypes);
}, IFilePickerFileType.Images());
});
EditRemarkCommand = ReactiveCommand.Create<IAccount>(async acc =>
{
@@ -72,7 +72,7 @@
<ui:MenuFlyoutItem
Command="{Binding CopyToClipboardCommand, Source={x:Static spp:App.Instance}}"
CommandParameter="{Binding AccountName}"
Text="Steamn 用户名" />
Text="Steam 用户名" />
</ui:MenuFlyoutSubItem>
<ui:MenuFlyoutSubItem IsVisible="{Binding $parent[UserControl].((spp:PlatformAccount)DataContext).IsSteamPlatform}" Text="复制 SteamID">
<ui:MenuFlyoutItem Click="CopySteamIdMenuItem_Click" Text="SteamID [STEAM0:~]">
@@ -5,6 +5,7 @@ namespace BD.WTTS.UI.ViewModels;
public sealed partial class GameListPageViewModel : TabItemViewModel
{
//bool firstLoadGameList = true;
readonly Dictionary<string, string[]> dictPinYinArray = new();
public GameListPageViewModel()
@@ -25,8 +26,7 @@ public sealed partial class GameListPageViewModel : TabItemViewModel
}
IsInstalledFilter = GameLibrarySettings.GameInstalledFilter.Value;
//IsCloudArchiveFilter = GameLibrarySettings.GameInstalledFilter.Value;
IsCloudArchiveFilter = GameLibrarySettings.GameCloudArchiveFilter.Value;
this.WhenValueChanged(x => x.IsInstalledFilter, false)
.Subscribe(s => GameLibrarySettings.GameInstalledFilter.Value = s);
@@ -61,13 +61,19 @@ public sealed partial class GameListPageViewModel : TabItemViewModel
.Bind(out _SteamApps)
.Subscribe(_ =>
{
this.RaisePropertyChanged(nameof(IsSteamAppsEmpty));
////无已安装游戏时,显示提示
//if (firstLoadGameList && SteamConnectService.Current.IsConnectToSteam && IsInstalledFilter && !_SteamApps.Any_Nullable())
//{
// GameLibrarySettings.GameInstalledFilter.Value = false;
// Toast.Show(ToastIcon.Info, "没有检测到已安装游戏");
//}
//if (firstLoadGameList)
//{
// firstLoadGameList = false;
//}
CalcTypeCount();
});
SteamConnectService.Current.WhenAnyValue(x => x.IsLoadingGameList)
.Subscribe(_ => this.RaisePropertyChanged(nameof(IsSteamAppsEmpty)));
ShowHideAppCommand = ReactiveCommand.Create(() =>
{
IWindowManager.Instance.ShowTaskDialogAsync(new HideAppsPageViewModel(), Strings.GameList_HideGameManger,
@@ -91,13 +97,7 @@ public sealed partial class GameListPageViewModel : TabItemViewModel
{
if (IsFirstActivation && !SteamConnectService.Current.SteamApps.Items.Any())
{
//SteamConnectService.Current.Initialize();
Task2.InBackground(SteamConnectService.Current.RefreshGamesListAsync);
//UISettings.GameListGridSize.Subscribe(x =>
//{
// SteamConnectService.Current.SteamApps.Refresh();
//}).AddTo(this);
}
base.Activation();
}
@@ -25,8 +25,6 @@ public sealed partial class GameListPageViewModel : TabItemViewModel
[Reactive]
public string? SearchText { get; set; }
public bool IsSteamAppsEmpty => !SteamConnectService.Current.SteamApps.Items.Any_Nullable() && !SteamConnectService.Current.IsLoadingGameList;
[Reactive]
public ObservableCollection<EnumModel<SteamAppType>>? AppTypeFiltres { get; set; }
@@ -199,7 +199,7 @@
<DropDownButton.Flyout>
<ui:FAMenuFlyout Placement="BottomEdgeAlignedRight">
<ui:MenuFlyoutItem Command="{Binding ShowHideAppCommand}" Text="{Binding Path=Res.Game_Hidden, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:MenuFlyoutItem x:Name="PluginSettingButton" Text="{Binding Path=Res.Settings_Plugin, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<!--<ui:MenuFlyoutItem x:Name="PluginSettingButton" Text="{Binding Path=Res.Settings_Plugin, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />-->
<ui:MenuFlyoutSeparator />
<ui:MenuFlyoutItem IsEnabled="False">
<ui:MenuFlyoutItem.Text>
@@ -215,9 +215,9 @@
</MultiBinding>
</ui:MenuFlyoutItem.Text>
</ui:MenuFlyoutItem>
<ui:MenuFlyoutItem Text="{Binding Path=Res.ViewInStore, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<!--<ui:MenuFlyoutItem Text="{Binding Path=Res.ViewInStore, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:MenuFlyoutItem Text="{Binding Path=Res.UseHelp, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:MenuFlyoutItem Text="{Binding Path=Res.AboutThisPlugin, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
<ui:MenuFlyoutItem Text="{Binding Path=Res.AboutThisPlugin, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />-->
</ui:FAMenuFlyout>
</DropDownButton.Flyout>
</DropDownButton>
@@ -342,21 +342,20 @@
</DockPanel>
</StackPanel>
</spp:PageBase.PaneContent>
<DockPanel>
<spp:ContentLoader
IsLoading="{Binding Source={x:Static s:SteamConnectService.Current}, Path=IsLoadingGameList, Mode=OneWay}"
IsShowNoResultText="{Binding IsSteamAppsEmpty}"
NoResultMessage="{Binding Path=Res.GameList_IsEmpty, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<spp:ContentLoader.GestureRecognizers>
<ScrollGestureRecognizer CanHorizontallyScroll="True" CanVerticallyScroll="True" />
</spp:ContentLoader.GestureRecognizers>
<ItemsRepeater x:Name="GameListRepeater" ItemsSource="{Binding SteamApps}">
<!--
Classes.Compact="{Binding Path=Value, Mode=OneWay, Source={x:Static ms:GameLibrarySettings.GameLibraryLayoutType}, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static spp:GridLayoutType.CompactGrid}}"
Classes.Grid="{Binding Path=Value, Mode=OneWay, Source={x:Static ms:GameLibrarySettings.GameLibraryLayoutType}, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static spp:GridLayoutType.Grid}}"
Classes.List="{Binding Path=Value, Mode=OneWay, Source={x:Static ms:GameLibrarySettings.GameLibraryLayoutType}, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static spp:GridLayoutType.List}}"
-->
<!--<ItemsRepeater.Styles>
<spp:ContentLoader
IsLoading="{Binding Source={x:Static s:SteamConnectService.Current}, Path=IsLoadingGameList, Mode=OneWay}"
IsShowNoResultText="{Binding !!!SteamApps.Count}"
NoResultMessage="{Binding Path=Res.GameList_IsEmpty, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
<spp:ContentLoader.GestureRecognizers>
<ScrollGestureRecognizer CanHorizontallyScroll="True" CanVerticallyScroll="True" />
</spp:ContentLoader.GestureRecognizers>
<ItemsRepeater x:Name="GameListRepeater" ItemsSource="{Binding SteamApps}">
<!--
Classes.Compact="{Binding Path=Value, Mode=OneWay, Source={x:Static ms:GameLibrarySettings.GameLibraryLayoutType}, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static spp:GridLayoutType.CompactGrid}}"
Classes.Grid="{Binding Path=Value, Mode=OneWay, Source={x:Static ms:GameLibrarySettings.GameLibraryLayoutType}, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static spp:GridLayoutType.Grid}}"
Classes.List="{Binding Path=Value, Mode=OneWay, Source={x:Static ms:GameLibrarySettings.GameLibraryLayoutType}, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static spp:GridLayoutType.List}}"
-->
<!--<ItemsRepeater.Styles>
<Style Selector="ItemsRepeater.Grid">
<Setter Property="Layout">
<Setter.Value>
@@ -394,78 +393,77 @@
<Setter Property="MaxHeight" Value="65" />
</Style>
</ItemsRepeater.Styles>-->
<ItemsRepeater.Layout>
<UniformGridLayout
ItemsJustification="Start"
ItemsStretch="Uniform"
MinColumnSpacing="10"
MinItemHeight="310"
MinItemWidth="150"
MinRowSpacing="10" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate DataType="spp:SteamApp">
<!-- Classes.Vertical="{Binding Path=Value, Mode=OneWay, Source={x:Static ms:GameLibrarySettings.GameLibraryLayoutType}, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static spp:GridLayoutType.Grid}}" -->
<spp:AppItem
Title="{Binding DisplayName}"
Classes="Vertical"
ContextFlyout="{StaticResource SharedMenuFlyout}"
MoreFlyout="{StaticResource SharedMenuFlyout}">
<spp:AppItem.Status>
<Border Classes="Status">
<TextBlock Text="{Binding AppId}" />
<ItemsRepeater.Layout>
<UniformGridLayout
ItemsJustification="Start"
ItemsStretch="Uniform"
MinColumnSpacing="10"
MinItemHeight="310"
MinItemWidth="150"
MinRowSpacing="10" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate DataType="spp:SteamApp">
<!-- Classes.Vertical="{Binding Path=Value, Mode=OneWay, Source={x:Static ms:GameLibrarySettings.GameLibraryLayoutType}, Converter={StaticResource EnumEqualValueConverter}, ConverterParameter={x:Static spp:GridLayoutType.Grid}}" -->
<spp:AppItem
Title="{Binding DisplayName}"
Classes="Vertical"
ContextFlyout="{StaticResource SharedMenuFlyout}"
MoreFlyout="{StaticResource SharedMenuFlyout}">
<spp:AppItem.Status>
<Border Classes="Status">
<TextBlock Text="{Binding AppId}" />
</Border>
</spp:AppItem.Status>
<spp:AppItem.Image>
<Panel Width="{Binding $parent[spp:AppItem].Bounds.Width}">
<spp:Image2
Name="AppImage"
DecodeWidth="169"
FallbackSource="avares://BD.WTTS.Client.Plugins.GameList/UI/Assets/defaultappimage.png"
RenderOptions.BitmapInterpolationMode="HighQuality"
Source="{Binding LibraryGridStream^}"
Stretch="UniformToFill" />
<TextBlock
Margin="8,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
IsVisible="{Binding #AppImage.IsFailed}"
Text="{Binding DisplayName}"
TextAlignment="Center"
TextWrapping="WrapWithOverflow"
Theme="{StaticResource BodyStrongTextBlockStyle}" />
<Border
Margin="10,0,10,-3"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}"
CornerRadius="3"
IsVisible="{Binding IsInstalled}">
<DockPanel Margin="10,3" HorizontalAlignment="Center">
<TextBlock
Margin="0,0,5,0"
VerticalAlignment="Center"
FontSize="11"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
IsVisible="{Binding InstalledDrive, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}"
Text="{Binding InstalledDrive}" />
<TextBlock
VerticalAlignment="Center"
FontSize="11"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="{Binding SizeOnDisk, Converter={StaticResource StringFormatConverter}, ConverterParameter=size}"
TextWrapping="Wrap" />
</DockPanel>
</Border>
</spp:AppItem.Status>
<spp:AppItem.Image>
<Panel Width="{Binding $parent[spp:AppItem].Bounds.Width}">
<spp:Image2
Name="AppImage"
DecodeWidth="169"
FallbackSource="avares://BD.WTTS.Client.Plugins.GameList/UI/Assets/defaultappimage.png"
RenderOptions.BitmapInterpolationMode="HighQuality"
Source="{Binding LibraryGridStream^}"
Stretch="UniformToFill" />
<TextBlock
Margin="8,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
IsVisible="{Binding #AppImage.IsFailed}"
Text="{Binding DisplayName}"
TextAlignment="Center"
TextWrapping="WrapWithOverflow"
Theme="{StaticResource BodyStrongTextBlockStyle}" />
</Panel>
</spp:AppItem.Image>
</spp:AppItem>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
<Border
Margin="10,0,10,-3"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}"
CornerRadius="3"
IsVisible="{Binding IsInstalled}">
<DockPanel Margin="10,3" HorizontalAlignment="Center">
<TextBlock
Margin="0,0,5,0"
VerticalAlignment="Center"
FontSize="11"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
IsVisible="{Binding InstalledDrive, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}"
Text="{Binding InstalledDrive}" />
<TextBlock
VerticalAlignment="Center"
FontSize="11"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="{Binding SizeOnDisk, Converter={StaticResource StringFormatConverter}, ConverterParameter=size}"
TextWrapping="Wrap" />
</DockPanel>
</Border>
</Panel>
</spp:AppItem.Image>
</spp:AppItem>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</spp:ContentLoader>
</DockPanel>
</spp:ContentLoader>
</spp:PageBase>
@@ -6,11 +6,13 @@ using System.Linq;
namespace BD.WTTS.UI.ViewModels;
public sealed class IdleAppsPageViewModel : ViewModelBase
public sealed partial class IdleAppsPageViewModel : ViewModelBase
{
readonly ISteamService SteamTool = ISteamService.Instance;
readonly ISteamIdleCardService IdleCard = ISteamIdleCardService.Instance;
readonly SteamLoginState steamLoginState = new SteamLoginState();
public IdleAppsPageViewModel()
{
this.WhenPropertyChanged(x => x.IsAutoNextOn)
@@ -19,65 +21,11 @@ public sealed class IdleAppsPageViewModel : ViewModelBase
RunOrStopAutoNext(x.Value);
this.IsAutoNextOnTxt = x.Value ? Strings.Idle_StopAutoNext : Strings.Idle_OpenAutoNext;
});
this.IdleRunStartOrStop = ReactiveCommand.Create(IdleRunStartOrStop_Click);
this.IdleManualRunNext = ReactiveCommand.Create(ManualRunNext);
}
[Reactive]
public bool RunLoaingState { get; set; }
[Reactive]
public bool RunState { get; set; }
[Reactive]
public string? RuningCountTxt { get; set; }
[Reactive]
public ObservableCollection<SteamApp> IdleGameList { get; set; } = new();
/// <summary>
/// 当前挂卡游戏
/// </summary>
[Reactive]
public SteamApp? CurrentIdle { get; set; }
/// <summary>
/// 挂卡规则
/// </summary>
[Reactive]
public IdleRule IdleRule { get; set; }
/// <summary>
/// 挂卡顺序
/// </summary>
[Reactive]
public IdleSequentital IdleSequentital { get; set; }
/// <summary>
/// 自动运行下一个游戏
/// </summary>
[Reactive]
public bool IsAutoNextOn { get; set; }
/// <summary>
/// 自动运行下一个游戏文本展示
/// </summary>
[Reactive]
public string? IsAutoNextOnTxt { get; set; }
#region 魔改
/// <summary>
/// 最少游戏时间 hours
/// </summary>
[Reactive]
private double MinRunTime { get; set; } = 2;
/// <summary>
/// 自动切换游戏时间间隔 ms
/// </summary>
[Reactive]
private double SwitchTime { get; set; } = 500;
#endregion
public override void Activation()
{
base.Activation();
@@ -101,6 +49,7 @@ public sealed class IdleAppsPageViewModel : ViewModelBase
if (RunState)
{
await SteamConnectService.Current.RefreshGamesListAsync();
await ReadyToGoIdle();
}
else
@@ -168,13 +117,17 @@ public sealed class IdleAppsPageViewModel : ViewModelBase
private async Task SteamAppsSort()
{
if (IdleSequentital == IdleSequentital.Mostvalue)
Badges = await IdleCard.GetBadgesAsync(SteamConnectService.Current.CurrentSteamUser!.SteamId64.ToString(), true);
else
Badges = await IdleCard.GetBadgesAsync(SteamConnectService.Current.CurrentSteamUser!.SteamId64.ToString());
Badges = Badges.Where(x => x.CardsRemaining != 0); // 过滤可掉落卡片的游戏
var appid_sorts = Enumerable.Empty<int>();
if (IdleSequentital == IdleSequentital.Default)
IdleGameList.Add(SteamConnectService.Current.SteamApps.Items);
appid_sorts = Badges.Select(s => s.AppId);
else
{
Badges = await IdleCard.GetBadgesAsync(SteamConnectService.Current.CurrentSteamUser!.SteamId64.ToString());
Badges = Badges.Where(x => x.CardsRemaining != 0); // 过滤可掉落卡片的游戏
var appid_sorts = Enumerable.Empty<int>();
switch (IdleSequentital)
{
case IdleSequentital.LeastCards:
@@ -189,10 +142,12 @@ public sealed class IdleAppsPageViewModel : ViewModelBase
default:
break;
}
var apps = SteamConnectService.Current.SteamApps.Items.OrderBy(o => appid_sorts.ToList().FindIndex(x => x == o.AppId)).ToList();
IdleGameList.Add(apps);
}
var apps = SteamConnectService.Current.SteamApps.Items
.Where(x => appid_sorts.Contains((int)x.AppId))
.OrderBy(o => appid_sorts.ToList().FindIndex(x => x == o.AppId))
.ToList();
IdleGameList.Add(apps);
}
private async Task ReadyToGoIdle()
@@ -238,13 +193,13 @@ public sealed class IdleAppsPageViewModel : ViewModelBase
var multi = IdleGameList.Where(x => canIdles.Contains((int)x.AppId));
if (multi.Count() >= 2)
{
PauseAutoNext(false);
StartSoloIdle(multi.First());
PauseAutoNext(true);
StartMultipleIdle();
}
else
{
PauseAutoNext(true);
StartMultipleIdle();
PauseAutoNext(false);
StartSoloIdle(multi.First());
}
}
}
@@ -259,7 +214,6 @@ public sealed class IdleAppsPageViewModel : ViewModelBase
IdleGameList.Remove(CurrentIdle);
StartIdle();
}
}
/// <summary>
@@ -382,6 +336,9 @@ public sealed class IdleAppsPageViewModel : ViewModelBase
await AutoNextTask();
await Task.Delay(TimeSpan.FromSeconds(SwitchTime), CancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
}
catch (Exception ex)
{
Toast.LogAndShowT(ex);
@@ -0,0 +1,70 @@
using BD.SteamClient.Models;
using BD.SteamClient.Models.Idle;
using BD.SteamClient.Services;
namespace BD.WTTS.UI.ViewModels;
public sealed partial class IdleAppsPageViewModel
{
public ICommand IdleRunStartOrStop { get; }
public ICommand IdleManualRunNext { get; }
[Reactive]
public bool RunLoaingState { get; set; }
[Reactive]
public bool RunState { get; set; }
[Reactive]
public string? RuningCountTxt { get; set; }
[Reactive]
public ObservableCollection<SteamApp> IdleGameList { get; set; } = new();
/// <summary>
/// 当前挂卡游戏
/// </summary>
[Reactive]
public SteamApp? CurrentIdle { get; set; }
/// <summary>
/// 挂卡规则
/// </summary>
[Reactive]
public IdleRule IdleRule { get; set; }
/// <summary>
/// 挂卡顺序
/// </summary>
[Reactive]
public IdleSequentital IdleSequentital { get; set; }
/// <summary>
/// 自动运行下一个游戏
/// </summary>
[Reactive]
public bool IsAutoNextOn { get; set; }
/// <summary>
/// 自动运行下一个游戏文本展示
/// </summary>
[Reactive]
public string? IsAutoNextOnTxt { get; set; }
#region 魔改
/// <summary>
/// 最少游戏时间 hours
/// </summary>
[Reactive]
private double MinRunTime { get; set; } = 2;
/// <summary>
/// 自动切换游戏时间间隔 ms
/// </summary>
[Reactive]
private double SwitchTime { get; set; } = 500;
#endregion
}
+8 -7
View File
@@ -461,15 +461,16 @@ public static class PluginsCore
static int OrderPlugin(IPlugin plugin) => plugin.UniqueEnglishName switch
{
Accelerator => 1,
GameAccount => 2,
GameList => 3,
ArchiSteamFarmPlus => 5,
Authenticator => 4,
GameTools => 6,
SteamIdleCard => 7,
Accelerator => 25,
GameAccount => 35,
GameList => 45,
Authenticator => 55,
ArchiSteamFarmPlus => 65,
SteamIdleCard => 75,
GameTools => 85,
_ => ushort.MaxValue,
};
var comparer = ComparerBuilder.For<PluginResult<IPlugin>>()
.OrderBy(x => x.IsDisable)
.ThenBy(x => x.Data.IsOfficial, descending: true)
+20 -2
View File
@@ -2673,7 +2673,7 @@ namespace BD.WTTS.Client.Resources {
}
/// <summary>
/// 查找类似 没有获取到任何游戏数据 的本地化字符串。
/// 查找类似 没有获取到游戏数据 的本地化字符串。
/// </summary>
public static string GameList_IsEmpty {
get {
@@ -3178,7 +3178,7 @@ namespace BD.WTTS.Client.Resources {
}
/// <summary>
/// 查找类似 输入您的令牌 Key 或二维码图片 url 链接 的本地化字符串。
/// 查找类似 输入网站提示的令牌导入二维码识别后的 Key 值 的本地化字符串。
/// </summary>
public static string GeneralAuth_EnterkeyOrLink {
get {
@@ -4811,6 +4811,15 @@ namespace BD.WTTS.Client.Resources {
}
}
/// <summary>
/// 查找类似 安全设置 的本地化字符串。
/// </summary>
public static string PersonalSafe {
get {
return ResourceManager.GetString("PersonalSafe", resourceCulture);
}
}
/// <summary>
/// 查找类似 选择图片缩放方式 的本地化字符串。
/// </summary>
@@ -7828,6 +7837,15 @@ namespace BD.WTTS.Client.Resources {
}
}
/// <summary>
/// 查找类似 目前导入时绑定手机会有收不到验证码的问题,建议先去账号中心绑定手机号之后再进行导入 的本地化字符串。
/// </summary>
public static string Warning_SteamAuthPhoneBind {
get {
return ResourceManager.GetString("Warning_SteamAuthPhoneBind", resourceCulture);
}
}
/// <summary>
/// 查找类似 UUID 及 SteamGuard 不可为空 的本地化字符串。
/// </summary>
+8 -2
View File
@@ -420,7 +420,7 @@
<value>安装</value>
</data>
<data name="GameList_IsEmpty" xml:space="preserve">
<value>没有获取到任何游戏数据</value>
<value>没有获取到游戏数据</value>
</data>
<data name="GameList_IsLoading" xml:space="preserve">
<value>正在加载...</value>
@@ -1924,7 +1924,7 @@
<value>生成 Code</value>
</data>
<data name="GeneralAuth_EnterkeyOrLink" xml:space="preserve">
<value>输入您的令牌 Key 或二维码图片 url 链接</value>
<value>输入网站提示的令牌导入二维码识别后的 Key 值</value>
</data>
<data name="GeneralAuth_ImportText" xml:space="preserve">
<value>导入文本</value>
@@ -2160,6 +2160,9 @@
<data name="Warning_NotAuthProvided" xml:space="preserve">
<value>请先设置令牌自定义密码</value>
</data>
<data name="Warning_SteamAuthPhoneBind" xml:space="preserve">
<value>目前导入时绑定手机会有收不到验证码的问题,建议先去账号中心绑定手机号之后再进行导入</value>
</data>
<data name="Warning_OnlySupportSteamAuth" xml:space="preserve">
<value>解绑功能目前仅支持 Steam 令牌</value>
</data>
@@ -2786,5 +2789,8 @@
</data>
<data name="GameRelated_Borderless_ToWallerpaperTip" xml:space="preserve">
<value>置于壁纸层的游戏可能会无法操控,这个问题可以解决但是目前开发精力不在这,目前这个功能就这样了。游戏结束后桌面会依然留下冻结的画面,这时可以点击刷新壁纸来恢复默认壁纸。并且将窗口放置于桌面壁纸层功能与 Wallpaper Engine 和其它动态桌面软件不兼容,使用时注意关掉 Wallpaper Engine 等软件。</value>
</data>
<data name="PersonalSafe" xml:space="preserve">
<value>安全设置</value>
</data>
</root>
@@ -9,6 +9,7 @@ partial class LinuxPlatformServiceImpl
var shellContent = new StringBuilder();
if (state)
{
var noProxyHostName = $"[{string.Join(",", IPlatformService.GetNoProxyHostName.Select(x => $"'{x}'"))}]";
var hasIpAndProt = ip != null && port >= 0;
shellContent.AppendLine("gsettings set org.gnome.system.proxy mode 'manual'");
if (hasIpAndProt)
@@ -17,6 +18,7 @@ partial class LinuxPlatformServiceImpl
shellContent.AppendLine($"gsettings set org.gnome.system.proxy.http port {port}");
shellContent.AppendLine($"gsettings set org.gnome.system.proxy.https host '{ip}'");
shellContent.AppendLine($"gsettings set org.gnome.system.proxy.https port {port}");
shellContent.AppendLine($"gsettings set org.gnome.system.proxy ignore-hosts \"{noProxyHostName}\"");
}
}
else
@@ -10,6 +10,7 @@ partial class MacCatalystPlatformServiceImpl
IPlatformService @this = this;
var stringList = @this.GetMacOSNetworkSetup();
var shellContent = new StringBuilder();
var noProxyHostName = state ? $"{string.Join(" ", IPlatformService.GetNoProxyHostName)}" : "";
foreach (var item in stringList)
{
if (item.Trim().Length > 0)
@@ -23,6 +24,7 @@ partial class MacCatalystPlatformServiceImpl
shellContent.AppendLine($"networksetup -setwebproxystate '{item}' on");
shellContent.AppendLine($"networksetup -setsecurewebproxy '{item}' '{bindIP}' {port}");
shellContent.AppendLine($"networksetup -setsecurewebproxystate '{item}' on");
shellContent.AppendLine($"networksetup -setproxybypassdomains '{item}' '{noProxyHostName}'");
}
else
{
@@ -119,8 +119,29 @@ partial class ScheduledTaskServiceImpl
{
if (process != null)
{
process.KillEntireProcessTree();
process.Dispose();
try
{
process.KillEntireProcessTree();
}
catch
{
try
{
process.Kill();
}
catch
{
}
}
try
{
process.Dispose();
}
catch
{
}
}
cts_runPowerShellSinglePipe = null;
}
@@ -5,8 +5,6 @@ namespace BD.WTTS.Services.Implementation;
partial class WindowsPlatformServiceImpl
{
const string ProxyOverride = "\"ProxyOverride\"=\"localhost;127.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;192.168.*\"";
internal static bool SetAsSystemProxyStatus;
public async Task<bool> SetAsSystemProxyAsync(bool state, IPAddress? ip, int port)
@@ -17,13 +15,14 @@ partial class WindowsPlatformServiceImpl
var proxyEnable = $"\"ProxyEnable\"=dword:{(state ? "00000001" : "00000000")}";
var hasIpAndProt = ip != null && port >= 0;
var proxyServer = hasIpAndProt ? $"\"proxyServer\"=\"{ip}:{port}\"" : "";
var noProxyHostName = state ? $"\"ProxyOverride\"=\"{string.Join(";", IPlatformService.GetNoProxyHostName)}\"" : "";
string contents =
$"""
Windows Registry Editor Version 5.00
; {AssemblyInfo.Trademark} BD.WTTS.Services.Implementation.WindowsPlatformServiceImpl.SetAsSystemProxy
[HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings]
{proxyEnable}
{(state ? $"{ProxyOverride}{Environment.NewLine}" : "")}{proxyServer}
{(state ? $"{noProxyHostName}{Environment.NewLine}" : "")}{proxyServer}
""";
var path = IOPath.GetCacheFilePath(CacheTempDirName, "SwitchProxy", FileEx.Reg);
var r = await StartProcessRegeditAsync(path, contents, markKey: nameof(SetAsSystemProxyAsync), markValue: state.ToString());
@@ -45,10 +45,16 @@ public sealed class UserService : ReactiveObject
{
var cUser = await userManager.GetCurrentUserAsync();
if (cUser.HasValue()) return;
var vm = new LoginOrRegisterWindowViewModel();
await windowManager.ShowTaskDialogAsync(vm, vm.Title, isOkButton: false);
//INavigationService.Instance.Navigate(typeof(LoginOrRegisterWindowViewModel), NavigationTransitionEffect.DrillIn);
if (Random2.Next(2) == 1)
{
var vm = new LoginOrRegisterWindowViewModel();
await windowManager.ShowTaskDialogAsync(vm, vm.Title, isOkButton: false);
}
else
{
INavigationService.Instance.Navigate(typeof(LoginOrRegisterWindowViewModel), NavigationTransitionEffect.DrillIn);
}
}
public void NavigateUserCenterPage()
@@ -237,15 +237,14 @@ public sealed class SteamConnectService
Name = item.Value,
});
}
var t = new Task(() =>
Task2.InBackground(() =>
{
foreach (var item in RuningSteamApps.Values)
{
if (item.Process == null)
if (item.Process == null || item.Process.HasExited)
item.StartSteamAppProcess();
}
});
t.Start();
}
}
@@ -617,9 +616,8 @@ public sealed class SteamConnectService
// 释放托管状态(托管对象)
foreach (var app in Current.RuningSteamApps.Values)
{
if (app.Process != null)
if (!app.Process.HasExited)
app.Process.KillEntireProcessTree();
if (app.Process != null && !app.Process.HasExited)
app.Process.KillEntireProcessTree();
}
DisposeSteamClient();
}
@@ -4,6 +4,35 @@ namespace BD.WTTS.Services;
partial interface IPlatformService
{
/// <summary>
/// 获取 排除代理地址
/// </summary>
protected static string[] GetNoProxyHostName => new string[] {
"localhost",
"127.*",
"10.*",
"172.16.*",
"172.17.*",
"172.18.*",
"172.19.*",
"172.20.*",
"172.21.*",
"172.22.*",
"172.23.*",
"172.24.*",
"172.25.*",
"172.26.*",
"172.27.*",
"172.28.*",
"172.29.*",
"172.30.*",
"172.31.*",
"192.168.*",
Constants.Urls.OfficialApiHostName,
Constants.Urls.OfficialShopApiHostName
};
/// <summary>
/// 设置启用或关闭系统代理
/// </summary>
+10 -3
View File
@@ -191,9 +191,16 @@ public abstract partial class Startup
#if WINDOWS || LINUX
#if DEBUG
// 调试时移动本机库到 native,通常指定了单个 RID(RuntimeIdentifier)
// 后本机库将位于程序根目录上否则将位于 runtimes 文件夹中
GlobalDllImportResolver.MoveFiles();
try
{
// 调试时移动本机库到 native,通常指定了单个 RID(RuntimeIdentifier)
// 后本机库将位于程序根目录上否则将位于 runtimes 文件夹中
GlobalDllImportResolver.MoveFiles();
}
catch
{
}
#if STARTUP_WATCH_TRACE || DEBUG
WatchTrace.Record("CustomAppDomain.MoveFiles");
#endif
@@ -6,12 +6,12 @@ public interface IWindowViewModel : IPageViewModel
/// <summary>
/// 关闭当前 ViewModel 绑定的窗口
/// </summary>
Action? Close { get; set; }
Action Close { get; set; }
/// <summary>
/// 显示当前 ViewModel 绑定的窗口
/// </summary>
Action? Show { get; set; }
Action Show { get; set; }
abstract void OnClosing(object? sender, CancelEventArgs e);
}
@@ -13,13 +13,6 @@ public sealed partial class SettingsPageViewModel : TabItemViewModel
this.WhenValueChanged(x => x.SelectLanguage, false)
.Subscribe(x => UISettings.Language.Value = x.Key);
UpdateChannels = new[]
{
UpdateChannelType.Auto,
UpdateChannelType.GitHub,
UpdateChannelType.Official,
};
OpenFolder_Click = ReactiveCommand.Create<string>(OpenFolder);
CheckUpdate_Click = ReactiveCommand.Create(CheckUpdate);
@@ -41,12 +34,13 @@ public sealed partial class SettingsPageViewModel : TabItemViewModel
SelectImage_Click = ReactiveCommand.CreateFromTask(async () =>
{
FilePickerFileType fileTypes = new ValueTuple<string, string[]>[]
{
("Image Files", FileEx.Images.Select(s => $"*{s}").ToArray()),
//("All Files", new[] { "*", }),
};
await FilePicker2.PickAsync(SetBackgroundImagePath, fileTypes);
//FilePickerFileType fileTypes = new ValueTuple<string, string[]>[]
//{
// ("Image Files", FileEx.Images.Select(s => $"*{s}").ToArray()),
// //("All Files", new[] { "*", }),
//};
//await FilePicker2.PickAsync(SetBackgroundImagePath, fileTypes);
await FilePicker2.PickAsync(SetBackgroundImagePath, IFilePickerFileType.Images());
});
ResetImage_Click = ReactiveCommand.Create(() => SetBackgroundImagePath(null));
@@ -42,8 +42,6 @@ public sealed partial class SettingsPageViewModel : TabItemViewModel
[IgnoreDataMember, MPIgnore, MP2Ignore, N_JsonIgnore, S_JsonIgnore]
public ICommand? OpenFolder_Click { get; }
public IReadOnlyCollection<UpdateChannelType> UpdateChannels { get; }
/// <summary>
/// 点击间隔时间(秒)Windows 11 上资源管理器启动缓慢,设置一个间隔时间避免频繁点击
/// </summary>
@@ -10,6 +10,8 @@ public partial class LoginOrRegisterWindowViewModel : WindowViewModel, SendSmsUI
{
Title = DisplayName;
Close = INavigationService.Instance.GoBack;
ChooseChannel = ReactiveCommand.CreateFromTask<string>(async channel_ =>
{
if (Enum.TryParse<ExternalLoginChannel>(channel_, out var channel))
@@ -1,6 +1,6 @@
// ReSharper disable once CheckNamespace
using dotnetCampus.Ipc.CompilerServices.Attributes;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.UI.ViewModels;
public sealed partial class MainWindowViewModel : WindowViewModel
@@ -63,27 +63,30 @@ public sealed partial class MainWindowViewModel : WindowViewModel
.Where(static x => x != null)
.SelectMany(static x => x!);
var sortTabSettings = UISettings.SortMenuTabs.Value;
int OrderBy(MenuTabItemViewModel m)
int OrderBy(KeyValuePair<int, MenuTabItemViewModel> m)
{
if (sortTabSettings != null)
{
var i = 0;
int i = byte.MaxValue;
foreach (var item in sortTabSettings)
{
if (item == m.Id)
return i;
i++;
if (item == m.Value.Id)
return i++;
}
}
return int.MaxValue;
return m.Key;
}
var comparer = ComparerBuilder.For<MenuTabItemViewModel>()
var comparer = ComparerBuilder.For<KeyValuePair<int, MenuTabItemViewModel>>()
.OrderBy(OrderBy)
.ThenBy(x => x.Id);
var sortTabs = new SortedSet<MenuTabItemViewModel>(comparer);
.ThenBy(x => x.Value.Id);
var sortTabs = new SortedSet<KeyValuePair<int, MenuTabItemViewModel>>(comparer);
int i = 0;
foreach (var item in tabs)
sortTabs.Add(item);
tabItems = tabItems.Concat(sortTabs);
{
sortTabs.Add(new(i, item));
i++;
}
tabItems = tabItems.Concat(sortTabs.Select(x => x.Value));
}
TabItems = new ObservableCollection<TabItemViewModel>(tabItems);
@@ -3,8 +3,11 @@ using AppResources = BD.WTTS.Client.Resources.Strings;
// ReSharper disable once CheckNamespace
namespace BD.WTTS.UI.ViewModels;
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class MenuTabItemViewModel : TabItemViewModel, IExplicitHasValue
{
string DebuggerDisplay => $"{Id} - {Name}";
public MenuTabItemViewModel(IPlugin plugin, string resourceKeyOrName)
{
var pluginUniqueEnglishName = plugin.UniqueEnglishName;
@@ -18,12 +18,12 @@ public partial class WindowViewModel : PageViewModel, IWindowViewModel
/// <summary>
/// 关闭当前 ViewModel 绑定的窗口
/// </summary>
public virtual Action? Close { get; set; }
public virtual Action Close { get; set; }
/// <summary>
/// 显示当前 ViewModel 绑定的窗口
/// </summary>
public virtual Action? Show { get; set; }
public virtual Action Show { get; set; }
/// <summary>
/// 隐藏当前 ViewModel 绑定的窗口