Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c16daafba | ||
|
|
2acc0a95e0 | ||
|
|
8072b16d42 | ||
|
|
9766e9e71b | ||
|
|
d39f63db65 | ||
|
|
fd6235f110 | ||
|
|
4a9a53dda8 | ||
|
|
4668e26b65 | ||
|
|
c5f24ae32c | ||
|
|
1acf872e80 | ||
|
|
82ea1a62e9 | ||
|
|
04b0c0e433 | ||
|
|
8dfb37bf8b | ||
|
|
a19b3128db | ||
|
|
f70b349229 | ||
|
|
ed104071c2 | ||
|
|
21ae674895 | ||
|
|
500a87d62f | ||
|
|
d1ae1787b7 | ||
|
|
5742158c83 | ||
|
|
235525a9f3 | ||
|
|
329340ae2d | ||
|
|
e8e6808930 | ||
|
|
a77fb08a04 | ||
|
|
c05fe78d2d | ||
|
|
7b6b8a9b2d | ||
|
|
3886a96f61 | ||
|
|
dfb64175de | ||
|
|
15dede2c6b | ||
|
|
65c647d704 | ||
|
|
76ce429aa9 | ||
|
|
b82bada2e9 | ||
|
|
e7dbf43c48 | ||
|
|
9f981c2ef5 | ||
|
|
89e957be3a | ||
|
|
5e508aa9d2 | ||
|
|
3d261c3603 | ||
|
|
d4bd07364a | ||
|
|
25f6f108c6 | ||
|
|
e908592859 | ||
|
|
3464437385 | ||
|
|
a06002ee72 | ||
|
|
d6b01686f2 | ||
|
|
a0706b9015 | ||
|
|
1d5e64498f | ||
|
|
bb2bb9fc26 | ||
|
|
f76005378c | ||
|
|
f0a87611af | ||
|
|
0feeeeddd1 | ||
|
|
0ddef23c2f | ||
|
|
623f892810 | ||
|
|
b29d826c1e | ||
|
|
cae9f82372 | ||
|
|
33f780d6fb | ||
|
|
36740cd639 | ||
|
|
b9ba4a8239 | ||
|
|
c4d9622ef0 |
@@ -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
|
||||
|
||||
@@ -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) 或更高版本
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 895 B After Width: | Height: | Size: 567 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 867 B |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 3.5 KiB |
@@ -14,7 +14,7 @@ public static partial class AssemblyInfo
|
||||
/// <summary>
|
||||
/// 预览版本号,范围 1~9
|
||||
/// </summary>
|
||||
const string ver_for_preview = "2";
|
||||
const string ver_for_preview = "5";
|
||||
|
||||
/// <summary>
|
||||
/// RC 版本号,范围 0~9
|
||||
|
||||
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 628 B After Width: | Height: | Size: 567 B |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -58,6 +58,7 @@ sealed partial class Program : Startup
|
||||
// Essentials
|
||||
#if LINUX
|
||||
services.TryAddSingleton<IApplicationVersionService, Essentials_AppVerS>();
|
||||
services.AddSingleton<IDeviceInfoPlatformService, LinuxDeviceInfoPlatformServiceImpl>();
|
||||
#else
|
||||
#if WINDOWS
|
||||
#if AVALONIA
|
||||
@@ -252,6 +253,30 @@ sealed partial class Program : Startup
|
||||
string IApplicationVersionService.AssemblyTrademark => AssemblyInfo.Trademark;
|
||||
}
|
||||
|
||||
#if LINUX
|
||||
sealed class LinuxDeviceInfoPlatformServiceImpl : IDeviceInfoPlatformService
|
||||
{
|
||||
public string Model => "";
|
||||
|
||||
public string Manufacturer => "";
|
||||
|
||||
public string Name => "";
|
||||
|
||||
public string VersionString => Environment.OSVersion.VersionString;
|
||||
|
||||
public DeviceType DeviceType => DeviceType.Physical;
|
||||
|
||||
public bool IsChromeOS => false;
|
||||
|
||||
public bool IsWinUI => false;
|
||||
|
||||
public bool IsUWP => false;
|
||||
|
||||
public DeviceIdiom Idiom => DeviceIdiom.Desktop;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if AVALONIA && WINDOWS
|
||||
sealed class AvaWinDeviceInfoPlatformServiceImpl :
|
||||
Common.Services.Implementation.Essentials.DeviceInfoPlatformServiceImpl
|
||||
|
||||
@@ -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,4 +1,6 @@
|
||||
#if LINUX
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
|
||||
namespace BD.WTTS.Services.Implementation;
|
||||
|
||||
public sealed class AvaloniaClipboardPlatformService : IClipboardPlatformService
|
||||
@@ -28,7 +30,9 @@ public sealed class AvaloniaClipboardPlatformService : IClipboardPlatformService
|
||||
var clipboard = topLevel.Clipboard;
|
||||
if (clipboard != null)
|
||||
{
|
||||
await clipboard.SetTextAsync(text);
|
||||
//不能用 await 等待 Linux 上不知啥原因导致卡死
|
||||
clipboard.SetTextAsync(text).GetAwaiter();
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,78 +19,205 @@ 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 FilePickerFileType.IFilePickerFileTypeWithName @interface)
|
||||
if (fileTypes is AvaloniaFilePickerFileTypeFilter ava)
|
||||
{
|
||||
var items = ava.Values;
|
||||
if (items != null)
|
||||
{
|
||||
var query = from m in items
|
||||
select new APS_FilePickerFileType(m.Name)
|
||||
{
|
||||
Patterns = m.Patterns,
|
||||
AppleUniformTypeIdentifiers = m.AppleUniformTypeIdentifiers,
|
||||
MimeTypes = m.MimeTypes,
|
||||
};
|
||||
return query.ToArray();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
else if (fileTypes is FilePickerFileTypeWrapper impl)
|
||||
{
|
||||
return impl.Values;
|
||||
}
|
||||
else if (fileTypes is FilePickerFileType.IFilePickerFileTypeWithName @interface)
|
||||
{
|
||||
var values = @interface.GetFileTypes();
|
||||
if (values.Any())
|
||||
@@ -103,7 +230,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,6 +1,8 @@
|
||||
using Avalonia.Controls;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using FluentAvalonia.UI.Media.Animation;
|
||||
using FluentAvalonia.UI.Navigation;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace BD.WTTS.Services.Implementation;
|
||||
|
||||
@@ -50,11 +52,20 @@ public sealed class NavigationService : INavigationService
|
||||
_overlayHost = p;
|
||||
}
|
||||
|
||||
public void Navigate(Type? t, NavigationTransitionEffect effect = NavigationTransitionEffect.None)
|
||||
public void Navigate(Type? t, NavigationTransitionEffect effect = NavigationTransitionEffect.None, bool useCache = true)
|
||||
{
|
||||
_frame.ThrowIsNull();
|
||||
|
||||
var navOptions = new FrameNavigationOptions
|
||||
{
|
||||
IsNavigationStackEnabled = useCache,
|
||||
TransitionInfoOverride = GetNavigationTransitionInfo(effect)
|
||||
};
|
||||
|
||||
if (t == null)
|
||||
{
|
||||
_frame?.Navigate(typeof(ErrorPage), null, GetNavigationTransitionInfo(effect));
|
||||
navOptions.IsNavigationStackEnabled = false;
|
||||
_frame.NavigateToType(typeof(ErrorPage), null, navOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -66,19 +77,45 @@ public sealed class NavigationService : INavigationService
|
||||
|
||||
if (t == null)
|
||||
{
|
||||
_frame?.Navigate(typeof(ErrorPage), null, GetNavigationTransitionInfo(effect));
|
||||
navOptions.IsNavigationStackEnabled = false;
|
||||
_frame.NavigateToType(typeof(ErrorPage), null, navOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_frame?.Content?.GetType() != t)
|
||||
if (_frame.Content?.GetType() != t)
|
||||
{
|
||||
_frame?.Navigate(t, null, GetNavigationTransitionInfo(effect));
|
||||
_frame.NavigateToType(t, null, navOptions);
|
||||
//if (!useCache && navOptions.IsNavigationStackEnabled)
|
||||
//{
|
||||
// _frame.BackStack.Remove(_frame.BackStack.Last());
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
public void GoBack()
|
||||
public void GoBack(Type? t = null)
|
||||
{
|
||||
_frame?.GoBack();
|
||||
if (t == null)
|
||||
{
|
||||
_frame?.GoBack();
|
||||
return;
|
||||
}
|
||||
|
||||
if (t.IsSubclassOf(typeof(ViewModelBase)))
|
||||
{
|
||||
var pageType = GetViewModelToPageContent(t, false);
|
||||
t = pageType as Type;
|
||||
}
|
||||
|
||||
if (t == null)
|
||||
{
|
||||
_frame?.GoBack();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_frame?.CurrentSourcePageType == t)
|
||||
{
|
||||
_frame?.GoBack();
|
||||
}
|
||||
}
|
||||
|
||||
public void NavigateFromContext(object dataContext, NavigationTransitionEffect effect = NavigationTransitionEffect.None)
|
||||
@@ -105,7 +142,6 @@ public sealed class NavigationService : INavigationService
|
||||
public void ClearOverlay()
|
||||
{
|
||||
_overlayHost?.Children.Clear();
|
||||
|
||||
}
|
||||
|
||||
private static NavigationTransitionInfo GetNavigationTransitionInfo(NavigationTransitionEffect effect)
|
||||
|
||||
@@ -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,14 +15,13 @@
|
||||
<ControlTemplate>
|
||||
<Panel>
|
||||
<StackPanel
|
||||
Name="LoadedContentPresenter"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{TemplateBinding IsLoading}"
|
||||
Spacing="8">
|
||||
<spp:Image2
|
||||
Width="150"
|
||||
Height="150"
|
||||
Width="100"
|
||||
Height="100"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality"
|
||||
@@ -32,6 +31,14 @@
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding Path=Res.GameList_IsLoading, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
IsVisible="{TemplateBinding CustomLoadingText,
|
||||
Converter={StaticResource IsNullConverter},
|
||||
ConverterParameter=invert}"
|
||||
Text="{TemplateBinding CustomLoadingText}"
|
||||
Theme="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
<Border
|
||||
Width="35"
|
||||
Height="35"
|
||||
@@ -46,13 +53,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 +66,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>
|
||||
|
||||
@@ -157,6 +157,7 @@
|
||||
<TextBlock
|
||||
Name="ControlNameTextBlock"
|
||||
FontSize="36"
|
||||
MaxLines="2"
|
||||
Text="{TemplateBinding Title}"
|
||||
Theme="{StaticResource TitleTextBlockStyle}">
|
||||
<TextBlock.Transitions>
|
||||
@@ -174,6 +175,7 @@
|
||||
<TextBlock
|
||||
FontStyle="Italic"
|
||||
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"
|
||||
MaxLines="2"
|
||||
Text="{TemplateBinding Subtitle}"
|
||||
Theme="{StaticResource CaptionTextBlockStyle}" />
|
||||
</Button>
|
||||
@@ -182,6 +184,7 @@
|
||||
FontStyle="Italic"
|
||||
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
|
||||
IsVisible="False"
|
||||
MaxLines="4"
|
||||
Text="{Binding Path=Description, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Theme="{StaticResource CaptionTextBlockStyle}" />
|
||||
</StackPanel>
|
||||
@@ -270,6 +273,11 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector="spp|PageBase:namespace /template/ Panel#OptionsRegion Button">
|
||||
<Setter Property="MaxWidth" Value="200" />
|
||||
<Setter Property="TextBlock.TextWrapping" Value="Wrap" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="spp|PageBase:namespace /template/ Button#NamespaceText">
|
||||
<Setter Property="IsVisible" Value="True" />
|
||||
</Style>
|
||||
|
||||
@@ -163,9 +163,9 @@ public class PageBase : UserControl
|
||||
{
|
||||
var sz = e.NewSize.Width;
|
||||
|
||||
bool isSmallWidth2 = sz < 580;
|
||||
bool isSmallWidth2 = sz < 640;
|
||||
|
||||
PseudoClasses.Set(":smallWidth", sz < 710);
|
||||
PseudoClasses.Set(":smallWidth", sz < 800);
|
||||
PseudoClasses.Set(":smallWidth2", isSmallWidth2);
|
||||
|
||||
if (isSmallWidth2 && !_isSmallWidth2)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -48,6 +48,12 @@ public class ContentLoader : ContentControl
|
||||
public static readonly StyledProperty<bool> IsShowNoResultTextProperty =
|
||||
AvaloniaProperty.Register<ContentLoader, bool>(nameof(IsShowNoResultText), false);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="CustomLoadingText"/> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<string?> CustomLoadingTextProperty =
|
||||
AvaloniaProperty.Register<ContentLoader, string?>(nameof(CustomLoadingText), null);
|
||||
|
||||
/// <summary>
|
||||
/// 是否正在加载中
|
||||
/// </summary>
|
||||
@@ -69,6 +75,15 @@ public class ContentLoader : ContentControl
|
||||
set => SetValue(IsShowNoResultTextProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自定义加载提示
|
||||
/// </summary>
|
||||
public string? CustomLoadingText
|
||||
{
|
||||
get => GetValue(CustomLoadingTextProperty);
|
||||
set => SetValue(CustomLoadingTextProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 无结果时提示
|
||||
/// </summary>
|
||||
@@ -99,31 +114,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,28 +74,16 @@ 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 == "*")
|
||||
//{
|
||||
// fileTypes = new ValueTuple<string, string[]>[] { ("All Files", new[] { "*" }) };
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// fileTypes = new ValueTuple<string, string[]>[] {
|
||||
// ("Required Files", FileExtensions.Split(",")),
|
||||
// ("All Files", new[] { "*" }),
|
||||
// };
|
||||
//}
|
||||
|
||||
var result = await FilePicker2.PickAsync(PickOptions.Images);
|
||||
if (result != null)
|
||||
{
|
||||
FileName = result.FullPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
Toast.Show(ToastIcon.Warning, Strings.PleaseSelect);
|
||||
}
|
||||
//else
|
||||
//{
|
||||
// Toast.Show(ToastIcon.Warning, Strings.PleaseSelect);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:generic="using:System.Collections.Generic"
|
||||
xmlns:i="using:Avalonia.Xaml.Interactivity"
|
||||
xmlns:ia="using:Avalonia.Xaml.Interactions.Core"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:spp="https://steampp.net/ui"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
@@ -32,7 +35,7 @@
|
||||
ClipToBounds="True"
|
||||
CornerRadius="8">
|
||||
<Panel>
|
||||
<Carousel Name="Carousel" Grid.Column="1">
|
||||
<Carousel Name="CarouselControl" Grid.Column="1">
|
||||
<Carousel.PageTransition>
|
||||
<PageSlide Orientation="Horizontal" Duration="0.25" />
|
||||
</Carousel.PageTransition>
|
||||
@@ -68,15 +71,43 @@
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Padding="0" Classes="Plain">
|
||||
<DataTemplate.DataType>
|
||||
<x:Type x:TypeArguments="x:Int32, x:String" TypeName="generic:KeyValuePair" />
|
||||
</DataTemplate.DataType>
|
||||
<Button
|
||||
Padding="0"
|
||||
Classes="Plain"
|
||||
Click="CarouselBannerIndexButton_Click"
|
||||
Tag="{Binding Key}">
|
||||
<Ellipse
|
||||
Name="IndexEllipse"
|
||||
Width="10"
|
||||
Height="10"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Fill="{ReflectionBinding}"
|
||||
Fill="{Binding Value}"
|
||||
Opacity="0.6"
|
||||
StrokeThickness="0" />
|
||||
<!--<i:Interaction.Behaviors>
|
||||
<ia:DataTriggerBehavior
|
||||
Binding="{Binding}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="{Binding #Carousel.SelectedIndex}">
|
||||
<ia:ChangePropertyAction
|
||||
PropertyName="Fill"
|
||||
TargetObject="IndexEllipse"
|
||||
Value="White" />
|
||||
</ia:DataTriggerBehavior>
|
||||
<ia:DataTriggerBehavior
|
||||
Binding="{Binding}"
|
||||
ComparisonCondition="NotEqual"
|
||||
Value="{Binding #Carousel.SelectedIndex}">
|
||||
<ia:ChangePropertyAction
|
||||
PropertyName="Fill"
|
||||
TargetObject="IndexEllipse"
|
||||
Value="Gray" />
|
||||
</ia:DataTriggerBehavior>
|
||||
</i:Interaction.Behaviors>-->
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
|
||||
@@ -73,14 +73,14 @@ namespace BD.WTTS.UI.Views.Controls
|
||||
Left.Click += (s, e) => SwiperPrevious();
|
||||
Right.Click += (s, e) => SwiperNext();
|
||||
|
||||
//Carousel[!Carousel.Items] = this[!Items];
|
||||
Carousel[!Carousel.ItemsSourceProperty] = this[!ItemsSourceProperty];
|
||||
Carousel[!Carousel.ItemTemplateProperty] = this[!ItemTemplateProperty];
|
||||
//CarouselControl[!Carousel.Items] = this[!Items];
|
||||
CarouselControl[!Carousel.ItemsSourceProperty] = this[!ItemsSourceProperty];
|
||||
CarouselControl[!Carousel.ItemTemplateProperty] = this[!ItemTemplateProperty];
|
||||
|
||||
this.GetObservable(ItemsSourceProperty)
|
||||
.Subscribe(_ => SwipersLoad());
|
||||
|
||||
Carousel.GetObservable(Carousel.SelectedIndexProperty)
|
||||
CarouselControl.GetObservable(Carousel.SelectedIndexProperty)
|
||||
.Subscribe(x => SwipersLoad());
|
||||
|
||||
this.GetObservable(AutoScrollProperty)
|
||||
@@ -107,14 +107,22 @@ namespace BD.WTTS.UI.Views.Controls
|
||||
});
|
||||
}
|
||||
|
||||
private void CarouselBannerIndexButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Button button && button.Tag is int index)
|
||||
{
|
||||
CarouselControl.SelectedIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
private void SwipersLoad()
|
||||
{
|
||||
if (Carousel.ItemCount <= 0)
|
||||
if (CarouselControl.ItemCount <= 0)
|
||||
{
|
||||
Swiper.ItemsSource = null;
|
||||
return;
|
||||
}
|
||||
if (Carousel.ItemCount == 1)
|
||||
if (CarouselControl.ItemCount == 1)
|
||||
{
|
||||
Left.IsVisible = Right.IsVisible = Swiper.IsVisible = false;
|
||||
return;
|
||||
@@ -122,12 +130,12 @@ namespace BD.WTTS.UI.Views.Controls
|
||||
else
|
||||
{
|
||||
Left.IsVisible = Right.IsVisible = Swiper.IsVisible = true;
|
||||
var arr = new string[Carousel.ItemCount];
|
||||
for (var i = 0; i < arr.Length; i++)
|
||||
var arr = new Dictionary<int, string>();
|
||||
for (var i = 0; i < CarouselControl.ItemCount; i++)
|
||||
{
|
||||
arr[i] = "#ADADAD";
|
||||
arr.Add(i, "#ADADAD");
|
||||
}
|
||||
var index = Carousel.SelectedIndex < 0 ? 0 : Carousel.SelectedIndex;
|
||||
var index = CarouselControl.SelectedIndex < 0 ? 0 : CarouselControl.SelectedIndex;
|
||||
arr[index] = "#FFFFFF";
|
||||
|
||||
Swiper.ItemsSource = arr;
|
||||
@@ -136,29 +144,29 @@ namespace BD.WTTS.UI.Views.Controls
|
||||
|
||||
private void SwiperNext()
|
||||
{
|
||||
if (Carousel.ItemCount < 1)
|
||||
if (CarouselControl.ItemCount < 1)
|
||||
return;
|
||||
if (Carousel.SelectedIndex < Carousel.ItemCount - 1)
|
||||
if (CarouselControl.SelectedIndex < CarouselControl.ItemCount - 1)
|
||||
{
|
||||
Carousel.Next();
|
||||
CarouselControl.Next();
|
||||
}
|
||||
else
|
||||
{
|
||||
Carousel.SelectedIndex = 0;
|
||||
CarouselControl.SelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void SwiperPrevious()
|
||||
{
|
||||
if (Carousel.ItemCount < 1)
|
||||
if (CarouselControl.ItemCount < 1)
|
||||
return;
|
||||
if (Carousel.SelectedIndex > 0)
|
||||
if (CarouselControl.SelectedIndex > 0)
|
||||
{
|
||||
Carousel.Previous();
|
||||
CarouselControl.Previous();
|
||||
}
|
||||
else
|
||||
{
|
||||
Carousel.SelectedIndex = Carousel.ItemCount - 1;
|
||||
CarouselControl.SelectedIndex = CarouselControl.ItemCount - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) =>
|
||||
{
|
||||
|
||||
@@ -67,166 +67,166 @@
|
||||
</StackPanel>
|
||||
</spp:PageBase.ActionContent>-->
|
||||
<ScrollViewer Padding="20">
|
||||
<spp:ContentLoader IsLoading="False">
|
||||
<StackPanel
|
||||
x:Name="HomeRoot"
|
||||
MaxWidth="1080"
|
||||
Spacing="20">
|
||||
<spp:Snackbar
|
||||
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
VerticalAlignment="Top"
|
||||
IsClosable="False"
|
||||
IsOpen="True"
|
||||
IsVisible="{spp:OnPlatform !GA}"
|
||||
Message="当前使用的程序版本为测试版本,可能会有各种各样不可言明的 Bug,遇到问题请理性反馈,谢谢。"
|
||||
Severity="Warning" />
|
||||
<spp:Snackbar
|
||||
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
VerticalAlignment="Top"
|
||||
IsClosable="False"
|
||||
IsOpen="True"
|
||||
IsVisible="{Binding !!!MainWindow2.PluginCount, Mode=OneWay, Source={x:Static s:IViewModelManager.Instance}}"
|
||||
Message="检测到你没有安装插件,请去下载插件即可使用更多功能。"
|
||||
Severity="Warning" />
|
||||
<!--<spp:ContentLoader IsLoading="False">-->
|
||||
<StackPanel
|
||||
x:Name="HomeRoot"
|
||||
MaxWidth="1080"
|
||||
Spacing="20">
|
||||
<spp:Snackbar
|
||||
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
VerticalAlignment="Top"
|
||||
IsClosable="False"
|
||||
IsOpen="True"
|
||||
IsVisible="{spp:OnPlatform !GA}"
|
||||
Message="当前使用的程序版本为测试版本,可能会有各种各样不可言明的 Bug,遇到问题请理性反馈,谢谢。"
|
||||
Severity="Warning" />
|
||||
<spp:Snackbar
|
||||
Title="{Binding Path=Res.Notice_Tray_Title, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
VerticalAlignment="Top"
|
||||
IsClosable="False"
|
||||
IsOpen="True"
|
||||
IsVisible="{Binding !!!MainWindow2.PluginCount, Mode=OneWay, Source={x:Static s:IViewModelManager.Instance}}"
|
||||
Message="检测到你没有安装插件,请去下载插件即可使用更多功能。"
|
||||
Severity="Warning" />
|
||||
|
||||
<WrapPanel Margin="5,0">
|
||||
<WrapPanel IsVisible="{Binding Source={x:Static s:SteamConnectService.Current}, Path=CurrentSteamUser, Mode=OneWay, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}">
|
||||
<spp:Image2
|
||||
Width="36"
|
||||
Height="36"
|
||||
Margin="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
DecodeWidth="36"
|
||||
Source="{Binding Source={x:Static s:SteamConnectService.Current}, Path=CurrentSteamUser.AvatarMedium, Mode=OneWay}"
|
||||
Stretch="Uniform">
|
||||
<spp:Image2.Clip>
|
||||
<EllipseGeometry Rect="0,0,36,36" />
|
||||
</spp:Image2.Clip>
|
||||
</spp:Image2>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Source={x:Static s:SteamConnectService.Current}, Path=CurrentSteamUser.SteamNickName, Mode=OneWay}"
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding Source={x:Static s:SteamConnectService.Current}, Path=CurrentSteamUser.IPCountry, Mode=OneWay, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}"
|
||||
Text="{Binding Source={x:Static s:SteamConnectService.Current}, Path=CurrentSteamUser.IPCountry, Mode=OneWay, StringFormat={}({0})}"
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}" />
|
||||
<TextBlock
|
||||
Margin="0,0,10,0"
|
||||
VerticalAlignment="Center"
|
||||
Text=","
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}" />
|
||||
</WrapPanel>
|
||||
<TextBlock VerticalAlignment="Center" Theme="{StaticResource SubtitleTextBlockStyle}">
|
||||
<Run Text="{Binding Path=Res.Welcome_User_, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</TextBlock>
|
||||
<WrapPanel Margin="5,0">
|
||||
<WrapPanel IsVisible="{Binding Source={x:Static s:SteamConnectService.Current}, Path=CurrentSteamUser, Mode=OneWay, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}">
|
||||
<spp:Image2
|
||||
Width="36"
|
||||
Height="36"
|
||||
Margin="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
DecodeWidth="36"
|
||||
Source="{Binding Source={x:Static s:SteamConnectService.Current}, Path=CurrentSteamUser.AvatarMedium, Mode=OneWay}"
|
||||
Stretch="Uniform">
|
||||
<spp:Image2.Clip>
|
||||
<EllipseGeometry Rect="0,0,36,36" />
|
||||
</spp:Image2.Clip>
|
||||
</spp:Image2>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Source={x:Static s:SteamConnectService.Current}, Path=CurrentSteamUser.SteamNickName, Mode=OneWay}"
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding Source={x:Static s:SteamConnectService.Current}, Path=CurrentSteamUser.IPCountry, Mode=OneWay, Converter={StaticResource IsNullConverter}, ConverterParameter=invert}"
|
||||
Text="{Binding Source={x:Static s:SteamConnectService.Current}, Path=CurrentSteamUser.IPCountry, Mode=OneWay, StringFormat={}({0})}"
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}" />
|
||||
<TextBlock
|
||||
Margin="0,0,10,0"
|
||||
VerticalAlignment="Center"
|
||||
Text=","
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}" />
|
||||
</WrapPanel>
|
||||
<TextBlock VerticalAlignment="Center" Theme="{StaticResource SubtitleTextBlockStyle}">
|
||||
<Run Text="{Binding Path=Res.Welcome_User_, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
</TextBlock>
|
||||
</WrapPanel>
|
||||
|
||||
<Grid Margin="0,-10,0,0" RowDefinitions="Auto,*">
|
||||
<spp:CarouselBanner
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
ItemsSource="{Binding NavigationBanners}">
|
||||
<spp:CarouselBanner.ItemTemplate>
|
||||
<DataTemplate DataType="spp:AdvertisementDTO">
|
||||
<spp:AppItem
|
||||
Title="{Binding Describe}"
|
||||
Height="350"
|
||||
Classes="Banner Scale"
|
||||
ClickCommand="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
ClickCommandParameter="{Binding Url}"
|
||||
Description="{Binding Name}">
|
||||
<spp:AppItem.Status>
|
||||
<Border
|
||||
Padding="20,10"
|
||||
Background="{DynamicResource AccentFillColorSelectedTextBackgroundBrush}"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="{StaticResource ControlCornerRadius}">
|
||||
<TextBlock FontSize="16" Text="阅读更多" />
|
||||
</Border>
|
||||
</spp:AppItem.Status>
|
||||
<spp:AppItem.Image>
|
||||
<spp:Image2 Source="{Binding ImageUrl}" />
|
||||
</spp:AppItem.Image>
|
||||
</spp:AppItem>
|
||||
</DataTemplate>
|
||||
</spp:CarouselBanner.ItemTemplate>
|
||||
</spp:CarouselBanner>
|
||||
</Grid>
|
||||
<Grid Margin="0,-10,0,0" RowDefinitions="Auto,*">
|
||||
<spp:CarouselBanner
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
ItemsSource="{Binding NavigationBanners}">
|
||||
<spp:CarouselBanner.ItemTemplate>
|
||||
<DataTemplate DataType="spp:AdvertisementDTO">
|
||||
<spp:AppItem
|
||||
Title="{Binding Describe}"
|
||||
Height="350"
|
||||
Classes="Banner Scale"
|
||||
ClickCommand="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
ClickCommandParameter="{Binding Url}"
|
||||
Description="{Binding Name}">
|
||||
<spp:AppItem.Status>
|
||||
<Border
|
||||
Padding="20,10"
|
||||
Background="{DynamicResource AccentFillColorSelectedTextBackgroundBrush}"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="{StaticResource ControlCornerRadius}">
|
||||
<TextBlock FontSize="16" Text="阅读更多" />
|
||||
</Border>
|
||||
</spp:AppItem.Status>
|
||||
<spp:AppItem.Image>
|
||||
<spp:Image2 Source="{Binding ImageUrl}" />
|
||||
</spp:AppItem.Image>
|
||||
</spp:AppItem>
|
||||
</DataTemplate>
|
||||
</spp:CarouselBanner.ItemTemplate>
|
||||
</spp:CarouselBanner>
|
||||
</Grid>
|
||||
|
||||
<!--<Viewbox Stretch="UniformToFill">
|
||||
<!--<Viewbox Stretch="UniformToFill">
|
||||
<spp:AdControl
|
||||
MaxWidth="{Binding #HomeRoot.MaxWidth}"
|
||||
MaxHeight="200"
|
||||
Standard="Horizontal" />
|
||||
</Viewbox>-->
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="2"
|
||||
Margin="0,10"
|
||||
Spacing="10">
|
||||
<DockPanel>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="最新消息"
|
||||
Theme="{StaticResource TitleTextBlockStyle}" />
|
||||
<Button
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
CommandParameter="{x:Static spp:HomePageViewModel.OfficialWebsite_Article}"
|
||||
Content="查看更多"
|
||||
DockPanel.Dock="Right"
|
||||
Theme="{StaticResource HyperlinkButton}" />
|
||||
</DockPanel>
|
||||
<StackPanel
|
||||
Grid.Row="2"
|
||||
Margin="0,10"
|
||||
Spacing="10">
|
||||
<DockPanel>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="最新消息"
|
||||
Theme="{StaticResource TitleTextBlockStyle}" />
|
||||
<Button
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
CommandParameter="{x:Static spp:HomePageViewModel.OfficialWebsite_Article}"
|
||||
Content="查看更多"
|
||||
DockPanel.Dock="Right"
|
||||
Theme="{StaticResource HyperlinkButton}" />
|
||||
</DockPanel>
|
||||
|
||||
<Grid Margin="0,0,0,20" ColumnDefinitions="*,10,*,10,*">
|
||||
<Grid.Styles>
|
||||
<Style x:DataType="spp:ArticleItemDTO" Selector="spp|AppItem">
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Title" Value="{Binding Title}" />
|
||||
<Setter Property="Description" Value="{Binding Introduction}" />
|
||||
<Setter Property="Tags" Value="{Binding AuthorName}" />
|
||||
<Setter Property="ClickCommand" Value="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}" />
|
||||
<Setter Property="ClickCommandParameter">
|
||||
<Setter.Value>
|
||||
<MultiBinding Converter="{StaticResource StringFormatConverter}">
|
||||
<CompiledBinding Path="." Source="{x:Static spp:HomePageViewModel.OfficialWebsite_Article_Detail_}" />
|
||||
<CompiledBinding Path="Id" />
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Image">
|
||||
<Setter.Value>
|
||||
<Template>
|
||||
<spp:Image2 RenderOptions.BitmapInterpolationMode="HighQuality" Source="{Binding CoverUrl}" />
|
||||
</Template>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Status">
|
||||
<Setter.Value>
|
||||
<Template>
|
||||
<Border Classes="Status">
|
||||
<TextBlock Text="{Binding CreationTime, StringFormat={}{0:yyyy-MM-dd}}" />
|
||||
</Border>
|
||||
</Template>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
<spp:AppItem Classes="Vertical Scale" DataContext="{Binding Articles[0]}" />
|
||||
<spp:AppItem
|
||||
Grid.Column="2"
|
||||
Classes="Vertical Scale"
|
||||
DataContext="{Binding Articles[1]}" />
|
||||
<spp:AppItem
|
||||
Grid.Column="4"
|
||||
Classes="Vertical Scale"
|
||||
DataContext="{Binding Articles[2]}" />
|
||||
</Grid>
|
||||
<Grid Margin="0,0,0,20" ColumnDefinitions="*,10,*,10,*">
|
||||
<Grid.Styles>
|
||||
<Style x:DataType="spp:ArticleItemDTO" Selector="spp|AppItem">
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Title" Value="{Binding Title}" />
|
||||
<Setter Property="Description" Value="{Binding Introduction}" />
|
||||
<Setter Property="Tags" Value="{Binding AuthorName}" />
|
||||
<Setter Property="ClickCommand" Value="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}" />
|
||||
<Setter Property="ClickCommandParameter">
|
||||
<Setter.Value>
|
||||
<MultiBinding Converter="{StaticResource StringFormatConverter}">
|
||||
<CompiledBinding Path="." Source="{x:Static spp:HomePageViewModel.OfficialWebsite_Article_Detail_}" />
|
||||
<CompiledBinding Path="Id" />
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Image">
|
||||
<Setter.Value>
|
||||
<Template>
|
||||
<spp:Image2 RenderOptions.BitmapInterpolationMode="HighQuality" Source="{Binding CoverUrl}" />
|
||||
</Template>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Status">
|
||||
<Setter.Value>
|
||||
<Template>
|
||||
<Border Classes="Status">
|
||||
<TextBlock Text="{Binding CreationTime, StringFormat={}{0:yyyy-MM-dd}}" />
|
||||
</Border>
|
||||
</Template>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
<spp:AppItem Classes="Vertical Scale" DataContext="{Binding Articles[0]}" />
|
||||
<spp:AppItem
|
||||
Grid.Column="2"
|
||||
Classes="Vertical Scale"
|
||||
DataContext="{Binding Articles[1]}" />
|
||||
<spp:AppItem
|
||||
Grid.Column="4"
|
||||
Classes="Vertical Scale"
|
||||
DataContext="{Binding Articles[2]}" />
|
||||
</Grid>
|
||||
|
||||
<!--<ItemsControl ItemsSource="{Binding Articles}">
|
||||
<!--<ItemsControl ItemsSource="{Binding Articles}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Grid ColumnDefinitions="*,*,*" />
|
||||
@@ -252,149 +252,149 @@
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>-->
|
||||
|
||||
<DockPanel>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="精选"
|
||||
Theme="{StaticResource TitleTextBlockStyle}" />
|
||||
<Button
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
CommandParameter="{x:Static spp:HomePageViewModel.WattGameUrl}"
|
||||
Content="访问 WattGame 商城"
|
||||
DockPanel.Dock="Right"
|
||||
Theme="{StaticResource HyperlinkButton}" />
|
||||
</DockPanel>
|
||||
|
||||
<ScrollViewer
|
||||
Name="ShopsScrollViewer"
|
||||
Margin="0,3,0,20"
|
||||
Padding="0,0,0,15"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Disabled">
|
||||
<ItemsControl ItemsSource="{Binding Shops}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="10" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="spp:ShopRecommendGoodItem">
|
||||
<spp:AppItem
|
||||
Title="{Binding Name}"
|
||||
MaxWidth="260"
|
||||
Classes="Vertical Scale"
|
||||
ClickCommand="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
Cursor="Hand"
|
||||
Tags="{Binding GoodsAllCategorysText}"
|
||||
ToolTip.Tip="{Binding Brief}">
|
||||
<spp:AppItem.ClickCommandParameter>
|
||||
<MultiBinding Converter="{StaticResource StringFormatConverter}">
|
||||
<CompiledBinding Path="." Source="{x:Static spp:HomePageViewModel.WattGame_Goods_Detail_}" />
|
||||
<CompiledBinding Path="Id" />
|
||||
</MultiBinding>
|
||||
</spp:AppItem.ClickCommandParameter>
|
||||
<spp:AppItem.Image>
|
||||
<spp:Image2 RenderOptions.BitmapInterpolationMode="HighQuality" Source="{Binding Image}" />
|
||||
</spp:AppItem.Image>
|
||||
<spp:AppItem.Status>
|
||||
<Border Classes="Status">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
IsVisible="{Binding !!DiscountValue}"
|
||||
Text="{Binding Mktprice, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static spp:CurrencyCode.CNY}}"
|
||||
TextDecorations="Strikethrough" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Text="{Binding Price, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static spp:CurrencyCode.CNY}}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</spp:AppItem.Status>
|
||||
<spp:AppItem.ActionButton>
|
||||
<Border
|
||||
Height="24"
|
||||
Padding="8,0"
|
||||
Background="#4c6b22"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="4"
|
||||
IsVisible="{Binding !!DiscountValue}">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Foreground="#beee11"
|
||||
Text="{Binding DiscountValue, StringFormat={}-{0}%}" />
|
||||
</Border>
|
||||
</spp:AppItem.ActionButton>
|
||||
</spp:AppItem>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
<DockPanel>
|
||||
<TextBlock
|
||||
Margin="-2,0,0,0"
|
||||
Text="已安装功能"
|
||||
VerticalAlignment="Center"
|
||||
Text="精选"
|
||||
Theme="{StaticResource TitleTextBlockStyle}" />
|
||||
<Button
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
CommandParameter="{x:Static spp:HomePageViewModel.WattGameUrl}"
|
||||
Content="访问 WattGame 商城"
|
||||
DockPanel.Dock="Right"
|
||||
Theme="{StaticResource HyperlinkButton}" />
|
||||
</DockPanel>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding MainWindow2.TabItems, Mode=OneWay, Source={x:Static s:IViewModelManager.Instance}}">
|
||||
<ItemsControl.Resources>
|
||||
<ControlTheme
|
||||
x:Key="NavButtonStyle"
|
||||
BasedOn="{StaticResource {x:Type Button}}"
|
||||
TargetType="Button">
|
||||
<Setter Property="Width" Value="120" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="6" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch" />
|
||||
|
||||
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="ClipToBounds" Value="True" />
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
</ItemsControl.Resources>
|
||||
<ScrollViewer
|
||||
Name="ShopsScrollViewer"
|
||||
Margin="0,3,0,20"
|
||||
Padding="0,0,0,15"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Disabled">
|
||||
<ItemsControl ItemsSource="{Binding Shops}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="10" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="spp:TabItemViewModel">
|
||||
<Border spp:Animations.EnableAnimations="True">
|
||||
<Button
|
||||
Command="{Binding $parent[spp:HomePage].((spp:HomePageViewModel)DataContext).NavgationToMenuPageCommand}"
|
||||
CommandParameter="{Binding}"
|
||||
Theme="{StaticResource NavButtonStyle}">
|
||||
<Grid RowDefinitions="3*,*">
|
||||
<Border
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CornerRadius="4 4 0 0"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality">
|
||||
<spp:Image2
|
||||
Width="96"
|
||||
Height="96"
|
||||
Margin="0,10,0,0"
|
||||
Source="{Binding IconKey}"
|
||||
Stretch="Uniform" />
|
||||
</Border>
|
||||
<DataTemplate DataType="spp:ShopRecommendGoodItem">
|
||||
<spp:AppItem
|
||||
Title="{Binding Name}"
|
||||
MaxWidth="260"
|
||||
Classes="Vertical Scale"
|
||||
ClickCommand="{Binding OpenBrowserCommand, Source={x:Static spp:App.Instance}}"
|
||||
Cursor="Hand"
|
||||
Tags="{Binding GoodsAllCategorysText}"
|
||||
ToolTip.Tip="{Binding Brief}">
|
||||
<spp:AppItem.ClickCommandParameter>
|
||||
<MultiBinding Converter="{StaticResource StringFormatConverter}">
|
||||
<CompiledBinding Path="." Source="{x:Static spp:HomePageViewModel.WattGame_Goods_Detail_}" />
|
||||
<CompiledBinding Path="Id" />
|
||||
</MultiBinding>
|
||||
</spp:AppItem.ClickCommandParameter>
|
||||
<spp:AppItem.Image>
|
||||
<spp:Image2 RenderOptions.BitmapInterpolationMode="HighQuality" Source="{Binding Image}" />
|
||||
</spp:AppItem.Image>
|
||||
<spp:AppItem.Status>
|
||||
<Border Classes="Status">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
IsVisible="{Binding !!DiscountValue}"
|
||||
Text="{Binding Mktprice, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static spp:CurrencyCode.CNY}}"
|
||||
TextDecorations="Strikethrough" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Text="{Binding Price, Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static spp:CurrencyCode.CNY}}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</spp:AppItem.Status>
|
||||
<spp:AppItem.ActionButton>
|
||||
<Border
|
||||
Height="24"
|
||||
Padding="8,0"
|
||||
Background="#4c6b22"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="4"
|
||||
IsVisible="{Binding !!DiscountValue}">
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="12"
|
||||
FontWeight="SemiBold"
|
||||
Text="{Binding Name}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</Border>
|
||||
VerticalAlignment="Center"
|
||||
Foreground="#beee11"
|
||||
Text="{Binding DiscountValue, StringFormat={}-{0}%}" />
|
||||
</Border>
|
||||
</spp:AppItem.ActionButton>
|
||||
</spp:AppItem>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
<!--<TextBlock Text="联系我们" Theme="{StaticResource TitleTextBlockStyle}" />
|
||||
<TextBlock
|
||||
Margin="-3,0,0,0"
|
||||
Text="已安装功能"
|
||||
Theme="{StaticResource TitleTextBlockStyle}" />
|
||||
|
||||
<ItemsControl ItemsSource="{Binding MainWindow2.TabItems, Mode=OneWay, Source={x:Static s:IViewModelManager.Instance}}">
|
||||
<ItemsControl.Resources>
|
||||
<ControlTheme
|
||||
x:Key="NavButtonStyle"
|
||||
BasedOn="{StaticResource {x:Type Button}}"
|
||||
TargetType="Button">
|
||||
<Setter Property="Width" Value="120" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="6" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch" />
|
||||
|
||||
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="ClipToBounds" Value="True" />
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
</ItemsControl.Resources>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="spp:TabItemViewModel">
|
||||
<Border spp:Animations.EnableAnimations="True">
|
||||
<Button
|
||||
Command="{Binding $parent[spp:HomePage].((spp:HomePageViewModel)DataContext).NavgationToMenuPageCommand}"
|
||||
CommandParameter="{Binding}"
|
||||
Theme="{StaticResource NavButtonStyle}">
|
||||
<Grid RowDefinitions="3*,*">
|
||||
<Border
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CornerRadius="4 4 0 0"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality">
|
||||
<spp:Image2
|
||||
Width="96"
|
||||
Height="96"
|
||||
Margin="0,10,0,0"
|
||||
Source="{Binding IconKey}"
|
||||
Stretch="Uniform" />
|
||||
</Border>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="12"
|
||||
FontWeight="SemiBold"
|
||||
Text="{Binding Name}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!--<TextBlock Text="联系我们" Theme="{StaticResource TitleTextBlockStyle}" />
|
||||
|
||||
<Grid Margin="0,0,0,20" ColumnDefinitions="*,10,*,10,*,10,*,10,*">
|
||||
<spp:AppItem
|
||||
@@ -449,9 +449,9 @@
|
||||
</spp:AppItem>
|
||||
</Grid>-->
|
||||
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!--<TabControl Grid.Row="3" Padding="0">
|
||||
<!--<TabControl Grid.Row="3" Padding="0">
|
||||
<TabItem Header="1111">
|
||||
<StackPanel Spacing="5">
|
||||
<spp:AppItem
|
||||
@@ -523,13 +523,13 @@
|
||||
</TabItem>
|
||||
</TabControl>-->
|
||||
|
||||
<TextBlock
|
||||
Margin="0,30"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Text="不忘初心,方得始终。"
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}" />
|
||||
</StackPanel>
|
||||
</spp:ContentLoader>
|
||||
<TextBlock
|
||||
Margin="0,30"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Text="不忘初心,方得始终。"
|
||||
Theme="{StaticResource SubtitleTextBlockStyle}" />
|
||||
</StackPanel>
|
||||
<!--</spp:ContentLoader>-->
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
@@ -7,36 +7,36 @@ public partial class HomePage : ReactiveUserControl<HomePageViewModel>
|
||||
public HomePage()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.SetViewModel<HomePageViewModel>();
|
||||
this.SetViewModel<HomePageViewModel>(false);
|
||||
|
||||
ShopsScrollViewer.PointerWheelChanged += ShopsScrollViewer_PointerWheelChanged;
|
||||
//ShopsScrollViewer.PointerWheelChanged += ShopsScrollViewer_PointerWheelChanged;
|
||||
}
|
||||
|
||||
private void ShopsScrollViewer_PointerWheelChanged(object? sender, PointerWheelEventArgs e)
|
||||
{
|
||||
if (sender is ScrollViewer scroll)
|
||||
if (scroll.IsPointerOver)
|
||||
if (scroll.Extent.Height > scroll.Viewport.Height || scroll.Extent.Width > scroll.Viewport.Width)
|
||||
{
|
||||
var delta = e.Delta;
|
||||
//private void ShopsScrollViewer_PointerWheelChanged(object? sender, PointerWheelEventArgs e)
|
||||
//{
|
||||
// if (sender is ScrollViewer scroll)
|
||||
// if (scroll.IsPointerOver)
|
||||
// if (scroll.Extent.Height > scroll.Viewport.Height || scroll.Extent.Width > scroll.Viewport.Width)
|
||||
// {
|
||||
// var delta = e.Delta;
|
||||
|
||||
var isScrollStart = scroll.Offset.X == 0;
|
||||
var isScrollEnd = scroll.Offset.X + scroll.Bounds.Size.Width >= scroll.Extent.Width;
|
||||
if (isScrollStart && MathUtilities.IsOne(e.Delta.Y))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (isScrollEnd && e.Delta.Y == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (MathUtilities.IsZero(delta.X))
|
||||
{
|
||||
var horizontalOffset = scroll.Offset.X;
|
||||
horizontalOffset -= delta.Y * 50; // 根据事件参数来调整滚动的距离
|
||||
scroll.Offset = new Vector(horizontalOffset, scroll.Offset.Y);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// var isScrollStart = scroll.Offset.X == 0;
|
||||
// var isScrollEnd = scroll.Offset.X + scroll.Bounds.Size.Width >= scroll.Extent.Width;
|
||||
// if (isScrollStart && MathUtilities.IsOne(e.Delta.Y))
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// if (isScrollEnd && e.Delta.Y == -1)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// if (MathUtilities.IsZero(delta.X))
|
||||
// {
|
||||
// var horizontalOffset = scroll.Offset.X;
|
||||
// horizontalOffset -= delta.Y * 50; // 根据事件参数来调整滚动的距离
|
||||
// scroll.Offset = new Vector(horizontalOffset, scroll.Offset.Y);
|
||||
// e.Handled = true;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,11 @@
|
||||
Background="{DynamicResource ControlOnImageFillColorDefaultBrush}"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="30">
|
||||
<spp:Image2 RenderOptions.BitmapInterpolationMode="HighQuality" Source="{StaticResource DefaultAvatar}" />
|
||||
<spp:Image2
|
||||
DecodeWidth="36"
|
||||
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="" />
|
||||
</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>
|
||||
|
||||
@@ -85,8 +97,7 @@
|
||||
Header="{Binding Path=Res.Settings_General_StorageSpace, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
IconSource="Globe"
|
||||
IsClickEnabled="False"
|
||||
IsExpanded="True"
|
||||
IsVisible="{spp:OnPlatform Windows}">
|
||||
IsExpanded="True">
|
||||
<ui:SettingsExpander.IconSource>
|
||||
<ui:FontIconSource FontFamily="Segoe Fluent Icons" Glyph="" />
|
||||
</ui:SettingsExpander.IconSource>
|
||||
|
||||
@@ -30,10 +30,22 @@
|
||||
|
||||
|
||||
<TextBlock Margin="0,10,0,5" Theme="{StaticResource BodyStrongTextBlockStyle}">
|
||||
<Run Text="{Binding Path=Res.Plugin_InstalledPlugins, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<TextBlock.Text>
|
||||
<MultiBinding StringFormat="{}{0}({1})">
|
||||
<CompiledBinding
|
||||
Mode="OneWay"
|
||||
Path="Res.Plugin_InstalledPlugins"
|
||||
Source="{x:Static s:ResourceService.Current}" />
|
||||
<CompiledBinding
|
||||
Mode="OneWay"
|
||||
Path="MainWindow2.PluginCount"
|
||||
Source="{x:Static s:IViewModelManager.Instance}" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
<!--<Run Text="{Binding Path=Res.Plugin_InstalledPlugins, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<Run Text="(" />
|
||||
<Run Text="{Binding MainWindow2.PluginCount, Mode=OneWay, Source={x:Static s:IViewModelManager.Instance}}" />
|
||||
<Run Text=")" />
|
||||
<Run Text=")" />-->
|
||||
</TextBlock>
|
||||
|
||||
<ItemsControl Classes="DraggableOrder" ItemsSource="{Binding Plugins}">
|
||||
|
||||
@@ -5,7 +5,6 @@ public partial class LoginOrRegisterPage : ReactiveUserControl<LoginOrRegisterWi
|
||||
public LoginOrRegisterPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.SetViewModel<LoginOrRegisterWindowViewModel>();
|
||||
|
||||
TbPhoneNumber.KeyUp += (_, e) =>
|
||||
{
|
||||
@@ -32,6 +31,22 @@ public partial class LoginOrRegisterPage : ReactiveUserControl<LoginOrRegisterWi
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
{
|
||||
base.OnLoaded(e);
|
||||
this.SetViewModel<LoginOrRegisterWindowViewModel>(false);
|
||||
|
||||
if (this.ViewModel != null)
|
||||
{
|
||||
this.ViewModel.LoginState = 0;
|
||||
if (UserService.Current.IsAuthenticated)
|
||||
{
|
||||
Toast.Show(ToastIcon.Info, "当前已是登录状态");
|
||||
this.ViewModel.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,37 @@ 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;
|
||||
info.IconUrl = oldInfo.IconUrl;
|
||||
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.Describe = oldInfo.Describe;
|
||||
info.UpdateTime = oldInfo.UpdateTime;
|
||||
info.AccelerateProjects = oldInfo.AccelerateProjects;
|
||||
info.Version = oldInfo.Version;
|
||||
info.Name = oldInfo.Name;
|
||||
}
|
||||
}
|
||||
if (pid.HasValue)
|
||||
info.Id = pid.Value;
|
||||
|
||||
@@ -17,7 +17,7 @@ partial class ProxyService
|
||||
|
||||
if (!proxyDomains.Any_Nullable())
|
||||
{
|
||||
Toast.Show(ToastIcon.Warning, "你没有启用任何加速项!!");
|
||||
Toast.Show(ToastIcon.Warning, Strings.CommunityFix_AccEmpty);
|
||||
}
|
||||
|
||||
IReadOnlyCollection<ScriptIPCDTO>? scripts = default;
|
||||
@@ -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,14 +284,24 @@ 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 计时器
|
||||
#if MACOS
|
||||
reverseProxyService.StopProxyAsync().GetAwaiter(); // 停止代理服务
|
||||
#else
|
||||
await reverseProxyService.StopProxyAsync(); // 停止代理服务
|
||||
#endif
|
||||
return default;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,42 +6,27 @@ 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);
|
||||
AddNewScriptCommand = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
FilePickerFileType? fileTypes;
|
||||
if (IApplication.IsDesktop())
|
||||
{
|
||||
fileTypes = new ValueTuple<string, string[]>[]
|
||||
{
|
||||
("JavaScript Files", new[] { $"*{FileEx.JS}", }),
|
||||
("Text Files", new[] { $"*{FileEx.TXT}", }),
|
||||
//("All Files", new[] { "*", }),
|
||||
};
|
||||
}
|
||||
else if (OperatingSystem2.IsAndroid())
|
||||
{
|
||||
fileTypes = new[] { MediaTypeNames.TXT, MediaTypeNames.JS };
|
||||
}
|
||||
else
|
||||
{
|
||||
fileTypes = null;
|
||||
}
|
||||
AvaloniaFilePickerFileTypeFilter fileTypes = new AvaloniaFilePickerFileTypeFilter.Item[] {
|
||||
new("JavaScript Files") {
|
||||
Patterns = new[] { "*.js", },
|
||||
MimeTypes = new[] { MediaTypeNames.JS, },
|
||||
//AppleUniformTypeIdentifiers =
|
||||
},
|
||||
new("Text Files") {
|
||||
Patterns = new[] { "*.txt", },
|
||||
MimeTypes = new[] { MediaTypeNames.TXT, },
|
||||
//AppleUniformTypeIdentifiers =
|
||||
},
|
||||
};
|
||||
await FilePicker2.PickAsync(ProxyService.Current.AddNewScriptAsync, fileTypes);
|
||||
});
|
||||
|
||||
@@ -51,17 +36,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 +152,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>
|
||||
@@ -140,19 +140,18 @@ public static class AuthenticatorHelper
|
||||
SaveFileResult? exportFile = null;
|
||||
if (Essentials.IsSupportedSaveFileDialog)
|
||||
{
|
||||
FilePickerFileType? fileTypes;
|
||||
if (IApplication.IsDesktop())
|
||||
{
|
||||
fileTypes = new ValueTuple<string, string[]>[]
|
||||
{
|
||||
("MsgPack Files", new[] { $"*{FileEx.MPO}", }), ("Data Files", new[] { $"*{FileEx.DAT}", }),
|
||||
//("All Files", new[] { "*", }),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
fileTypes = null;
|
||||
}
|
||||
AvaloniaFilePickerFileTypeFilter fileTypes = new AvaloniaFilePickerFileTypeFilter.Item[] {
|
||||
new("MsgPack Files") {
|
||||
Patterns = new[] { $"*.{FileEx.MPO}", },
|
||||
//MimeTypes
|
||||
//AppleUniformTypeIdentifiers =
|
||||
},
|
||||
new("Data Files") {
|
||||
Patterns = new[] { $"*.{FileEx.DAT}", },
|
||||
//MimeTypes
|
||||
//AppleUniformTypeIdentifiers =
|
||||
},
|
||||
};
|
||||
exportFile = await FilePicker2.SaveAsync(new PickOptions
|
||||
{
|
||||
FileTypes = fileTypes,
|
||||
@@ -366,99 +365,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 +419,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 +434,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":
|
||||
@@ -552,12 +461,18 @@ public static class AuthenticatorHelper
|
||||
var options = new PickOptions();
|
||||
if (!string.IsNullOrEmpty(fileExtension))
|
||||
{
|
||||
FilePickerFileType fileTypes = new[] { ($"{fileExtension} Files", new[] { "*" + fileExtension, }), };
|
||||
AvaloniaFilePickerFileTypeFilter fileTypes = new AvaloniaFilePickerFileTypeFilter.Item[] {
|
||||
new($"{fileExtension} Files") {
|
||||
Patterns = new[] { $"*.{fileExtension}", },
|
||||
//MimeTypes
|
||||
//AppleUniformTypeIdentifiers =
|
||||
},
|
||||
};
|
||||
options.FileTypes = fileTypes;
|
||||
}
|
||||
var filePath = (await FilePicker2.PickAsync(options))?.FullPath;
|
||||
|
||||
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
|
||||
if (!File.Exists(filePath))
|
||||
Toast.Show(ToastIcon.Warning, Strings.FilePathNotExist);
|
||||
return filePath;
|
||||
}
|
||||
|
||||
@@ -17,9 +17,11 @@ public partial class AuthenticatorItemModel : ReactiveObject, IDisposable
|
||||
|
||||
public async Task CopyCode()
|
||||
{
|
||||
//if (!IsShowCode) return;
|
||||
await Clipboard2.SetTextAsync(AuthData.Value?.CurrentCode);
|
||||
Toast.Show(ToastIcon.Success, Strings.LocalAuth_CopyAuthTip + AuthName);
|
||||
if (AuthData.Value?.CurrentCode != null)
|
||||
{
|
||||
await Clipboard2.SetTextAsync(AuthData.Value?.CurrentCode);
|
||||
Toast.Show(ToastIcon.Success, Strings.LocalAuth_CopyAuthTip + AuthName);
|
||||
}
|
||||
}
|
||||
|
||||
public AuthenticatorItemModel(IAuthenticatorDTO authenticatorDto)
|
||||
|
||||
@@ -207,8 +207,8 @@ internal sealed class AccountPlatformAuthenticatorRepository : Repository<Accoun
|
||||
IsNotLocal = !isLocal,
|
||||
IsNeedSecondaryPassword = !notSecondaryPassword,
|
||||
Index = item.Index,
|
||||
Created = item.Created,
|
||||
LastUpdate = item.LastUpdate,
|
||||
Created = item.Created == default ? DateTimeOffset.Now : item.Created,
|
||||
LastUpdate = DateTimeOffset.Now,
|
||||
};
|
||||
return entity;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -500,16 +500,13 @@ public sealed partial class AuthenticatorHomePageViewModel : ViewModelBase
|
||||
|
||||
if (Essentials.IsSupportedSaveFileDialog)
|
||||
{
|
||||
FilePickerFileType? fileTypes;
|
||||
if (IApplication.IsDesktop())
|
||||
{
|
||||
fileTypes = new[] { ("maFile Files", new[] { FileEx.maFile, }), };
|
||||
}
|
||||
else
|
||||
{
|
||||
fileTypes = null;
|
||||
}
|
||||
|
||||
AvaloniaFilePickerFileTypeFilter fileTypes = new AvaloniaFilePickerFileTypeFilter.Item[] {
|
||||
new("maFile Files") {
|
||||
Patterns = new[] { $"*.{FileEx.maFile}", },
|
||||
//MimeTypes
|
||||
//AppleUniformTypeIdentifiers =
|
||||
},
|
||||
};
|
||||
var exportFile = await FilePicker2.SaveAsync(new PickOptions
|
||||
{
|
||||
FileTypes = fileTypes,
|
||||
@@ -563,7 +560,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 - 1)
|
||||
return;
|
||||
if (authenticatorItemModel.AuthData.ServerId != null)
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ public record AuthenticatorImportMethod(string Name, string Description, string?
|
||||
|
||||
public class AuthenticatorImportPageViewModel : ViewModelBase
|
||||
{
|
||||
public static string Name => Strings.PleaseSelect + Strings.AuthImport;
|
||||
public static string Name => Strings.AuthImport;
|
||||
|
||||
public IReadOnlyCollection<AuthenticatorImportMethod> AuthenticatorImportMethods { get; }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -142,6 +142,7 @@ public sealed partial class SteamTradePageViewModel : WindowViewModel
|
||||
Toast.Show(ToastIcon.Success, string.Format(Strings.Success_, Strings.User_Login));
|
||||
_steamAuthenticator.SessionData = RemenberLogin ? _steamClient.Session.ToString() : null;
|
||||
await AuthenticatorHelper.SaveAuthenticator(_authenticatorDto!);
|
||||
_steamClient = _steamAuthenticator.GetClient(ResourceService.GetCurrentCultureSteamLanguageName());
|
||||
await GetConfirmations(_steamClient);
|
||||
}
|
||||
else
|
||||
@@ -185,12 +186,14 @@ public sealed partial class SteamTradePageViewModel : WindowViewModel
|
||||
|
||||
public async Task Logout()
|
||||
{
|
||||
if (_steamClient == null) return;
|
||||
if (_steamClient == null || _steamAuthenticator == null) return;
|
||||
if (await IWindowManager.Instance.ShowTaskDialogAsync(
|
||||
new MessageBoxWindowViewModel() { Content = Strings.LocalAuth_LogoutTip, }, AppResources.Logout, isCancelButton: true,
|
||||
isDialog: false))
|
||||
{
|
||||
_steamClient.Logout();
|
||||
_steamAuthenticator.SessionData = null;
|
||||
await AuthenticatorHelper.SaveAuthenticator(_authenticatorDto!);
|
||||
IsLogged = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,30 +34,32 @@
|
||||
</Viewbox>
|
||||
</Button>-->
|
||||
<Button
|
||||
Padding="8"
|
||||
Command="{Binding EditAuthNameAsync}"
|
||||
Theme="{StaticResource TransparentButton}"
|
||||
ToolTip.Tip="{Binding Path=Res.Auth_EditName, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
|
||||
<Viewbox Width="20">
|
||||
<Viewbox Width="16">
|
||||
<ui:SymbolIcon Symbol="Edit" />
|
||||
</Viewbox>
|
||||
</Button>
|
||||
<Button
|
||||
Padding="8"
|
||||
Command="{Binding ShowSteamTradeWindow}"
|
||||
IsVisible="{Binding IsSteamAuthenticator, Mode=OneWay}"
|
||||
Theme="{StaticResource TransparentButton}"
|
||||
ToolTip.Tip="{Binding Path=Res.LocalAuth_AuthTrade, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
|
||||
<Viewbox Width="20">
|
||||
<Viewbox Width="16">
|
||||
<ui:FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</Viewbox>
|
||||
</Button>
|
||||
<Button
|
||||
<!--<Button
|
||||
Command="{Binding ShowDetailData}"
|
||||
Theme="{StaticResource TransparentButton}"
|
||||
ToolTip.Tip="{Binding Path=Res.ViewDetailedData, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
|
||||
<Viewbox Width="20">
|
||||
<ui:SymbolIcon Symbol="Calendar" />
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</Button>-->
|
||||
<!--<Button
|
||||
Command="{Binding ShowQrCode}"
|
||||
Theme="{StaticResource TransparentButton}"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -134,12 +134,12 @@
|
||||
Source={x:Static s:ResourceService.Current}}" />-->
|
||||
<!--<ui:MenuFlyoutItem Command="{Binding ShowRecoverWindow}" Text="{Binding Path=Res.Auth_Sync_Recover, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<ui:MenuFlyoutItem Command="{Binding ResetIndependentPassword}" Text="{Binding Path=Res.Auth_Sync_ResetPassword, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />-->
|
||||
<ui:MenuFlyoutItem Text="{Binding Path=Res.Settings_Plugin, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<!--<ui:MenuFlyoutItem Text="{Binding Path=Res.Settings_Plugin, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />-->
|
||||
<ui:MenuFlyoutSeparator />
|
||||
<ui:MenuFlyoutItem IsEnabled="False" Text="{Binding Path=Res.Auth_Version, 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.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>
|
||||
@@ -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"
|
||||
@@ -291,6 +291,14 @@
|
||||
<ui:FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="{StaticResource QrCode}" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
Command="{Binding ShowDetailData}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="{Binding Path=Res.ViewDetailedData, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
|
||||
<MenuItem.Icon>
|
||||
<ui:SymbolIcon Symbol="Calendar" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
Command="{Binding $parent[spp:PageBase].((spp:AuthenticatorHomePageViewModel)DataContext).DefaultExport}"
|
||||
CommandParameter="{Binding}"
|
||||
|
||||
@@ -19,9 +19,7 @@
|
||||
<TextBlock
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Source={x:Static s:ResourceService.Current},
|
||||
Path=Res.LocalAuth_ShowAuthInfo,
|
||||
Mode=OneWay}"
|
||||
Text="{Binding Source={x:Static s:ResourceService.Current}, Path=Res.LocalAuth_ShowAuthInfo, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
<ToggleButton x:Name="ShowDetail" VerticalAlignment="Center">
|
||||
<ToggleButton.Content>
|
||||
@@ -40,8 +38,7 @@
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Text="{Binding Source={x:Static s:ResourceService.Current},
|
||||
Path=Res.LocalAuth_ShowSteamAuthTip2}"
|
||||
Text="{Binding Source={x:Static s:ResourceService.Current}, Path=Res.LocalAuth_ShowSteamAuthTip2}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
@@ -73,7 +70,6 @@
|
||||
<TextBlock Text="{Binding Path=Res.LocalAuth_ShowSteamAuthTip1, Mode=OneWay, Source={x:Static s:ResourceService.Current}}" />
|
||||
<TextBox
|
||||
Height="250"
|
||||
AcceptsReturn="True"
|
||||
IsReadOnly="True"
|
||||
PasswordChar="{StaticResource PasswordChar}"
|
||||
RevealPassword="{Binding #ShowDetail.IsChecked}"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -12,14 +12,19 @@
|
||||
x:CompileBindings="True"
|
||||
x:DataType="spp:SteamTradePageViewModel"
|
||||
mc:Ignorable="d">
|
||||
<spp:ContentLoader IsLoading="{Binding IsLoading}">
|
||||
<spp:ContentLoader CustomLoadingText="提示:不要忘了先开加速" IsLoading="{Binding IsLoading}">
|
||||
<Panel>
|
||||
<!-- 登录 -->
|
||||
<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 =>
|
||||
{
|
||||
|
||||
@@ -17,9 +17,12 @@ public class PlatformSettingsPageViewModel : ViewModelBase
|
||||
if (Platform is null)
|
||||
return;
|
||||
|
||||
FilePickerFileType fileTypes = new ValueTuple<string, string[]>[]
|
||||
{
|
||||
(Platform.FullName, new[] { Platform.ExeName ?? "*" }),
|
||||
AvaloniaFilePickerFileTypeFilter fileTypes = new AvaloniaFilePickerFileTypeFilter.Item[] {
|
||||
new(Platform.FullName) {
|
||||
Patterns = new[] { Platform.ExeName ?? "*", },
|
||||
//MimeTypes =
|
||||
//AppleUniformTypeIdentifiers =
|
||||
},
|
||||
};
|
||||
|
||||
await FilePicker2.PickAsync((path) =>
|
||||
|
||||
@@ -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:~]">
|
||||
|
||||
@@ -258,6 +258,7 @@ public sealed class EditAppInfoPageViewModel : WindowViewModel
|
||||
}
|
||||
return;
|
||||
}
|
||||
Toast.Show(ToastIcon.Info, "图片下载需要一些时间,请稍后");
|
||||
var imageHttpClientService = Ioc.Get<IImageHttpClientService>();
|
||||
var stream = await imageHttpClientService.GetImageMemoryStreamAsync(SelectGrid.Url, default);
|
||||
switch (type)
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -101,22 +101,22 @@
|
||||
|
||||
<TabItem Header="{Binding Path=Res.Media, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
|
||||
<Panel>
|
||||
<spp:ContentLoader
|
||||
<DockPanel
|
||||
x:Name="SteamGridDBContent"
|
||||
MinWidth="300"
|
||||
IsLoading="{Binding IsLoadingSteamGrid}"
|
||||
IsShowNoResultText="{Binding IsSteamGridEmpty}"
|
||||
IsVisible="False"
|
||||
NoResultMessage="{Binding Path=Res.FailedGetImageFromSteamGridDB, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
|
||||
<DockPanel>
|
||||
<Button
|
||||
Height="30"
|
||||
Margin="5,5,5,10"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
Click="HideGridDialog_Click"
|
||||
Content="{Binding Path=Res.Back, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
DockPanel.Dock="Top" />
|
||||
IsVisible="False">
|
||||
<Button
|
||||
Height="30"
|
||||
Margin="5,5,5,10"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
Click="HideGridDialog_Click"
|
||||
Content="{Binding Path=Res.Back, Mode=OneWay, Source={x:Static s:ResourceService.Current}}"
|
||||
DockPanel.Dock="Top" />
|
||||
<spp:ContentLoader
|
||||
IsLoading="{Binding IsLoadingSteamGrid}"
|
||||
IsShowNoResultText="{Binding IsSteamGridEmpty}"
|
||||
NoResultMessage="{Binding Path=Res.FailedGetImageFromSteamGridDB, Mode=OneWay, Source={x:Static s:ResourceService.Current}}">
|
||||
<ScrollViewer DockPanel.Dock="Bottom">
|
||||
<ItemsControl
|
||||
ItemsSource="{Binding SteamGridItems}"
|
||||
@@ -166,8 +166,8 @@
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</spp:ContentLoader>
|
||||
</spp:ContentLoader>
|
||||
</DockPanel>
|
||||
|
||||
<ScrollViewer x:Name="MediaContent">
|
||||
<StackPanel Classes="InfoBox">
|
||||
|
||||
@@ -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,25 @@ public sealed class IdleAppsPageViewModel : ViewModelBase
|
||||
|
||||
private async Task SteamAppsSort()
|
||||
{
|
||||
try
|
||||
{
|
||||
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); // 过滤可掉落卡片的游戏
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Toast.LogAndShowT(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
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 +150,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()
|
||||
@@ -208,7 +171,10 @@ public sealed class IdleAppsPageViewModel : ViewModelBase
|
||||
private void StartIdle()
|
||||
{
|
||||
if (!IdleGameList.Any())
|
||||
{
|
||||
IdleComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
if (IdleRule == IdleRule.OnlyOneGame)
|
||||
{
|
||||
@@ -238,13 +204,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 +225,6 @@ public sealed class IdleAppsPageViewModel : ViewModelBase
|
||||
IdleGameList.Remove(CurrentIdle);
|
||||
StartIdle();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -382,6 +347,9 @@ public sealed class IdleAppsPageViewModel : ViewModelBase
|
||||
await AutoNextTask();
|
||||
await Task.Delay(TimeSpan.FromSeconds(SwitchTime), CancellationTokenSource.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Toast.LogAndShowT(ex);
|
||||
@@ -435,7 +403,7 @@ public sealed class IdleAppsPageViewModel : ViewModelBase
|
||||
/// </summary>
|
||||
private void IdleComplete()
|
||||
{
|
||||
MessageBox.Show(Strings.Idle_Complete, button: MessageBox.Button.OK);
|
||||
Toast.Show(ToastIcon.Success, Strings.Idle_Complete);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
namespace BD.WTTS.Models;
|
||||
|
||||
public sealed class AvaloniaFilePickerFileTypeFilter : IFilePickerFileType
|
||||
{
|
||||
public IEnumerable<Item> Values { get; }
|
||||
|
||||
public AvaloniaFilePickerFileTypeFilter(
|
||||
IEnumerable<Item> values)
|
||||
{
|
||||
Values = values;
|
||||
}
|
||||
|
||||
public AvaloniaFilePickerFileTypeFilter(
|
||||
params Item[] values)
|
||||
{
|
||||
Values = values;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator AvaloniaFilePickerFileTypeFilter(Item[] values) => new(values);
|
||||
|
||||
IEnumerable<string>? IFilePickerFileType.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;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct Item
|
||||
{
|
||||
public Item(string? name)
|
||||
{
|
||||
Name = name ?? string.Empty;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public IReadOnlyList<string>? Patterns { get; init; }
|
||||
|
||||
public IReadOnlyList<string>? MimeTypes { get; init; }
|
||||
|
||||
public IReadOnlyList<string>? AppleUniformTypeIdentifiers { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -24,4 +24,24 @@ public interface IRequestCacheRepository : IRequestCache
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<int> DeleteAllAsync(DateTimeOffset dateTimeOffset);
|
||||
|
||||
const string DefaultRequestUri = "/";
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static string GetOriginalRequestUri(HttpRequestMessage request)
|
||||
{
|
||||
string? originalRequestUri;
|
||||
if (request is ImageHttpClientService.ImageHttpRequestMessage request2)
|
||||
{
|
||||
// 对于一些重定向的 Url 使用原始 Url 进行唯一性的计算
|
||||
originalRequestUri = request2.OriginalRequestUri;
|
||||
}
|
||||
else
|
||||
{
|
||||
originalRequestUri = request.RequestUri?.ToString()!;
|
||||
}
|
||||
if (string.IsNullOrEmpty(originalRequestUri))
|
||||
originalRequestUri = DefaultRequestUri;
|
||||
return originalRequestUri;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ namespace BD.WTTS.Repositories;
|
||||
|
||||
internal sealed class RequestCacheRepository : IRequestCacheRepository, IDisposable
|
||||
{
|
||||
const string DefaultRequestUri = "/";
|
||||
const string CacheDirName = "Http";
|
||||
const string TableName = "Fusillade_RequestCache";
|
||||
const string DbFileName = "RequestCache.LiteDB";
|
||||
@@ -49,17 +48,7 @@ internal sealed class RequestCacheRepository : IRequestCacheRepository, IDisposa
|
||||
if (!bytes.Any_Nullable())
|
||||
return;
|
||||
|
||||
var requestUri = response.RequestMessage?.RequestUri ?? request.RequestUri;
|
||||
string requestUriString;
|
||||
if (requestUri == null)
|
||||
{
|
||||
requestUriString = DefaultRequestUri;
|
||||
}
|
||||
else
|
||||
{
|
||||
requestUriString = requestUri.ToString();
|
||||
}
|
||||
|
||||
var requestUriString = IRequestCacheRepository.GetOriginalRequestUri(request);
|
||||
var fileTypeResult = FileFormat.AnalyzeFileType(bytes);
|
||||
var hashKey = Hashs.String.SHA384(bytes, false);
|
||||
var fileEx = fileTypeResult.FileEx;
|
||||
@@ -116,6 +105,7 @@ internal sealed class RequestCacheRepository : IRequestCacheRepository, IDisposa
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
key = UniqueKeyForRequest(requestUriString, request);
|
||||
var entity = table.FindById(key);
|
||||
bool isInsertOrUpdate;
|
||||
if (entity == null)
|
||||
@@ -158,17 +148,8 @@ internal sealed class RequestCacheRepository : IRequestCacheRepository, IDisposa
|
||||
string key,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var requestUri = request.RequestUri;
|
||||
string requestUriString;
|
||||
if (requestUri == null)
|
||||
{
|
||||
requestUriString = DefaultRequestUri;
|
||||
}
|
||||
else
|
||||
{
|
||||
requestUriString = requestUri.ToString();
|
||||
}
|
||||
|
||||
var requestUriString = IRequestCacheRepository.GetOriginalRequestUri(request);
|
||||
key = UniqueKeyForRequest(requestUriString, request);
|
||||
var entity = table.Query().Where(x => x.Id == key &&
|
||||
x.HttpMethod == request.Method.Method &&
|
||||
x.RequestUri == requestUriString).FirstOrDefault();
|
||||
@@ -249,4 +230,49 @@ internal sealed class RequestCacheRepository : IRequestCacheRepository, IDisposa
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
static string UniqueKeyForRequest(string originalRequestUri, HttpRequestMessage request)
|
||||
{
|
||||
// https://github.com/reactiveui/Fusillade/blob/2.4.67/src/Fusillade/RateLimitedHttpMessageHandler.cs#L54-L89
|
||||
|
||||
using var s = new MemoryStream();
|
||||
s.Write(Encoding.UTF8.GetBytes(originalRequestUri));
|
||||
s.Write("\r\n"u8);
|
||||
s.Write(Encoding.UTF8.GetBytes(request.Method.Method));
|
||||
s.Write("\r\n"u8);
|
||||
static void Write(Stream s, IEnumerable<object> items)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
var str = item.ToString();
|
||||
if (!string.IsNullOrEmpty(str))
|
||||
s.Write(Encoding.UTF8.GetBytes(str));
|
||||
s.Write("|"u8);
|
||||
}
|
||||
}
|
||||
Write(s, request.Headers.Accept);
|
||||
s.Write("\r\n"u8);
|
||||
Write(s, request.Headers.AcceptEncoding);
|
||||
s.Write("\r\n"u8);
|
||||
var referrer = request.Headers.Referrer;
|
||||
if (referrer == default)
|
||||
s.Write("http://example"u8);
|
||||
else
|
||||
s.Write(Encoding.UTF8.GetBytes(referrer.ToString()));
|
||||
s.Write("\r\n"u8);
|
||||
Write(s, request.Headers.UserAgent);
|
||||
s.Write("\r\n"u8);
|
||||
if (request.Headers.Authorization != null)
|
||||
{
|
||||
var parameter = request.Headers.Authorization.Parameter;
|
||||
if (!string.IsNullOrEmpty(parameter))
|
||||
s.Write(Encoding.UTF8.GetBytes(parameter));
|
||||
s.Write(Encoding.UTF8.GetBytes(request.Headers.Authorization.Scheme));
|
||||
s.Write("\r\n"u8);
|
||||
}
|
||||
s.Position = 0;
|
||||
var bytes = SHA384.HashData(s);
|
||||
var str = bytes.ToHexString();
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace BD.WTTS;
|
||||
|
||||
#if DEBUG
|
||||
[Obsolete("use ResourceService", true)]
|
||||
public static class R
|
||||
{
|
||||
}
|
||||
#endif
|
||||
@@ -1052,6 +1052,15 @@ namespace BD.WTTS.Client.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 没有启用任何加速项!! 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string CommunityFix_AccEmpty {
|
||||
get {
|
||||
return ResourceManager.GetString("CommunityFix_AccEmpty", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 已加速 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -2673,7 +2682,7 @@ namespace BD.WTTS.Client.Resources {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 没有获取到任何游戏数据 的本地化字符串。
|
||||
/// 查找类似 没有获取到游戏数据 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string GameList_IsEmpty {
|
||||
get {
|
||||
@@ -3178,7 +3187,7 @@ namespace BD.WTTS.Client.Resources {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 输入您的令牌 Key 或二维码图片 url 链接 的本地化字符串。
|
||||
/// 查找类似 输入网站提示的令牌导入二维码识别后的 Key 值 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string GeneralAuth_EnterkeyOrLink {
|
||||
get {
|
||||
@@ -4811,6 +4820,15 @@ namespace BD.WTTS.Client.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 安全设置 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string PersonalSafe {
|
||||
get {
|
||||
return ResourceManager.GetString("PersonalSafe", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 选择图片缩放方式 的本地化字符串。
|
||||
/// </summary>
|
||||
@@ -4829,15 +4847,6 @@ namespace BD.WTTS.Client.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 请选择 的本地化字符串。
|
||||
/// </summary>
|
||||
public static string PleaseSelect {
|
||||
get {
|
||||
return ResourceManager.GetString("PleaseSelect", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类似 作者:{0} 的本地化字符串。
|
||||
/// </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>
|
||||
|
||||
@@ -241,6 +241,9 @@
|
||||
</data>
|
||||
<data name="CommunityFix_ItemsEmpty" xml:space="preserve">
|
||||
<value>获取加速信息失败,请检查程序是否能正常联网</value>
|
||||
</data>
|
||||
<data name="CommunityFix_AccEmpty" xml:space="preserve">
|
||||
<value>没有启用任何加速项!!</value>
|
||||
</data>
|
||||
<data name="CommunityFix_AutoRunProxy" xml:space="preserve">
|
||||
<value>启动时自动启动加速</value>
|
||||
@@ -420,7 +423,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>
|
||||
@@ -726,9 +729,6 @@
|
||||
<data name="PleaseEnterNickName" xml:space="preserve">
|
||||
<value>请输入昵称</value>
|
||||
</data>
|
||||
<data name="PleaseSelect" xml:space="preserve">
|
||||
<value>请选择</value>
|
||||
</data>
|
||||
<data name="RecoveryCode" xml:space="preserve">
|
||||
<value>恢复代码</value>
|
||||
</data>
|
||||
@@ -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>
|
||||