diff --git a/SteamToolsV2+.sln b/SteamToolsV2+.sln index d433ca257..080cb8ab8 100644 --- a/SteamToolsV2+.sln +++ b/SteamToolsV2+.sln @@ -111,6 +111,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UI Framework", "UI Framewor EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web API", "Web API", "{4E7DF449-F136-4B90-82CE-D1418CD5123C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ST.Client.Android", "src\ST.Client.Android\ST.Client.Android.csproj", "{DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ST.Client.iOS", "src\ST.Client.iOS\ST.Client.iOS.csproj", "{7EE23547-B2AC-409D-991C-1FDAE122657C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1207,6 +1211,62 @@ Global {D30203AC-97AB-4A51-BFA5-826251C9A28E}.Release|x64.Build.0 = Release|Any CPU {D30203AC-97AB-4A51-BFA5-826251C9A28E}.Release|x86.ActiveCfg = Release|Any CPU {D30203AC-97AB-4A51-BFA5-826251C9A28E}.Release|x86.Build.0 = Release|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Debug|ARM.ActiveCfg = Debug|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Debug|ARM.Build.0 = Debug|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Debug|ARM64.Build.0 = Debug|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Debug|iPhone.Build.0 = Debug|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Debug|x64.ActiveCfg = Debug|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Debug|x64.Build.0 = Debug|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Debug|x86.ActiveCfg = Debug|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Debug|x86.Build.0 = Debug|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Release|Any CPU.Build.0 = Release|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Release|ARM.ActiveCfg = Release|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Release|ARM.Build.0 = Release|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Release|ARM64.ActiveCfg = Release|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Release|ARM64.Build.0 = Release|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Release|iPhone.ActiveCfg = Release|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Release|iPhone.Build.0 = Release|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Release|x64.ActiveCfg = Release|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Release|x64.Build.0 = Release|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Release|x86.ActiveCfg = Release|Any CPU + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B}.Release|x86.Build.0 = Release|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Debug|ARM.ActiveCfg = Debug|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Debug|ARM.Build.0 = Debug|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Debug|ARM64.Build.0 = Debug|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Debug|iPhone.Build.0 = Debug|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Debug|x64.ActiveCfg = Debug|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Debug|x64.Build.0 = Debug|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Debug|x86.ActiveCfg = Debug|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Debug|x86.Build.0 = Debug|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Release|Any CPU.Build.0 = Release|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Release|ARM.ActiveCfg = Release|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Release|ARM.Build.0 = Release|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Release|ARM64.ActiveCfg = Release|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Release|ARM64.Build.0 = Release|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Release|iPhone.ActiveCfg = Release|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Release|iPhone.Build.0 = Release|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Release|x64.ActiveCfg = Release|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Release|x64.Build.0 = Release|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Release|x86.ActiveCfg = Release|Any CPU + {7EE23547-B2AC-409D-991C-1FDAE122657C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1252,6 +1312,8 @@ Global {E50441E8-75F3-406E-9921-30BFC211B5CE} = {327FDEE7-C4C8-4AA4-A7B0-8BCE3D75346F} {8E38AAF2-8C7D-4B0D-AB25-AB37EECB4029} = {327FDEE7-C4C8-4AA4-A7B0-8BCE3D75346F} {4E7DF449-F136-4B90-82CE-D1418CD5123C} = {327FDEE7-C4C8-4AA4-A7B0-8BCE3D75346F} + {DEF239E4-AC40-4BD8-8D36-5AA6B36AB86B} = {E50441E8-75F3-406E-9921-30BFC211B5CE} + {7EE23547-B2AC-409D-991C-1FDAE122657C} = {E50441E8-75F3-406E-9921-30BFC211B5CE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9313F2B4-10DA-43AA-A481-CAA7571C740C} diff --git a/src/AboutAppInfoPopup.cs b/src/AboutAppInfoPopup.cs index 372c9ecf5..9d2bfea9e 100644 --- a/src/AboutAppInfoPopup.cs +++ b/src/AboutAppInfoPopup.cs @@ -358,20 +358,8 @@ namespace System.Application.UI b.AppendLine(); #endif - b.Append("[xamarin.essentials.supported] "); - static bool? GetXamarinEssentialsIsSupported() - { - try - { - if (typeof(MainThread2).Assembly.GetType("System.Application.XamarinEssentials").GetProperty("IsSupported", BindingFlags.Public | BindingFlags.Static).GetValue(null) is bool b) - return b; - } - catch - { - } - return null; - } - b.Append(GetXamarinEssentialsIsSupported().ToLowerString()); + b.Append("[essentials] "); + b.Append(Essentials.IsSupported.ToLowerString()); b.AppendLine(); var b_str = b.ToString(); diff --git a/src/Common.ClientLib/Application/Essentials/Clipboard2.cs b/src/Common.ClientLib/Application/Essentials/Clipboard2.cs index 62d165e98..cb4ee2aa9 100644 --- a/src/Common.ClientLib/Application/Essentials/Clipboard2.cs +++ b/src/Common.ClientLib/Application/Essentials/Clipboard2.cs @@ -13,7 +13,7 @@ namespace System.Application { public static async Task SetTextAsync(string? text) { - if (XamarinEssentials.IsSupported) + if (Essentials.IsSupported) { await Clipboard.SetTextAsync(text); } @@ -27,7 +27,7 @@ namespace System.Application public static async Task GetTextAsync() { - if (XamarinEssentials.IsSupported) + if (Essentials.IsSupported) { return await Clipboard.GetTextAsync(); } @@ -41,7 +41,7 @@ namespace System.Application { get { - if (XamarinEssentials.IsSupported) + if (Essentials.IsSupported) { return Clipboard.HasText; } diff --git a/src/Common.ClientLib/Application/Essentials/Email2.cs b/src/Common.ClientLib/Application/Essentials/Email2.cs index 3070b05d0..19fa80734 100644 --- a/src/Common.ClientLib/Application/Essentials/Email2.cs +++ b/src/Common.ClientLib/Application/Essentials/Email2.cs @@ -21,7 +21,7 @@ namespace System.Application public static async Task ComposeAsync(EmailMessage? message) { - if (XamarinEssentials.IsSupported) + if (Essentials.IsSupported) { await Email.ComposeAsync(message); } diff --git a/src/Common.ClientLib/Application/Essentials/XamarinEssentials.cs b/src/Common.ClientLib/Application/Essentials/Essentials.cs similarity index 93% rename from src/Common.ClientLib/Application/Essentials/XamarinEssentials.cs rename to src/Common.ClientLib/Application/Essentials/Essentials.cs index 965759c7a..d65121996 100644 --- a/src/Common.ClientLib/Application/Essentials/XamarinEssentials.cs +++ b/src/Common.ClientLib/Application/Essentials/Essentials.cs @@ -4,7 +4,7 @@ using Xamarin.Essentials; namespace System.Application { - internal static class XamarinEssentials + public static class Essentials { static readonly Lazy _IsSupported = new(() => { diff --git a/src/Common.ClientLib/Application/Essentials/FilePicker2.cs b/src/Common.ClientLib/Application/Essentials/FilePicker2.cs index 200565269..9ee7ca9fc 100644 --- a/src/Common.ClientLib/Application/Essentials/FilePicker2.cs +++ b/src/Common.ClientLib/Application/Essentials/FilePicker2.cs @@ -16,7 +16,7 @@ namespace System.Application { static readonly Lazy mIsSupportedFileExtensionFilter = new(() => { - if (XamarinEssentials.IsSupported) + if (Essentials.IsSupported) { return OperatingSystem2.IsWindows; } @@ -48,7 +48,7 @@ namespace System.Application public static async Task PickAsync(PickOptions? options = null) { - if (XamarinEssentials.IsSupported) + if (Essentials.IsSupported) { return await FilePicker.PickAsync(options); } @@ -137,7 +137,7 @@ namespace System.Application public static async Task> PickMultipleAsync(PickOptions? options = null) { - if (XamarinEssentials.IsSupported) + if (Essentials.IsSupported) { return await FilePicker.PickMultipleAsync(options); } @@ -286,7 +286,7 @@ namespace System.Application { static readonly Lazy _FileTypes = new(() => { - if (XamarinEssentials.IsSupported) + if (Essentials.IsSupported) { return FilePickerFileType.Images; } diff --git a/src/Common.ClientLib/Application/Essentials/MainThread2.cs b/src/Common.ClientLib/Application/Essentials/MainThread2.cs index a8ad9f44b..a67950a82 100644 --- a/src/Common.ClientLib/Application/Essentials/MainThread2.cs +++ b/src/Common.ClientLib/Application/Essentials/MainThread2.cs @@ -16,7 +16,7 @@ namespace System.Application { get { - if (XamarinEssentials.IsSupported) + if (Essentials.IsSupported) { return MainThread.IsMainThread; } @@ -29,7 +29,7 @@ namespace System.Application public static void BeginInvokeOnMainThread(Action action, DispatcherPriorityCompat priority = DispatcherPriorityCompat.Normal) { - if (XamarinEssentials.IsSupported) + if (Essentials.IsSupported) { MainThread.BeginInvokeOnMainThread(action); } @@ -48,7 +48,7 @@ namespace System.Application public static Task InvokeOnMainThreadAsync(Action action, DispatcherPriorityCompat priority = DispatcherPriorityCompat.Normal) { - if (XamarinEssentials.IsSupported) + if (Essentials.IsSupported) { return MainThread.InvokeOnMainThreadAsync(action); } @@ -85,7 +85,7 @@ namespace System.Application public static Task InvokeOnMainThreadAsync(Func func, DispatcherPriorityCompat priority = DispatcherPriorityCompat.Normal) { - if (XamarinEssentials.IsSupported) + if (Essentials.IsSupported) { return MainThread.InvokeOnMainThreadAsync(func); } @@ -117,7 +117,7 @@ namespace System.Application public static Task InvokeOnMainThreadAsync(Func funcTask, DispatcherPriorityCompat priority = DispatcherPriorityCompat.Normal) { - if (XamarinEssentials.IsSupported) + if (Essentials.IsSupported) { return MainThread.InvokeOnMainThreadAsync(funcTask); } @@ -150,7 +150,7 @@ namespace System.Application public static Task InvokeOnMainThreadAsync(Func> funcTask, DispatcherPriorityCompat priority = DispatcherPriorityCompat.Normal) { - if (XamarinEssentials.IsSupported) + if (Essentials.IsSupported) { return MainThread.InvokeOnMainThreadAsync(funcTask); } diff --git a/src/ST.Client.Android/ST.Client.Android.csproj b/src/ST.Client.Android/ST.Client.Android.csproj new file mode 100644 index 000000000..8d8494dc7 --- /dev/null +++ b/src/ST.Client.Android/ST.Client.Android.csproj @@ -0,0 +1,32 @@ + + + + net6.0-android;MonoAndroid11.0 + System.Application + System.Application.SteamTools.Client.Android + __HAVE_ANDROIDX__;$(DefineConstants) + + + + + + + + Properties\AssemblyInfo.cs + + + Properties\AssemblyInfo.Version.cs + + + Properties\InternalsVisibleTo.cs + + + + + + + + + diff --git a/src/ST.Client.Desktop.Avalonia.App/App.axaml.cs b/src/ST.Client.Desktop.Avalonia.App/App.axaml.cs index 84d6f75e1..5f4009860 100644 --- a/src/ST.Client.Desktop.Avalonia.App/App.axaml.cs +++ b/src/ST.Client.Desktop.Avalonia.App/App.axaml.cs @@ -529,5 +529,7 @@ namespace System.Application.UI #endregion string IAvaloniaApplication.RenderingSubsystemName => Program.RenderingSubsystemName; + + object IApplication.CurrentPlatformUIHost => MainWindow; } } \ No newline at end of file diff --git a/src/ST.Client.Desktop.Linux/ST.Client.Linux.csproj b/src/ST.Client.Desktop.Linux/ST.Client.Linux.csproj index 3c878bb85..ff504f395 100644 --- a/src/ST.Client.Desktop.Linux/ST.Client.Linux.csproj +++ b/src/ST.Client.Desktop.Linux/ST.Client.Linux.csproj @@ -7,12 +7,6 @@ LINUX;$(DefineConstants) - - - - - - Properties\AssemblyInfo.cs @@ -24,7 +18,7 @@ Properties\InternalsVisibleTo.cs - + Internals diff --git a/src/ST.Client.Desktop.Linux/Services/Implementation/LinuxPlatformServiceImpl.cs b/src/ST.Client.Desktop.Linux/Services/Implementation/LinuxPlatformServiceImpl.cs index 4f706e49c..c6b02241c 100644 --- a/src/ST.Client.Desktop.Linux/Services/Implementation/LinuxPlatformServiceImpl.cs +++ b/src/ST.Client.Desktop.Linux/Services/Implementation/LinuxPlatformServiceImpl.cs @@ -1,4 +1,3 @@ -using System.Application.Internals; using System.Application.Models; using System.Diagnostics; using System.IO; diff --git a/src/ST.Client.Desktop.Mac/Internals/UnixHelper.RunShellAsync.cs b/src/ST.Client.Desktop.Mac/Internals/UnixHelper.RunShellAsync.cs new file mode 100644 index 000000000..bbd709e1b --- /dev/null +++ b/src/ST.Client.Desktop.Mac/Internals/UnixHelper.RunShellAsync.cs @@ -0,0 +1,75 @@ +using System; +using System.Application.UI.Resx; +using System.Application.UI.ViewModels; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using System.Application.Services; + +static partial class UnixHelper +{ + static readonly Lazy _bin_bash = new(() => string.Format("{0}bin{0}bash", Path.DirectorySeparatorChar)); + + /// + /// /bin/bash + /// + public static string BinBash => _bin_bash.Value; + + /// + public static ValueTask RunShellAsync(string script, bool admin) + => RunShellAsync(script, admin, 3); + + /// + static async ValueTask RunShellAsync(string script, bool admin, sbyte retry_count) + { + if (retry_count <= 0) return; + + var file = new FileInfo(Path.Combine(IOPath.AppDataDirectory, $@"{(admin ? "sudo" : "")}shell.sh")); + + if (file.Exists) + file.Delete(); + var scriptContent = new StringBuilder(); + if (admin) + { + TextBoxWindowViewModel vm = new() + { + Title = AppResources.MacSudoPasswordTips, + InputType = TextBoxWindowViewModel.TextBoxInputType.Password, + Description = $"sudo {script}", + }; + await TextBoxWindowViewModel.ShowDialogAsync(vm); + if (string.IsNullOrWhiteSpace(vm.Value)) + return; + scriptContent.AppendLine($"echo \"{vm.Value}\" | sudo -S {script}"); + } + else + { + scriptContent.AppendLine(script); + } + using (var stream = file.CreateText()) + { + stream.Write(scriptContent); + stream.Flush(); + } + using var p = new Process(); + p.StartInfo.FileName = BinBash; + p.StartInfo.Arguments = $"\"{file.FullName}\""; + p.StartInfo.UseShellExecute = false; + p.StartInfo.RedirectStandardOutput = true; + p.Exited += async (_, _) => + { + if (file.Exists) + file.Delete(); + if (p.ExitCode != 0) + { + await RunShellAsync(script, admin, --retry_count); + } + }; + p.Start(); + var ret = p.StandardOutput.ReadToEnd(); + p.Kill(); + if (file.Exists) + file.Delete(); + } +} \ No newline at end of file diff --git a/src/ST.Client.Desktop.Mac/Internals/UnixHelper.cs b/src/ST.Client.Desktop.Mac/Internals/UnixHelper.cs deleted file mode 100644 index 9ec5b8601..000000000 --- a/src/ST.Client.Desktop.Mac/Internals/UnixHelper.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Application.UI.Resx; -using System.Application.UI.ViewModels; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using System.Application.Services; - -namespace System.Application.Internals -{ - static class UnixHelper - { - static readonly Lazy _bin_bash = new(() => string.Format("{0}bin{0}bash", Path.DirectorySeparatorChar)); - - /// - /// /bin/bash - /// - public static string BinBash => _bin_bash.Value; - - /// - public static ValueTask RunShellAsync(string script, bool admin) - => RunShellAsync(script, admin, 3); - - /// - static async ValueTask RunShellAsync(string script, bool admin, sbyte retry_count) - { - if (retry_count <= 0) return; - - var file = new FileInfo(Path.Combine(IOPath.AppDataDirectory, $@"{(admin ? "sudo" : "")}shell.sh")); - - if (file.Exists) - file.Delete(); - var scriptContent = new StringBuilder(); - if (admin) - { - TextBoxWindowViewModel vm = new() - { - Title = AppResources.MacSudoPasswordTips, - InputType = TextBoxWindowViewModel.TextBoxInputType.Password, - Description = $"sudo {script}", - }; - await TextBoxWindowViewModel.ShowDialogAsync(vm); - if (string.IsNullOrWhiteSpace(vm.Value)) - return; - scriptContent.AppendLine($"echo \"{vm.Value}\" | sudo -S {script}"); - } - else - { - scriptContent.AppendLine(script); - } - using (var stream = file.CreateText()) - { - stream.Write(scriptContent); - stream.Flush(); - } - using var p = new Process(); - p.StartInfo.FileName = BinBash; - p.StartInfo.Arguments = $"\"{file.FullName}\""; - p.StartInfo.UseShellExecute = false; - p.StartInfo.RedirectStandardOutput = true; - p.Exited += async (_, _) => - { - if (file.Exists) - file.Delete(); - if (p.ExitCode != 0) - { - await RunShellAsync(script, admin, --retry_count); - } - }; - p.Start(); - var ret = p.StandardOutput.ReadToEnd(); - p.Kill(); - if (file.Exists) - file.Delete(); - } - } -} \ No newline at end of file diff --git a/src/ST.Client.Desktop.Mac/Services/Implementation/MacPlatformServiceImpl.cs b/src/ST.Client.Desktop.Mac/Services/Implementation/MacPlatformServiceImpl.cs index 7bd448103..6c219cb00 100644 --- a/src/ST.Client.Desktop.Mac/Services/Implementation/MacPlatformServiceImpl.cs +++ b/src/ST.Client.Desktop.Mac/Services/Implementation/MacPlatformServiceImpl.cs @@ -5,7 +5,6 @@ using MonoMac.Foundation; using AppKit; using Foundation; #endif -using System.Application.Internals; using System.Application.Models; using System.Diagnostics; using System.IO; @@ -166,7 +165,7 @@ namespace System.Application.Services.Implementation static string GetSerialNumber() => GetIOPlatformExpertDevice("IOPlatformSerialNumber"); #if DEBUG - public static string GetPlatformUUID() => GetIOPlatformExpertDevice("IOPlatformUUID"); + static string GetPlatformUUID() => GetIOPlatformExpertDevice("IOPlatformUUID"); #endif static string GetMachineSecretKey() diff --git a/src/ST.Client.Maui.App/Platforms/Android/MainApplication.cs b/src/ST.Client.Maui.App/Platforms/Android/MainApplication.cs deleted file mode 100644 index f1295bf45..000000000 --- a/src/ST.Client.Maui.App/Platforms/Android/MainApplication.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Android.App; -using Android.Runtime; -using Microsoft.Maui; -using System; - -namespace ST.Client.Maui.App -{ - [Application] - public class MainApplication : MauiApplication - { - public MainApplication(IntPtr handle, JniHandleOwnership ownership) - : base(handle, ownership) - { - } - - protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); - } -} \ No newline at end of file diff --git a/src/ST.Client.iOS/ST.Client.iOS.csproj b/src/ST.Client.iOS/ST.Client.iOS.csproj new file mode 100644 index 000000000..dc82a255e --- /dev/null +++ b/src/ST.Client.iOS/ST.Client.iOS.csproj @@ -0,0 +1,26 @@ + + + + net6.0-ios;Xamarin.iOS10 + System.Application + System.Application.SteamTools.Client.iOS + + + + + Properties\AssemblyInfo.cs + + + Properties\AssemblyInfo.Version.cs + + + Properties\InternalsVisibleTo.cs + + + + + + + + + diff --git a/src/ST.Client/Services/IPlatformService.cs b/src/ST.Client/Services/IPlatformService.cs index baf030576..51a175093 100644 --- a/src/ST.Client/Services/IPlatformService.cs +++ b/src/ST.Client/Services/IPlatformService.cs @@ -7,6 +7,7 @@ using System.IO; using System.Security.Cryptography; using System.Application.Models; using System.Application.Settings; +using Xamarin.Essentials; #if NETSTANDARD using JustArchiNET.Madness; #else @@ -272,7 +273,28 @@ namespace System.Application.Services return GetMachineSecretKey(value); }); - (byte[] key, byte[] iv) MachineSecretKey { get; } + protected static (byte[] key, byte[] iv) GetMachineSecretKeyBySecureStorage() + { + if (!Essentials.IsSupported) throw new PlatformNotSupportedException(); + const string KEY_MACHINE_SECRET = "KEY_MACHINE_SECRET_2105"; + var guid = GetMachineSecretKeyGuid(); + static Guid GetMachineSecretKeyGuid() + { + Func> getAsync = () => SecureStorage.GetAsync(KEY_MACHINE_SECRET); + var guidStr = getAsync.RunSync(); + if (Guid.TryParse(guidStr, out var guid)) return guid; + guid = Guid.NewGuid(); + guidStr = guid.ToString(); + Func setAsync = () => SecureStorage.SetAsync(KEY_MACHINE_SECRET, guidStr); + setAsync.RunSync(); + return guid; + } + var r = AESUtils.GetParameters(guid.ToByteArray()); + return r; + } + + protected static readonly Lazy<(byte[] key, byte[] iv)> mMachineSecretKeyBySecureStorage = new(GetMachineSecretKeyBySecureStorage); + (byte[] key, byte[] iv) MachineSecretKey => mMachineSecretKeyBySecureStorage.Value; #endregion diff --git a/src/ST.Client/Settings/GeneralSettings.cs b/src/ST.Client/Settings/GeneralSettings.cs index f23fe45b0..7ac2a9227 100644 --- a/src/ST.Client/Settings/GeneralSettings.cs +++ b/src/ST.Client/Settings/GeneralSettings.cs @@ -104,5 +104,11 @@ namespace System.Application.Settings /// public static SerializableProperty UseOpenGL { get; } = GetProperty(defaultValue: OperatingSystem2.IsWindows7, autoSave: true); + + /// + /// 屏幕捕获(允许截图) + /// + public static SerializableProperty CaptureScreen { get; } + = GetProperty(defaultValue: false, autoSave: true); } } diff --git a/src/ST.Client/UI/IApplication.cs b/src/ST.Client/UI/IApplication.cs index fa1c18533..a93a2927f 100644 --- a/src/ST.Client/UI/IApplication.cs +++ b/src/ST.Client/UI/IApplication.cs @@ -99,5 +99,11 @@ namespace System.Application.UI [Obsolete] WinUI3_Desktop, } + + /// + /// 获取当前平台 UI Host + /// reference to the ViewController (if using Xamarin.iOS), Activity (if using Xamarin.Android) IWin32Window or IntPtr (if using .Net Framework). + /// + object CurrentPlatformUIHost { get; } } } \ No newline at end of file diff --git a/src/ST.Client/UI/ViewModels/Pages/About/AboutPageViewModel.Mobile.cs b/src/ST.Client/UI/ViewModels/Pages/About/AboutPageViewModel.Mobile.cs new file mode 100644 index 000000000..968e8f67e --- /dev/null +++ b/src/ST.Client/UI/ViewModels/Pages/About/AboutPageViewModel.Mobile.cs @@ -0,0 +1,146 @@ +using ReactiveUI; +using System.Application.Mvvm; +using System.Application.Services; +using System.Application.UI.Resx; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Drawing; +using System.Linq; +using System.Text; +using Xamarin.Essentials; + +// ReSharper disable once CheckNamespace +namespace System.Application.UI.ViewModels +{ + partial class AboutPageViewModel + { + ObservableCollection? preferenceButtons; + public ObservableCollection PreferenceButtons + { + get => preferenceButtons ?? throw new ArgumentNullException(nameof(preferenceButtons)); + set => this.RaiseAndSetIfChanged(ref preferenceButtons, value); + } + + public enum PreferenceButton + { + 检查更新 = 1, + 更新日志, + 常见问题疑难解答, + + 联系我们, + Bug反馈, + 社区翻译, + + 源码仓库, + 产品官网, + 开放源代码许可, + + 账号注销, + } + + public static bool IsHeaderPreferenceButton(PreferenceButton button) => button switch + { + PreferenceButton.检查更新 or + PreferenceButton.联系我们 or + PreferenceButton.源码仓库 or + PreferenceButton.账号注销 => true, + _ => false, + }; + + public sealed class PreferenceButtonViewModel : RIdTitleViewModel + { + PreferenceButtonViewModel() + { + } + + protected override string GetTitleById(PreferenceButton id) + { + var title = id switch + { + PreferenceButton.检查更新 => AppResources.CheckUpdate, + PreferenceButton.更新日志 => AppResources.About_UpdateLog, + PreferenceButton.常见问题疑难解答 => AppResources.About_FAQ, + PreferenceButton.开放源代码许可 => AppResources.About_OpenSource, + PreferenceButton.源码仓库 => AppResources.SourceRepository, + PreferenceButton.产品官网 => AppResources.ProductOfficialWebsite, + PreferenceButton.联系我们 => AppResources.About_Contactus, + PreferenceButton.Bug反馈 => AppResources.BugFeedback, + PreferenceButton.账号注销 => AppResources.DelAccount, + PreferenceButton.社区翻译 => AppResources.Translators, + _ => id.ToString(), + }; + return title; + } + + public static void RemoveAuthorized(ICollection collection, IDisposableHolder vm) + { + var removeArray = collection.Where(x => x.Id == PreferenceButton.账号注销).ToArray(); + Array.ForEach(removeArray, x => + { + collection.Remove(x); + x.OnUnbind(vm); + }); + } + + /// + /// 创建实例 + /// + /// + /// + /// + public static PreferenceButtonViewModel Create(PreferenceButton id, IDisposableHolder vm) + { + PreferenceButtonViewModel r = new() { Id = id, }; + r.OnBind(vm); + return r; + } + } + + public const string GitHub = "GitHub"; + public const string Gitee = "Gitee"; + public static readonly string[] SourceRepositories = new[] { GitHub, Gitee }; + public const string Title_0 = "Steam++ Tools "; + public const string Title_1 = "2.0"; + public const string Developers_0 = "Developers: "; + public const string Contributors_0 = "Contributors: "; + public const string Separator = " - "; + public const string Separator2 = " | "; + public const string At_Rmbadmin = "@软妹币玩家"; + public const string At_AigioL = "@AigioL"; + public const string At_Mossimos = "@Mossimos"; + public const string At_Cliencer = "@Cliencer克总"; + public const string BusinessCooperationContact_0 = "Business Cooperation Contact: "; + public const string OpenSourceLicensed_0 = "This open source software is licensed with "; + public const string OpenSourceLicensed_1 = "GPLv3 License"; + const string _ThemeAccentBrushKey = "#FF0078D7"; + public static readonly Color ThemeAccentBrushKey = ColorConverters.FromHex(_ThemeAccentBrushKey); + public const string Translation = "Translation"; + + public static readonly IReadOnlyDictionary contributors_translations = new Dictionary + { + { 沙中金, "English" }, + { EspRoy, "Spanish" }, + { Zhengye, "Italian" }, + }; + + public const string Translators_0 = "Translators:"; + + static readonly Lazy _TextTranslators = new(() => + { + StringBuilder str = new(Translators_0); + str.AppendLine(); + foreach (var item in contributors_translations) + { + const string tab_str = " "; + str.AppendLine(); + str.Append(tab_str); + str.Append(item.Value); + str.AppendLine(":"); + str.Append(tab_str); + str.AppendLine(item.Key); + } + return str.ToString(); + }); + public static string TextTranslators => _TextTranslators.Value; + } +} diff --git a/src/ST.Client/UI/ViewModels/Pages/About/AboutPageViewModel.cs b/src/ST.Client/UI/ViewModels/Pages/About/AboutPageViewModel.cs index 614f5ceea..4185b66d2 100644 --- a/src/ST.Client/UI/ViewModels/Pages/About/AboutPageViewModel.cs +++ b/src/ST.Client/UI/ViewModels/Pages/About/AboutPageViewModel.cs @@ -39,6 +39,28 @@ namespace System.Application.UI.ViewModels await UserService.Current.DelAccountAsync(); } }); + + if (IApplication.IsMobileLayout) + { + preferenceButtons = new(Enum2.GetAll().Select(x => PreferenceButtonViewModel.Create(x, this))); + + UserService.Current.WhenAnyValue(x => x.User).Subscribe(value => + { + if (value == null) + { + PreferenceButtonViewModel.RemoveAuthorized(preferenceButtons, this); + } + else + { + var delAccount = preferenceButtons.FirstOrDefault(x => x.Id == PreferenceButton.账号注销); + if (delAccount == null) + { + delAccount = PreferenceButtonViewModel.Create(PreferenceButton.账号注销, this); + preferenceButtons.Add(delAccount); + } + } + }).AddTo(this); + } } public string VersionDisplay => $"{ThisAssembly.VersionDisplay} for {DeviceInfo2.OSName} ({RuntimeInformation.ProcessArchitecture})"; diff --git a/src/ST.Client/UI/ViewModels/Pages/ExplorerPageViewModel.cs b/src/ST.Client/UI/ViewModels/Pages/ExplorerPageViewModel.cs new file mode 100644 index 000000000..69d44024d --- /dev/null +++ b/src/ST.Client/UI/ViewModels/Pages/ExplorerPageViewModel.cs @@ -0,0 +1,169 @@ +using ReactiveUI; +using System.Application.Services; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; + +// ReSharper disable once CheckNamespace +namespace System.Application.UI.ViewModels +{ + /// + /// 文件资源管理器(目录浏览)视图模型 + /// + public class ExplorerPageViewModel : PageViewModel + { + public ExplorerPageViewModel() + { + title = DefaultTitle; + } + + string _CurrentPath = string.Empty; + public string CurrentPath + { + get => _CurrentPath; + set + { + if (_CurrentPath == value) return; + _CurrentPath = value; + var title = DefaultTitle; + foreach (var item in GetRootPaths()) + { + if (_CurrentPath.StartsWith(item.Key)) + { + title = Path.DirectorySeparatorChar + + item.Value + + _CurrentPath.TrimStart(item.Key); + break; + } + } + Title = title; + PathInfos.Clear(); + if (string.IsNullOrEmpty(_CurrentPath)) + { + PathInfos.AddRange(GetRootPathInfoViewModels>()); + } + else + { + AddRange(PathInfos, _CurrentPath); + } + } + } + + static readonly string DefaultTitle = Path.DirectorySeparatorChar.ToString(); + + public ObservableCollection PathInfos { get; } = GetRootPathInfoViewModels>(); + + public class PathInfoViewModel : ReactiveObject + { + public PathInfoViewModel(FileSystemInfo fileSystemInfo, string? name = null) + { + Name = name ?? fileSystemInfo.Name; + FullName = fileSystemInfo.FullName; + Desc = $" | {fileSystemInfo.CreationTime.ToString(DateTimeFormat.Standard)}"; + if (fileSystemInfo is FileInfo fileInfo) + { + IsDirectory = false; + var fileSize = IOPath.GetSizeString(fileInfo.Length); + Desc = $"Size: {fileSize}{Desc}"; + } + else if (fileSystemInfo is DirectoryInfo dirInfo) + { + IsDirectory = true; + var ignore_rule_log_under_cache_on_cache = IApplication.LogUnderCache && dirInfo.FullName == IOPath.CacheDirectory; + var filesCount = dirInfo.GetFiles().Length; + var dirsCount = dirInfo.GetDirectories().Length; + Desc = $"Count: {filesCount + (ignore_rule_log_under_cache_on_cache ? dirsCount - 1 : dirsCount)}{Desc}"; + } + else + { + throw new NotSupportedException(); + } + } + + public bool IsDirectory { get; } + + public string Name { get; } + + public string FullName { get; } + + public string Desc { get; } + } + + static IEnumerable> GetRootPaths() + { + yield return new(IOPath.AppDataDirectory, IOPath.DirName_AppData); + yield return new(IApplication.LogDirPath, IApplication.LogDirName); + yield return new(IOPath.CacheDirectory, IOPath.DirName_Cache); + } + + static T GetRootPathInfoViewModels() where T : ICollection, new() + { + return new T() + { + new PathInfoViewModel(new DirectoryInfo(IOPath.AppDataDirectory), IOPath.DirName_AppData), + new PathInfoViewModel(new DirectoryInfo(IOPath.CacheDirectory), IOPath.DirName_Cache), + new PathInfoViewModel(new DirectoryInfo(IApplication.LogDirPath)), + }; + } + + public static void AddRange(IList list, string dirPath) + { + var ignore_rule_log_under_cache_on_cache = IApplication.LogUnderCache && dirPath == IOPath.CacheDirectory; + var dirInfo = new DirectoryInfo(dirPath); + Array.ForEach(dirInfo.GetDirectories(), x => + { + if (ignore_rule_log_under_cache_on_cache && x.Name == IApplication.LogDirName) return; + list.Add(new(x)); + }); + Array.ForEach(dirInfo.GetFiles(), x => list.Add(new(x))); + } + + public void OnItemClick(PathInfoViewModel pathInfo) + { + if (pathInfo.IsDirectory) + { + CurrentPath = pathInfo.FullName; + } + else + { + var extension = Path.GetExtension(pathInfo.FullName); + if (string.Equals(extension, FileEx.JSON, StringComparison.OrdinalIgnoreCase) || + string.Equals(extension, FileEx.TXT, StringComparison.OrdinalIgnoreCase) || + string.Equals(extension, FileEx.LOG, StringComparison.OrdinalIgnoreCase)) + { + IPlatformService.Instance.OpenFileByTextReader(pathInfo.FullName); + } + } + } + + public bool OnBack() + { + if (Title != DefaultTitle) + { + var dirs = Title.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); + if (dirs.Length == 1) + { + CurrentPath = string.Empty; + } + else if (dirs.Length > 0) + { + foreach (var item in GetRootPaths()) + { + if (item.Value == dirs[0]) + { + CurrentPath = Path.Combine(new[] { item.Key }.Concat(dirs.Skip(1).Take(dirs.Length - 2)).ToArray()); + break; + } + } + } + else + { + return false; + } + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/src/ST.Client/UI/ViewModels/Pages/LocalAuthPageViewModel.Mobile.cs b/src/ST.Client/UI/ViewModels/Pages/LocalAuthPageViewModel.Mobile.cs new file mode 100644 index 000000000..ddf558d73 --- /dev/null +++ b/src/ST.Client/UI/ViewModels/Pages/LocalAuthPageViewModel.Mobile.cs @@ -0,0 +1,49 @@ +using System.Application.UI.Resx; + +// ReSharper disable once CheckNamespace +namespace System.Application.UI.ViewModels +{ + partial class LocalAuthPageViewModel + { + public enum ActionItem + { + Add = 1, + Encrypt, + Export, + Lock, + Refresh, + } + + public static string ToString2(ActionItem action) => action switch + { + ActionItem.Add => AppResources.Add, + ActionItem.Encrypt => AppResources.Encrypt, + ActionItem.Export => AppResources.Export, + ActionItem.Lock => AppResources.Lock, + ActionItem.Refresh => AppResources.Refresh, + _ => throw new ArgumentOutOfRangeException(nameof(action), action, null), + }; + + public void MenuItemClick(ActionItem id) + { + switch (id) + { + case ActionItem.Add: + AddAuthCommand.Invoke(); + break; + case ActionItem.Encrypt: + EncryptionAuthCommand.Invoke(); + break; + case ActionItem.Export: + ExportAuthCommand.Invoke(); + break; + case ActionItem.Lock: + LockCommand.Invoke(); + break; + case ActionItem.Refresh: + RefreshAuthCommand.Invoke(); + break; + } + } + } +} \ No newline at end of file diff --git a/src/ST.Client/UI/ViewModels/Pages/MyPageViewModel.cs b/src/ST.Client/UI/ViewModels/Pages/MyPageViewModel.cs new file mode 100644 index 000000000..690ed997b --- /dev/null +++ b/src/ST.Client/UI/ViewModels/Pages/MyPageViewModel.cs @@ -0,0 +1,214 @@ +using ReactiveUI; +using System.Application.Mvvm; +using System.Application.Services; +using System.Application.UI.Resx; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +// ReSharper disable once CheckNamespace +namespace System.Application.UI.ViewModels +{ + /// + /// 我的页面视图模型 + /// + public partial class MyPageViewModel : PageViewModel + { + public static string DisplayName => AppResources.My; + + public MyPageViewModel() + { + preferenceButtons = new() + { + PreferenceButtonViewModel.Create(PreferenceButton.UserProfile, this), + PreferenceButtonViewModel.Create(PreferenceButton.BindPhoneNumber, this), + PreferenceButtonViewModel.Create(PreferenceButton.Settings, this, 1), + PreferenceButtonViewModel.Create(PreferenceButton.About, this, 1), + }; + + UserService.Current.WhenAnyValue(x => x.User).Subscribe(value => + { + if (value == null) + { + nickNameNullValLangChangeSubscribe?.RemoveTo(this); + nickNameNullValLangChangeSubscribe = R.Current.WhenAnyValue(x => x.Res) + .Subscribe(_ => + { + // 未登录时显示的文本,多语言绑定 + NickName = NickNameNullVal; + }).AddTo(this); + + //PreferenceButtonViewModel.RemoveAuthorized(preferenceButtons, this); + } + else + { + nickNameNullValLangChangeSubscribe?.RemoveTo(this); + nickNameNullValLangChangeSubscribe = null; + NickName = value.NickName; + + //var editProfile = preferenceButtons.FirstOrDefault(x => x.Id == PreferenceButton.EditProfile); + //if (editProfile == null) + //{ + // editProfile = PreferenceButtonViewModel.Create(PreferenceButton.EditProfile, this); + // preferenceButtons.Insert(0, editProfile); + //} + + //var phoneNumber = preferenceButtons.FirstOrDefault(x => PreferenceButtonViewModel.IsPhoneNumber(x.Id)); + //if (phoneNumber == null) + //{ + // phoneNumber = PreferenceButtonViewModel.Create(PreferenceButtonViewModel.GetPhoneNumberId(UserService.Current.HasPhoneNumber), this); + // preferenceButtons.Insert(1, phoneNumber); + //} + } + }).AddTo(this); + + UserService.Current.WhenAnyValue(x => x.HasPhoneNumber).Subscribe(value => + { + var id = PreferenceButtonViewModel.GetPhoneNumberId(value); + foreach (var item in preferenceButtons) + { + if (item.Id != id && PreferenceButtonViewModel.IsPhoneNumber(item.Id)) + { + item.Id = id; + } + } + }).AddTo(this); + } + + IDisposable? nickNameNullValLangChangeSubscribe; + static string NickNameNullVal => AppResources.LoginAndRegister; + string nickName = NickNameNullVal; + public string NickName + { + get => nickName; + set => this.RaiseAndSetIfChanged(ref nickName, value); + } + + ObservableCollection preferenceButtons; + /// + /// 我的选项按钮组 + /// + public ObservableCollection PreferenceButtons + { + get => preferenceButtons; + set => this.RaiseAndSetIfChanged(ref preferenceButtons, value); + } + + /// + /// 我的选项按钮组唯一键 + /// + public enum PreferenceButton + { + UserProfile = 1, + BindPhoneNumber, + ChangePhoneNumber, + Settings, + About, + } + + /// + /// 我的选项按钮视图模型 + /// + public sealed class PreferenceButtonViewModel : RIdTitleIconViewModel, IReadOnlyItemViewGroup + { + PreferenceButtonViewModel() + { + } + + public int ItemViewGroup { get; set; } + + protected override string GetTitleById(PreferenceButton id) + { + var title = id switch + { + PreferenceButton.UserProfile => AppResources.UserProfile, + PreferenceButton.BindPhoneNumber => AppResources.User_BindPhoneNum, + PreferenceButton.ChangePhoneNumber => AppResources.User_ChangePhoneNum, + PreferenceButton.Settings => AppResources.Settings, + PreferenceButton.About => AppResources.About, + _ => string.Empty, + }; + return title; + } + + protected override ResIcon GetIconById(PreferenceButton id) + { + var icon = id switch + { + PreferenceButton.UserProfile => ResIcon.AccountBox, + PreferenceButton.BindPhoneNumber => ResIcon.Phone, + PreferenceButton.ChangePhoneNumber => ResIcon.Phone, + PreferenceButton.Settings => ResIcon.Settings, + PreferenceButton.About => ResIcon.Info, + _ => default, + }; + return icon; + } + + /// + /// 判断键是否为手机号相关,绑定手机、换绑手机 + /// + /// + /// + public static bool IsPhoneNumber(PreferenceButton id) => id == PreferenceButton.BindPhoneNumber || id == PreferenceButton.ChangePhoneNumber; + +#if DEBUG + /// + /// 从选项按钮组中移除已登录才有的选项组 + /// + /// + /// + [Obsolete("未登录时不隐藏选项,点击相关选项跳转登录", true)] + public static void RemoveAuthorized(ICollection collection, IDisposableHolder vm) + { + var removeArray = collection.Where(x => IsPhoneNumber(x.Id) || x.Id == PreferenceButton.UserProfile).ToArray(); + Array.ForEach(removeArray, x => + { + collection.Remove(x); + x.OnUnbind(vm); + }); + } +#endif + + /// + /// 根据是否有手机号码确定键为[绑定手机]还是[换绑手机] + /// + /// + /// + public static PreferenceButton GetPhoneNumberId(bool hasPhoneNumber) + { + return hasPhoneNumber ? PreferenceButton.ChangePhoneNumber : PreferenceButton.BindPhoneNumber; + } + + /// + /// 创建实例 + /// + /// + /// + /// + public static PreferenceButtonViewModel Create(PreferenceButton id, IDisposableHolder vm, int groupId = default) + { + PreferenceButtonViewModel r = new() { Id = id, ItemViewGroup = groupId, }; + r.OnBind(vm); + return r; + } + + static bool GetAuthentication(PreferenceButton id) + { + var r = id switch + { + PreferenceButton.UserProfile or + PreferenceButton.BindPhoneNumber or + PreferenceButton.ChangePhoneNumber => true, + _ => false, + }; + return r; + } + + /// + /// 是否需要已登录的用户 + /// + public bool Authentication => GetAuthentication(id); + } + } +} \ No newline at end of file diff --git a/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/AddAuthWindowViewModel.Mobile.cs b/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/AddAuthWindowViewModel.Mobile.cs new file mode 100644 index 000000000..695c58f15 --- /dev/null +++ b/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/AddAuthWindowViewModel.Mobile.cs @@ -0,0 +1,18 @@ +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.Text; + +// ReSharper disable once CheckNamespace +namespace System.Application.UI.ViewModels +{ + partial class AddAuthWindowViewModel + { + private string? _LoginSteamLoadingText; + public string? LoginSteamLoadingText + { + get => _LoginSteamLoadingText; + set => this.RaiseAndSetIfChanged(ref _LoginSteamLoadingText, value); + } + } +} diff --git a/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/AddAuthWindowViewModel.cs b/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/AddAuthWindowViewModel.cs index 40e6ca0f8..0a4f767ca 100644 --- a/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/AddAuthWindowViewModel.cs +++ b/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/AddAuthWindowViewModel.cs @@ -496,12 +496,5 @@ namespace System.Application.UI.ViewModels public ICommand SdaBtn_Click { get; } public ICommand WinAuthBtn_Click { get; } - - string? _LoginSteamLoadingText; - public string? LoginSteamLoadingText - { - get => _LoginSteamLoadingText; - set => this.RaiseAndSetIfChanged(ref _LoginSteamLoadingText, value); - } } } diff --git a/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/AuthTradeWindowViewModel.Mobile.cs b/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/AuthTradeWindowViewModel.Mobile.cs new file mode 100644 index 000000000..dd8fa2454 --- /dev/null +++ b/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/AuthTradeWindowViewModel.Mobile.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Text; +using DynamicData; +using DynamicData.Binding; +using ReactiveUI; +using System.Application.UI.Resx; +using System.Linq; +using System.Reactive.Linq; + +// ReSharper disable once CheckNamespace +namespace System.Application.UI.ViewModels +{ + partial class AuthTradeWindowViewModel + { + public enum ActionItem + { + ConfirmAll = 1, + CancelAll, + Refresh, + Logout, + } + + public void MenuItemClick(ActionItem id) + { + switch (id) + { + case ActionItem.ConfirmAll: + ConfirmAllButton_Click(); + break; + case ActionItem.CancelAll: + CancelAllButton_Click(); + break; + case ActionItem.Refresh: + Refresh_Click(); + break; + case ActionItem.Logout: + Logout_Click(); + break; + } + } + + public static string ToString2(ActionItem action) => action switch + { + ActionItem.ConfirmAll => AppResources.LocalAuth_AuthTrade_ConfirmAll, + ActionItem.CancelAll => AppResources.LocalAuth_AuthTrade_CancelAll, + ActionItem.Refresh => AppResources.Refresh, + ActionItem.Logout => AppResources.Logout, + _ => throw new ArgumentOutOfRangeException(nameof(action), action, null), + }; + + private bool _UnselectAll; + /// + /// 全不选 + /// + public bool UnselectAll + { + get => _UnselectAll; + set + { + if (this.RaiseAndSetIfChangedReturnIsNotChange(ref _UnselectAll, value)) return; + foreach (var item in Confirmations) + { + if (item.IsOperate != 0) continue; + item.NotChecked = value; + } + } + } + + private string? _SelectAllText; + /// + /// 全选文字,当值为 时应隐藏底部操作区域 + /// + public string? SelectAllText + { + get => _SelectAllText; + set => this.RaiseAndSetIfChanged(ref _SelectAllText, value); + } + + /// + /// 注册全选监听 + /// + void RegisterSelectAllObservable() => this.WhenAnyValue(x => x.Confirmations) + .Subscribe(x => x? + .ToObservableChangeSet() + .AutoRefresh(x => x.NotChecked) + .ToCollection() + .Select(x => + { + var select_count = x.Count(y => y.IsOperate == 0 && !y.NotChecked); + return (select_count, count: x.Count(y => y.IsOperate == 0)); + }) + .Subscribe(x => + { + if (x.count > 0) + { + var unselectAll = x.select_count != x.count; + if (_UnselectAll != unselectAll) + { + _UnselectAll = unselectAll; + this.RaisePropertyChanged(nameof(UnselectAll)); + } + SelectAllText = AppResources.SelectAllText_.Format(x.select_count, x.count); + } + else + { + SelectAllText = null; + } + }).AddTo(this)).AddTo(this); + } +} diff --git a/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/AuthTradeWindowViewModel.cs b/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/AuthTradeWindowViewModel.cs index 0dd1b3f0c..57e1ccff7 100644 --- a/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/AuthTradeWindowViewModel.cs +++ b/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/AuthTradeWindowViewModel.cs @@ -22,7 +22,6 @@ namespace System.Application.UI.ViewModels { public AuthTradeWindowViewModel() : base() { - } public AuthTradeWindowViewModel(MyAuthenticator? auth) : base(auth) @@ -72,14 +71,6 @@ namespace System.Application.UI.ViewModels _ => true, }; - /// - /// 注册全选监听 - /// - void RegisterSelectAllObservable() - { - if (!IApplication.IsMobileLayout) return; - } - private string? AuthPassword; private bool AuthIsLocal; diff --git a/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/ExportAuthWindowViewModel.Mobile.cs b/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/ExportAuthWindowViewModel.Mobile.cs new file mode 100644 index 000000000..8ab8e11ac --- /dev/null +++ b/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/ExportAuthWindowViewModel.Mobile.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +// ReSharper disable once CheckNamespace +namespace System.Application.UI.ViewModels +{ + partial class ExportAuthWindowViewModel + { + public const string DefaultExportAuthDirName = "ExportAuths"; + } +} diff --git a/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/ExportAuthWindowViewModel.cs b/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/ExportAuthWindowViewModel.cs index 862d4de23..6581fffad 100644 --- a/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/ExportAuthWindowViewModel.cs +++ b/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/ExportAuthWindowViewModel.cs @@ -13,7 +13,7 @@ using static System.Application.FilePicker2; // ReSharper disable once CheckNamespace namespace System.Application.UI.ViewModels { - public class ExportAuthWindowViewModel : WindowViewModel + public partial class ExportAuthWindowViewModel : WindowViewModel { public static string DisplayName => AppResources.LocalAuth_ExportAuth; diff --git a/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/MyAuthenticatorWindowViewModel.cs b/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/MyAuthenticatorWindowViewModel.cs index b9cab6aaa..c9e9fd793 100644 --- a/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/MyAuthenticatorWindowViewModel.cs +++ b/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/MyAuthenticatorWindowViewModel.cs @@ -8,9 +8,11 @@ namespace System.Application.UI.ViewModels { public partial class MyAuthenticatorWindowViewModel : WindowViewModel, IExplicitHasValue { - protected readonly MyAuthenticator? MyAuthenticator; + protected readonly MyAuthenticator? _MyAuthenticator; protected readonly GAPAuthenticatorValueDTO.SteamAuthenticator? _Authenticator; + public MyAuthenticator? MyAuthenticator => _MyAuthenticator; + public MyAuthenticatorWindowViewModel() { InitializeComponent(); @@ -18,7 +20,7 @@ namespace System.Application.UI.ViewModels public MyAuthenticatorWindowViewModel(MyAuthenticator? auth) : this() { - MyAuthenticator = auth; + _MyAuthenticator = auth; if (auth?.AuthenticatorData.Value is GAPAuthenticatorValueDTO.SteamAuthenticator authenticator) { _Authenticator = authenticator; @@ -27,6 +29,9 @@ namespace System.Application.UI.ViewModels protected virtual void InitializeComponent() { } - public virtual bool ExplicitHasValue() => MyAuthenticator != null && _Authenticator != null; + public virtual bool ExplicitHasValue() + => _MyAuthenticator != null && + (_MyAuthenticator.AuthenticatorData.Platform != GamePlatform.Steam + || _Authenticator != null); } } diff --git a/src/ST.Client/UI/ViewModels/Windows/LoginOrRegisterWindowViewModel.Mobile.cs b/src/ST.Client/UI/ViewModels/Windows/LoginOrRegisterWindowViewModel.Mobile.cs new file mode 100644 index 000000000..1654ef568 --- /dev/null +++ b/src/ST.Client/UI/ViewModels/Windows/LoginOrRegisterWindowViewModel.Mobile.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Input; + +// ReSharper disable once CheckNamespace +namespace System.Application.UI.ViewModels +{ + partial class LoginOrRegisterWindowViewModel + { + public ICommand? TbPhoneNumberReturnCommand { get; set; } + } +} diff --git a/src/ST.Client/UI/ViewModels/Windows/UserProfileWindowViewModel.Mobile.cs b/src/ST.Client/UI/ViewModels/Windows/UserProfileWindowViewModel.Mobile.cs new file mode 100644 index 000000000..a2d3d7d02 --- /dev/null +++ b/src/ST.Client/UI/ViewModels/Windows/UserProfileWindowViewModel.Mobile.cs @@ -0,0 +1,41 @@ +using System.Application.Services; +using System.Application.UI.Resx; + +// ReSharper disable once CheckNamespace +namespace System.Application.UI.ViewModels +{ + partial class UserProfileWindowViewModel + { + public static string GetIsBindOrUnbundleBtnText(FastLoginChannel channel) => IsBindOrUnbundle(channel) ? AppResources.Bind : AppResources.Unbundling; + + public static bool IsBindOrUnbundle(FastLoginChannel channel) => channel switch + { + FastLoginChannel.Steam => UserService.Current.User?.SteamAccountId == null, + FastLoginChannel.Microsoft => UserService.Current.User?.MicrosoftAccountEmail == null, + FastLoginChannel.Apple => UserService.Current.User?.AppleAccountEmail == null, + FastLoginChannel.QQ => UserService.Current.User?.QQNickName == null, + _ => throw new ArgumentOutOfRangeException(nameof(channel), channel.ToString()), + }; + + public static string? GetIsBindOrUnbundleTbText(FastLoginChannel channel) => channel switch + { + FastLoginChannel.Steam => UserService.Current.User?.SteamAccountId?.ToString(), + FastLoginChannel.Microsoft => UserService.Current.User?.MicrosoftAccountEmail, + FastLoginChannel.Apple => UserService.Current.User?.AppleAccountEmail, + FastLoginChannel.QQ => UserService.Current.User?.QQNickName, + _ => throw new ArgumentOutOfRangeException(nameof(channel), channel.ToString()), + }; + + public void OnBindOrUnbundleFastLoginClick(FastLoginChannel channel) + { + if (IsBindOrUnbundle(channel)) + { + OnBindFastLoginClick.Invoke(channel.ToString()); + } + else + { + OnUnbundleFastLoginClick.Invoke(channel.ToString()); + } + } + } +} \ No newline at end of file