diff --git a/references/AvaloniaGif b/references/AvaloniaGif index 16c810dce..0356f4bad 160000 --- a/references/AvaloniaGif +++ b/references/AvaloniaGif @@ -1 +1 @@ -Subproject commit 16c810dce2178ec554ccd29b57359f8ca37bbb69 +Subproject commit 0356f4bad09c6236a5ff7f7d1a03af669575f054 diff --git a/src/Common.ClientLib.Droid/Application/DarkModeUtil.cs b/src/Common.ClientLib.Droid/Application/DarkModeUtil.cs index 8f9edf3ae..3738a8950 100644 --- a/src/Common.ClientLib.Droid/Application/DarkModeUtil.cs +++ b/src/Common.ClientLib.Droid/Application/DarkModeUtil.cs @@ -1,7 +1,7 @@ -using Android.Content; +using Android.Content; using Android.Content.Res; -using Android.OS; -using AndroidApplication = Android.App.Application; +//using Android.OS; +//using AndroidApplication = Android.App.Application; namespace System.Application { @@ -38,16 +38,16 @@ namespace System.Application /// /// /// - static bool IsDarkMode(Context context) + public static bool IsDarkMode(Context context) { var isNight = IsNight(context); return isNight.HasValue && isNight.Value; } - /// - /// 是否正在安卓Q中使用深色主题(Dark) - /// - public static bool IsDarkModeQ - => Build.VERSION.SdkInt >= BuildVersionCodes.Q && IsDarkMode(AndroidApplication.Context); + ///// + ///// 是否正在安卓Q中使用深色主题(Dark) + ///// + //public static bool IsDarkModeQ + // => Build.VERSION.SdkInt >= BuildVersionCodes.Q && IsDarkMode(AndroidApplication.Context); } } \ No newline at end of file diff --git a/src/Common.ClientLib.Droid/Application/UI/Internals/IToolbar.cs b/src/Common.ClientLib.Droid/Application/UI/Internals/IToolbar.cs index e40773cf9..ce0baf229 100644 --- a/src/Common.ClientLib.Droid/Application/UI/Internals/IToolbar.cs +++ b/src/Common.ClientLib.Droid/Application/UI/Internals/IToolbar.cs @@ -1,4 +1,4 @@ -using AndroidX.AppCompat.App; +using AndroidX.AppCompat.App; using AndroidX.AppCompat.Widget; using System.Application.UI.Internals; using System.Common; diff --git a/src/Common.ClientLib/Application/UI/ViewModels/IdTitleIconViewModel.cs b/src/Common.ClientLib/Application/UI/ViewModels/IdTitleIconViewModel.cs index 12ab9b4b0..e261f5f35 100644 --- a/src/Common.ClientLib/Application/UI/ViewModels/IdTitleIconViewModel.cs +++ b/src/Common.ClientLib/Application/UI/ViewModels/IdTitleIconViewModel.cs @@ -4,6 +4,8 @@ namespace System.Application.UI.ViewModels { public abstract class IdTitleIconViewModel : ReactiveObject { + protected virtual bool IgnoreIcon { get; } + protected TId? id; /// /// 唯一键 @@ -15,7 +17,7 @@ namespace System.Application.UI.ViewModels { if (this.RaiseAndSetIfChanged2(ref id, value)) return; Title = GetTitleById(value); - Icon = GetIconById(value); + if (!IgnoreIcon) Icon = GetIconById(value); OnIdChanged(value); } } diff --git a/src/ST.Client.Desktop.Avalonia/Application/UI/CefNetApp.cs b/src/ST.Client.Desktop.Avalonia/Application/UI/CefNetApp.cs index df7ba372a..251a9e74b 100644 --- a/src/ST.Client.Desktop.Avalonia/Application/UI/CefNetApp.cs +++ b/src/ST.Client.Desktop.Avalonia/Application/UI/CefNetApp.cs @@ -251,7 +251,7 @@ navigator.__proto__ = newProto; return theme switch { AppTheme.FollowingSystem => GetThemeStringByFollowingSystem(), - _ => theme.ToString(), + _ => theme.ToString2(), }; static string GetThemeStringByFollowingSystem() { @@ -267,8 +267,8 @@ navigator.__proto__ = newProto; { goto dark; } - return "auto"; - dark: return AppTheme.Dark.ToString(); + return AppTheme.FollowingSystem.ToString2(); + dark: return AppTheme.Dark.ToString2(); } } } diff --git a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/About/About_Changelog.axaml.cs b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/About/About_Changelog.axaml.cs index a09778014..60c68aeeb 100644 --- a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/About/About_Changelog.axaml.cs +++ b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/About/About_Changelog.axaml.cs @@ -18,7 +18,7 @@ namespace System.Application.UI.Views.Pages webView = this.FindControl(nameof(webView)); webView.Url = string.Format( - "https://steampp.net/changelogbox?theme={0}&language={1}", + UrlConstants.OfficialWebsite_Box_Changelog_, CefNetApp.GetTheme(), R.Language); } diff --git a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/About/About_FAQPage.axaml.cs b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/About/About_FAQPage.axaml.cs index 791369992..db30ea9c1 100644 --- a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/About/About_FAQPage.axaml.cs +++ b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/About/About_FAQPage.axaml.cs @@ -17,7 +17,7 @@ namespace System.Application.UI.Views.Pages InitializeComponent(); webViewQA = this.FindControl("webViewQA"); webViewQA.Url = string.Format( - "https://steampp.net/faqbox?theme={0}&language={1}", + UrlConstants.OfficialWebsite_Box_Faq_, CefNetApp.GetTheme(), R.Language); } diff --git a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/ShareManagePage/ShareManageWindow.axaml b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/ShareManagePage/ShareManageWindow.axaml index 9cc2809a7..73a872c1f 100644 --- a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/ShareManagePage/ShareManageWindow.axaml +++ b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/ShareManagePage/ShareManageWindow.axaml @@ -20,7 +20,7 @@ x:DataType="vm:ShareManageViewModel" x:CompileBindings="True"> - + - - - @@ -226,6 +226,6 @@ - - + + diff --git a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/WebView3Window.axaml.cs b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/WebView3Window.axaml.cs index 8b7f60fc4..f37525214 100644 --- a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/WebView3Window.axaml.cs +++ b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Windows/WebView3Window.axaml.cs @@ -238,7 +238,7 @@ namespace System.Application.UI.Views.Windows } else if (IsHttpUrl(x)) { - if (x.StartsWith("https://steampp.net", StringComparison.OrdinalIgnoreCase)) + if (x.StartsWith(UrlConstants.OfficialWebsite, StringComparison.OrdinalIgnoreCase)) x = string.Format( x + "?theme={0}&language={1}", CefNetApp.GetTheme(), diff --git a/src/ST.Client.Desktop/Models/Enum/AppTheme.cs b/src/ST.Client.Desktop/Models/Enum/AppTheme.cs index 48e795d45..ebab2cb0b 100644 --- a/src/ST.Client.Desktop/Models/Enum/AppTheme.cs +++ b/src/ST.Client.Desktop/Models/Enum/AppTheme.cs @@ -1,4 +1,6 @@ -namespace System.Application.Models +using System.Application.Models; + +namespace System.Application.Models { /// /// 应用程序主题 @@ -23,6 +25,23 @@ /// /// 自定义主题 /// + [Obsolete] Custom, } +} + +namespace System +{ + public static partial class AppThemeEnumExtensions + { + public static string ToString2(this AppTheme value) + { + return value switch + { + AppTheme.FollowingSystem => "auto", + AppTheme.Light => "light", + _ => "dark", + }; + } + } } \ No newline at end of file diff --git a/src/ST.Client.Desktop/Services/Mvvm/UserService.cs b/src/ST.Client.Desktop/Services/Mvvm/UserService.cs index 7bf427f1b..10ace03f1 100644 --- a/src/ST.Client.Desktop/Services/Mvvm/UserService.cs +++ b/src/ST.Client.Desktop/Services/Mvvm/UserService.cs @@ -73,7 +73,7 @@ namespace System.Application.Services #endif } - public async void DelAccount() + public async Task DelAccountAsync() { if (!IsAuthenticated) return; var msg = AppResources.Success_.Format(AppResources.DelAccount); diff --git a/src/ST.Client.Desktop/UI/Resx/AppResources.Designer.cs b/src/ST.Client.Desktop/UI/Resx/AppResources.Designer.cs index 93a2ac2e2..2d70921f8 100644 --- a/src/ST.Client.Desktop/UI/Resx/AppResources.Designer.cs +++ b/src/ST.Client.Desktop/UI/Resx/AppResources.Designer.cs @@ -440,6 +440,15 @@ namespace System.Application.UI.Resx { } } + /// + /// 查找类似 Bug 反馈 的本地化字符串。 + /// + public static string BugFeedback { + get { + return ResourceManager.GetString("BugFeedback", resourceCulture); + } + } + /// /// 查找类似 取消 的本地化字符串。 /// @@ -2520,6 +2529,15 @@ namespace System.Application.UI.Resx { } } + /// + /// 查找类似 产品官网 的本地化字符串。 + /// + public static string ProductOfficialWebsite { + get { + return ResourceManager.GetString("ProductOfficialWebsite", resourceCulture); + } + } + /// /// 查找类似 恢复代码 的本地化字符串。 /// @@ -2988,6 +3006,15 @@ namespace System.Application.UI.Resx { } } + /// + /// 查找类似 源码仓库 的本地化字符串。 + /// + public static string SourceRepository { + get { + return ResourceManager.GetString("SourceRepository", resourceCulture); + } + } + /// /// 查找类似 开始更新 的本地化字符串。 /// diff --git a/src/ST.Client.Desktop/UI/Resx/AppResources.resx b/src/ST.Client.Desktop/UI/Resx/AppResources.resx index 97d1ab529..1f80100e3 100644 --- a/src/ST.Client.Desktop/UI/Resx/AppResources.resx +++ b/src/ST.Client.Desktop/UI/Resx/AppResources.resx @@ -245,6 +245,9 @@ 继续 + + Bug 反馈 + 取消 @@ -942,6 +945,9 @@ 请选择 + + 产品官网 + 恢复代码 @@ -1099,6 +1105,9 @@ 短信验证代码 + + 源码仓库 + 开始更新 diff --git a/src/ST.Client.Desktop/UI/ViewModels/Pages/About/AboutPageViewModel.cs b/src/ST.Client.Desktop/UI/ViewModels/Pages/About/AboutPageViewModel.cs index 70a359ada..ffeaf1297 100644 --- a/src/ST.Client.Desktop/UI/ViewModels/Pages/About/AboutPageViewModel.cs +++ b/src/ST.Client.Desktop/UI/ViewModels/Pages/About/AboutPageViewModel.cs @@ -1,114 +1,39 @@ -using ReactiveUI; -using System.Application.Services; -using System.Application.UI.Resx; -using System.Properties; -using System.Reactive; -using System.Runtime.InteropServices; -using System.Windows; -using static System.Application.Services.CloudService.Constants; - namespace System.Application.UI.ViewModels { - public class AboutPageViewModel : TabItemViewModel + partial class AboutPageViewModel : TabItemViewModel { public static AboutPageViewModel Instance { get; } = new(); public override string Name { - get => AppResources.About; - protected set { throw new NotImplementedException(); } - } - - public AboutPageViewModel() - { - IconKey = nameof(AboutPageViewModel).Replace("ViewModel", "Svg"); - - OpenBrowserCommand = ReactiveCommand.Create(BrowserOpen); - - CopyLinkCommand = ReactiveCommand.Create(IDesktopAppService.Instance.SetClipboardText); - - CheckUpdateCommand = ReactiveCommand.CreateFromTask(async () => - { - await IAppUpdateService.Instance.CheckUpdateAsync(showIsExistUpdateFalse: true); - }); - - DelAccountCommand = ReactiveCommand.CreateFromTask(async () => - { - if (!UserService.Current.IsAuthenticated) return; - var r = await MessageBoxCompat.ShowAsync(AppResources.DelAccountTips, ThisAssembly.AssemblyTrademark, MessageBoxButtonCompat.OKCancel); - if (r == MessageBoxResultCompat.OK) - { - UserService.Current.DelAccount(); - } - }); - } - - public ReactiveCommand CheckUpdateCommand { get; } - - public ReactiveCommand OpenBrowserCommand { get; } - - public ReactiveCommand CopyLinkCommand { get; } - - public ReactiveCommand DelAccountCommand { get; } - - static string GetOS() => DI.Platform switch - { - Platform.Windows => "Windows", - Platform.Linux => "Linux", - Platform.Android => "Android", - Platform.Apple => DI.DeviceIdiom switch - { - DeviceIdiom.Phone => "iOS", - DeviceIdiom.Tablet => "iPadOS", - DeviceIdiom.Desktop => "macOS", - DeviceIdiom.TV => "tvOS", - DeviceIdiom.Watch => "watchOS", - _ => string.Empty, - }, - Platform.UWP => "UWP", - _ => string.Empty, - }; - - public string VersionDisplay => $"{ThisAssembly.VersionDisplay} for {GetOS()} ({RuntimeInformation.ProcessArchitecture})"; - - public string LabelVersionDisplay => ThisAssembly.IsBetaRelease ? "Beta Version:" : "Current Version:"; - - public string Copyright - { - get - { - // https://www.w3cschool.cn/html/html-copyright.html - int startYear = 2020, thisYear = 2021; - var nowYear = DateTime.Now.Year; - if (nowYear < thisYear) nowYear = thisYear; - return $"© {startYear}{(nowYear == startYear ? startYear : "-" + nowYear)} {ThisAssembly.AssemblyCompany}. All Rights Reserved."; - } + get => TitleName; + protected set { throw new NotSupportedException(); } } public static string RmbadminSteamLink => SteamApiUrls.MY_PROFILE_URL; - public static string RmbadminLink => "https://github.com/rmbadmin"; + public static string RmbadminLink => UrlConstants.GitHub_User_Rmbadmin; - public static string AigioLLink => "https://github.com/AigioL"; + public static string AigioLLink => UrlConstants.GitHub_User_AigioL; - public static string MossimosLink => "https://github.com/Mossimos"; + public static string MossimosLink => UrlConstants.GitHub_User_Mossimos; - public static string CliencerLink => "https://space.bilibili.com/30031316"; + public static string CliencerLink => UrlConstants.BILI_User_Cliencer; - public static string PrivacyLink => "https://steampp.net/privacy"; + public static string PrivacyLink => UrlConstants.OfficialWebsite_Privacy; - public static string AgreementLink => "https://steampp.net/agreement"; + public static string AgreementLink => UrlConstants.OfficialWebsite_Agreement; - public static string OfficialLink => "https://steampp.net"; + public static string OfficialLink => UrlConstants.OfficialWebsite; - public static string SourceCodeLink => "https://github.com/rmbadmin/SteamTools"; + public static string SourceCodeLink => UrlConstants.GitHub_Repository; - public static string UserSupportLink => "https://steampp.net/contact"; + public static string UserSupportLink => UrlConstants.OfficialWebsite_Contact; - public static string BugReportLink => "https://github.com/rmbadmin/SteamTools/issues"; + public static string BugReportLink => UrlConstants.GitHub_Issues; - public static string FAQLink => "https://steampp.net/faq"; + public static string FAQLink => UrlConstants.OfficialWebsite_Faq; - public static string LicenseLink => "https://github.com/rmbadmin/SteamTools/blob/develop/LICENSE"; + public static string LicenseLink => UrlConstants.License_GPLv3; } } \ No newline at end of file diff --git a/src/ST.Client.Desktop/UI/ViewModels/Pages/About/AboutPageViewModel.shared.cs b/src/ST.Client.Desktop/UI/ViewModels/Pages/About/AboutPageViewModel.shared.cs new file mode 100644 index 000000000..63081a4e7 --- /dev/null +++ b/src/ST.Client.Desktop/UI/ViewModels/Pages/About/AboutPageViewModel.shared.cs @@ -0,0 +1,132 @@ +using ReactiveUI; +using System.Application.Services; +using System.Application.UI.Resx; +using System.Linq; +using System.Properties; +using System.Reactive; +using System.Runtime.InteropServices; +using System.Windows; +//#if __MOBILE__ +//using Xamarin.Essentials; +//#endif +using static System.Application.Services.CloudService.Constants; + +namespace System.Application.UI.ViewModels +{ + public partial class AboutPageViewModel + { + static string TitleName => AppResources.About; + + public AboutPageViewModel() + { +#if !__MOBILE__ + IconKey = nameof(AboutPageViewModel).Replace("ViewModel", "Svg"); +#endif + + OpenBrowserCommand = ReactiveCommand.Create(BrowserOpen); + + //#if !__MOBILE__ + // CopyLinkCommand = + //#if __MOBILE__ + // ReactiveCommand.CreateFromTask(Clipboard.SetTextAsync); + //#else + // ReactiveCommand.Create(IDesktopAppService.Instance.SetClipboardText); + //#endif + //#endif + + CheckUpdateCommand = ReactiveCommand.CreateFromTask(async () => + { + await IAppUpdateService.Instance.CheckUpdateAsync(showIsExistUpdateFalse: true); + }); + + DelAccountCommand = ReactiveCommand.CreateFromTask(async () => + { + if (!UserService.Current.IsAuthenticated) return; +#if !__MOBILE__ + var r = await MessageBoxCompat.ShowAsync(AppResources.DelAccountTips, ThisAssembly.AssemblyTrademark, MessageBoxButtonCompat.OKCancel); + if (r == MessageBoxResultCompat.OK) + { + await UserService.Current.DelAccountAsync(); + } +#else + throw new NotImplementedException(); +#endif + }); + +#if __MOBILE__ + preferenceButtons = new() + { + PreferenceButtonViewModel.Create(PreferenceButton.检查更新, this), + PreferenceButtonViewModel.Create(PreferenceButton.更新日志, this), + PreferenceButtonViewModel.Create(PreferenceButton.常见问题疑难解答, this), + PreferenceButtonViewModel.Create(PreferenceButton.开放源代码许可, this), + PreferenceButtonViewModel.Create(PreferenceButton.源码仓库, this), + PreferenceButtonViewModel.Create(PreferenceButton.产品官网, this), + PreferenceButtonViewModel.Create(PreferenceButton.联系我们, this), + PreferenceButtonViewModel.Create(PreferenceButton.Bug反馈, 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); +#endif + } + + public ReactiveCommand CheckUpdateCommand { get; } + + public ReactiveCommand OpenBrowserCommand { get; } + + //#if !__MOBILE__ + // public ReactiveCommand CopyLinkCommand { get; } + //#endif + + public ReactiveCommand DelAccountCommand { get; } + + static string GetOS() => DI.Platform switch + { + Platform.Windows => "Windows", + Platform.Linux => "Linux", + Platform.Android => "Android", + Platform.Apple => DI.DeviceIdiom switch + { + DeviceIdiom.Phone => "iOS", + DeviceIdiom.Tablet => "iPadOS", + DeviceIdiom.Desktop => "macOS", + DeviceIdiom.TV => "tvOS", + DeviceIdiom.Watch => "watchOS", + _ => string.Empty, + }, + Platform.UWP => "UWP", + _ => string.Empty, + }; + + public string VersionDisplay => $"{ThisAssembly.VersionDisplay} for {GetOS()} ({RuntimeInformation.ProcessArchitecture})"; + + public string LabelVersionDisplay => ThisAssembly.IsBetaRelease ? "Beta Version:" : "Current Version:"; + + public string Copyright + { + get + { + // https://www.w3cschool.cn/html/html-copyright.html + int startYear = 2020, thisYear = 2021; + var nowYear = DateTime.Now.Year; + if (nowYear < thisYear) nowYear = thisYear; + return $"© {startYear}{(nowYear == startYear ? startYear : "-" + nowYear)} {ThisAssembly.AssemblyCompany}. All Rights Reserved."; + } + } + } +} \ No newline at end of file diff --git a/src/ST.Client.Desktop/UI/ViewModels/Windows/LoginOrRegisterWindowViewModel.shared.cs b/src/ST.Client.Desktop/UI/ViewModels/Windows/LoginOrRegisterWindowViewModel.shared.cs index 55452712f..0a6829928 100644 --- a/src/ST.Client.Desktop/UI/ViewModels/Windows/LoginOrRegisterWindowViewModel.shared.cs +++ b/src/ST.Client.Desktop/UI/ViewModels/Windows/LoginOrRegisterWindowViewModel.shared.cs @@ -223,10 +223,10 @@ namespace System.Application.UI.ViewModels switch (parameter) { case Agreement: - parameter = "https://steampp.net/AgreementBox"; + parameter = UrlConstants.OfficialWebsite_Box_Agreement; break; case Privacy: - parameter = "https://steampp.net/PrivacyBox"; + parameter = UrlConstants.OfficialWebsite_Box_Privacy; break; } #if __MOBILE__ diff --git a/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/activity_about.xml b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/activity_about.xml index f1defb6ad..68f7ec469 100644 --- a/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/activity_about.xml +++ b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/activity_about.xml @@ -9,11 +9,10 @@ - + android:background="@color/grey_window_background"> @@ -65,6 +65,7 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/preference_buttons_space_min" android:textColor="@color/text_primary" + android:textColorLink="@color/text_link" android:textSize="14sp" tools:text="Business Cooperation Contact: @Cliencer克总" xamarin:managedType="AndroidX.AppCompat.Widget.AppCompatTextView" /> @@ -75,6 +76,7 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/preference_buttons_space_min" android:textColor="@color/text_primary" + android:textColorLink="@color/text_link" android:textSize="14sp" tools:text="This open source software is licensed with GPLv3 License" xamarin:managedType="AndroidX.AppCompat.Widget.AppCompatTextView" /> @@ -87,52 +89,29 @@ android:paddingTop="@dimen/preference_buttons_space_min" android:paddingBottom="@dimen/preference_buttons_space_min" tools:itemCount="9" - tools:listitem="@layout/layout_preference_large_button" + tools:listitem="@layout/layout_preference_small_button" xamarin:managedType="AndroidX.RecyclerView.Widget.RecyclerView" /> - - - - - - - - - + - - - - - - - - - - + - + diff --git a/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/layout_preference_large_button.xml b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/layout_preference_large_button.xml index 95e13ff88..9fcd49825 100644 --- a/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/layout_preference_large_button.xml +++ b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/layout_preference_large_button.xml @@ -20,8 +20,8 @@ android:id="@+id/ivIcon" android:layout_width="24dp" android:layout_height="24dp" - android:layout_marginStart="16dp" - android:layout_marginEnd="16dp" + android:layout_marginStart="@dimen/activity_horizontal_margin" + android:layout_marginEnd="@dimen/activity_horizontal_margin" app:tint="@color/icon_background" tools:src="@drawable/baseline_account_box_black_24" xamarin:managedType="AndroidX.AppCompat.Widget.AppCompatImageView" /> diff --git a/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/layout_preference_small_button.xml b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/layout_preference_small_button.xml new file mode 100644 index 000000000..85d6bcff8 --- /dev/null +++ b/src/ST.Client.Mobile.Droid.Design/ui/src/main/res/layout/layout_preference_small_button.xml @@ -0,0 +1,25 @@ + + + + + + diff --git a/src/ST.Client.Mobile.Droid/ST.Client.Mobile.Droid.csproj b/src/ST.Client.Mobile.Droid/ST.Client.Mobile.Droid.csproj index d421018bb..d65514d9f 100644 --- a/src/ST.Client.Mobile.Droid/ST.Client.Mobile.Droid.csproj +++ b/src/ST.Client.Mobile.Droid/ST.Client.Mobile.Droid.csproj @@ -82,6 +82,7 @@ + @@ -337,6 +338,9 @@ Resources\drawable-xxxhdpi\baseline_verified_user_black_24.png + + Resources\layout\activity_about.xml + Resources\layout\activity_login_or_register.xml @@ -373,6 +377,9 @@ Resources\layout\layout_preference_large_button.xml + + Resources\layout\layout_preference_small_button.xml + Resources\layout\shared_loading.xml @@ -474,3 +481,4 @@ + diff --git a/src/ST.Client.Mobile.Droid/UI/Activities/AboutActivity.cs b/src/ST.Client.Mobile.Droid/UI/Activities/AboutActivity.cs index 78a61d770..57134495b 100644 --- a/src/ST.Client.Mobile.Droid/UI/Activities/AboutActivity.cs +++ b/src/ST.Client.Mobile.Droid/UI/Activities/AboutActivity.cs @@ -1,15 +1,207 @@ using Android.App; +using Android.Content; using Android.Content.PM; +using Android.Graphics; +using Android.OS; using Android.Runtime; +using Android.Text; +using Android.Text.Style; +using AndroidX.RecyclerView.Widget; +using Binding; +using Google.Android.Material.Dialog; +using ReactiveUI; +using System.Application.UI.Adapters; +using System.Application.UI.Resx; +using System.Application.UI.ViewModels; +using System.Text; +using Xamarin.Essentials; +using static System.Application.Services.CloudService.Constants; +using static System.Application.UI.ViewModels.AboutPageViewModel; namespace System.Application.UI.Activities { [Register(JavaPackageConstants.Activities + nameof(AboutActivity))] - [Activity(Theme = ManifestConstants.MainTheme, + [Activity(Theme = ManifestConstants.MainTheme_NoActionBar, LaunchMode = LaunchMode.SingleTask, ConfigurationChanges = ManifestConstants.ConfigurationChanges)] - internal sealed class AboutActivity : BaseActivity + internal sealed class AboutActivity : BaseActivity { - protected override int? LayoutResource => throw new NotImplementedException(); + protected override int? LayoutResource => Resource.Layout.activity_about; + + protected override void OnCreate(Bundle? savedInstanceState) + { + base.OnCreate(savedInstanceState); + + this.SetSupportActionBarWithNavigationClick(binding!.toolbar); + SupportActionBar.SetDisplayHomeAsUpEnabled(true); + + ViewModel = new(); + ViewModel.AddTo(this); + + binding!.tvDevelopers.SetLinkMovementMethod(); + binding!.tvBusinessCooperationContact.SetLinkMovementMethod(); + binding!.tvOpenSourceLicensed.SetLinkMovementMethod(); + binding!.tvAgreementAndPrivacy.SetLinkMovementMethod(); + + binding!.tvTitle.TextFormatted = CreateTitle(); + binding!.tvVersion.Text = $"{ViewModel.LabelVersionDisplay} {ViewModel.VersionDisplay}"; + binding!.tvDevelopers.TextFormatted = CreateDevelopers(); + binding!.tvBusinessCooperationContact.TextFormatted = CreateBusinessCooperationContact(); + binding!.tvOpenSourceLicensed.TextFormatted = CreateOpenSourceLicensed(); + binding!.tvCopyright.Text = ViewModel.Copyright; + + R.Current.WhenAnyValue(x => x.Res).Subscribe(_ => + { + Title = ViewModel.Title; + binding!.tvAgreementAndPrivacy.TextFormatted = CreateAgreementAndPrivacy(); + }).AddTo(this); + + static void BrowserOpenByDialogClick(DialogClickEventArgs e, Func func) + { + if (e.Which >= 0 && e.Which < SourceRepositories.Length) + { + var value = func(SourceRepositories[e.Which]); + if (value != default) BrowserOpen(value); + } + } + + var adapter = new SmallPreferenceButtonAdapter(ViewModel!.PreferenceButtons); + adapter.ItemClick += (_, e) => + { + switch (e.Current.Id) + { + case PreferenceButton.检查更新: + ViewModel!.CheckUpdateCommand.Invoke(); + break; + case PreferenceButton.更新日志: + BrowserOpen(string.Format( + UrlConstants.OfficialWebsite_Box_Changelog_, + MainApplication.GetTheme(), + R.Language)); + break; + case PreferenceButton.常见问题疑难解答: + BrowserOpen(string.Format( + UrlConstants.OfficialWebsite_Box_Faq_, + MainApplication.GetTheme(), + R.Language)); + break; + case PreferenceButton.开放源代码许可: + throw new NotImplementedException(); + break; + case PreferenceButton.源码仓库: + new MaterialAlertDialogBuilder(this).SetItems(SourceRepositories, (_, e) => BrowserOpenByDialogClick(e, value => value switch + { + GitHub => UrlConstants.GitHub_Repository, + Gitee => UrlConstants.Gitee_Repository, + _ => default, + })).Show(); + break; + case PreferenceButton.产品官网: + BrowserOpen(UrlConstants.OfficialWebsite); + break; + case PreferenceButton.联系我们: + BrowserOpen(UrlConstants.OfficialWebsite_Contact); + break; + case PreferenceButton.Bug反馈: + new MaterialAlertDialogBuilder(this).SetItems(SourceRepositories, (_, e) => BrowserOpenByDialogClick(e, value => value switch + { + GitHub => UrlConstants.GitHub_Issues, + Gitee => UrlConstants.Gitee_Issues, + _ => default, + })).Show(); + break; + case PreferenceButton.账号注销: + ViewModel!.DelAccountCommand.Invoke(); + break; + } + }; + var layout = new LinearLayoutManager(this, LinearLayoutManager.Vertical, false); + binding.rvPreferenceButtons.SetLayoutManager(layout); + binding.rvPreferenceButtons.SetAdapter(adapter); + } + + static SpannableString CreateTitle() + { + SpannableString spannable = new(Title_0 + Title_1); + ForegroundColorSpan fcs = new(ThemeAccentBrushKey.ToPlatformColor()); + int start = Title_0.Length, end = Title_0.Length + Title_1.Length; + spannable.SetSpan(fcs, start, end, SpanTypes.ExclusiveExclusive); + StyleSpan ss = new(TypefaceStyle.Bold); + spannable.SetSpan(ss, start, end, SpanTypes.ExclusiveExclusive); + return spannable; + } + + static SpannableString CreateDevelopers() => MainApplication.CreateSpannableString(list => + { + int length; + StringBuilder str = new(Developers_0); + length = str.Length; + str.Append(At_Rmbadmin); + list.Add((new HyperlinkClickableSpan(_ => + { + BrowserOpen(UrlConstants.GitHub_User_Rmbadmin); + }), length, str.Length, SpanTypes.ExclusiveExclusive)); + str.Append(Separator); + length = str.Length; + str.Append(At_AigioL); + list.Add((new HyperlinkClickableSpan(_ => + { + BrowserOpen(UrlConstants.GitHub_User_AigioL); + }), length, str.Length, SpanTypes.ExclusiveExclusive)); + str.Append(Separator); + length = str.Length; + str.Append(At_Mossimos); + list.Add((new HyperlinkClickableSpan(_ => + { + BrowserOpen(UrlConstants.GitHub_User_Mossimos); + }), length, str.Length, SpanTypes.ExclusiveExclusive)); + return str; + }); + + static SpannableString CreateBusinessCooperationContact() => MainApplication.CreateSpannableString(list => + { + int length; + StringBuilder str = new(BusinessCooperationContact_0); + length = str.Length; + str.Append(At_Cliencer); + list.Add((new HyperlinkClickableSpan(_ => + { + BrowserOpen(UrlConstants.BILI_User_Cliencer); + }), length, str.Length, SpanTypes.ExclusiveExclusive)); + return str; + }); + + static SpannableString CreateAgreementAndPrivacy() => MainApplication.CreateSpannableString(list => + { + int length; + StringBuilder str = new(); + length = str.Length; + str.Append(AppResources.User_Agreement); + list.Add((new HyperlinkClickableSpan(_ => + { + BrowserOpen(UrlConstants.OfficialWebsite_Box_Agreement); + }), length, str.Length, SpanTypes.ExclusiveExclusive)); + str.Append(Separator2); + length = str.Length; + str.Append(AppResources.User_Privacy); + list.Add((new HyperlinkClickableSpan(_ => + { + BrowserOpen(UrlConstants.OfficialWebsite_Box_Privacy); + }), length, str.Length, SpanTypes.ExclusiveExclusive)); + return str; + }); + + static SpannableString CreateOpenSourceLicensed() => MainApplication.CreateSpannableString(list => + { + int length; + StringBuilder str = new(OpenSourceLicensed_0); + length = str.Length; + str.Append(OpenSourceLicensed_1); + list.Add((new HyperlinkClickableSpan(_ => + { + BrowserOpen(UrlConstants.License_GPLv3); + }), length, str.Length, SpanTypes.ExclusiveExclusive)); + return str; + }); } } \ No newline at end of file diff --git a/src/ST.Client.Mobile.Droid/UI/Activities/LoginOrRegisterActivity.cs b/src/ST.Client.Mobile.Droid/UI/Activities/LoginOrRegisterActivity.cs index 1491e057c..fed126425 100644 --- a/src/ST.Client.Mobile.Droid/UI/Activities/LoginOrRegisterActivity.cs +++ b/src/ST.Client.Mobile.Droid/UI/Activities/LoginOrRegisterActivity.cs @@ -10,13 +10,10 @@ using AndroidX.Navigation.UI; using Binding; using Microsoft.Identity.Client; using ReactiveUI; -using System.Application.Mvvm; using System.Application.UI.Resx; using System.Application.UI.ViewModels; using System.Collections.Generic; -using System.Reactive.Disposables; using System.Text; -using JObject = Java.Lang.Object; namespace System.Application.UI.Activities { @@ -24,11 +21,8 @@ namespace System.Application.UI.Activities [Activity(Theme = ManifestConstants.MainTheme_NoActionBar, LaunchMode = LaunchMode.SingleTask, ConfigurationChanges = ManifestConstants.ConfigurationChanges)] - internal sealed class LoginOrRegisterActivity : BaseActivity, IDisposableHolder + internal sealed class LoginOrRegisterActivity : BaseActivity { - readonly CompositeDisposable disposables = new(); - ICollection IDisposableHolder.CompositeDisposable => disposables; - protected override int? LayoutResource => Resource.Layout.activity_login_or_register; static readonly Dictionary> title_strings = new() @@ -73,15 +67,14 @@ namespace System.Application.UI.Activities AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(requestCode, resultCode, data); } - public static SpannableString CreateAgreementAndPrivacy(LoginOrRegisterPageViewModel viewModel) + public static SpannableString CreateAgreementAndPrivacy(LoginOrRegisterPageViewModel viewModel) => MainApplication.CreateSpannableString(list => { int length; - var linkTextIndexs = new List<(JObject what, int start, int end, SpanTypes flags)>(); StringBuilder str = new(AppResources.User_RegisterAgreed); str.Append(" "); length = str.Length; str.Append(AppResources.User_Agreement); - linkTextIndexs.Add((new HyperlinkClickableSpan(_ => + list.Add((new HyperlinkClickableSpan(_ => { viewModel.OpenHyperlink.Invoke(LoginOrRegisterPageViewModel.Agreement); }), length, str.Length, SpanTypes.ExclusiveExclusive)); @@ -90,17 +83,11 @@ namespace System.Application.UI.Activities str.Append(" "); length = str.Length; str.Append(AppResources.User_Privacy); - linkTextIndexs.Add((new HyperlinkClickableSpan(_ => + list.Add((new HyperlinkClickableSpan(_ => { viewModel.OpenHyperlink.Invoke(LoginOrRegisterPageViewModel.Privacy); }), length, str.Length, SpanTypes.ExclusiveExclusive)); - - SpannableString spannable = new(str.ToString()); - foreach (var (what, start, end, flags) in linkTextIndexs) - { - spannable.SetSpan(what, start, end, flags); - } - return spannable; - } + return str; + }); } } \ No newline at end of file diff --git a/src/ST.Client.Mobile.Droid/UI/Activities/MainActivity.cs b/src/ST.Client.Mobile.Droid/UI/Activities/MainActivity.cs index df843e1a0..ee999061f 100644 --- a/src/ST.Client.Mobile.Droid/UI/Activities/MainActivity.cs +++ b/src/ST.Client.Mobile.Droid/UI/Activities/MainActivity.cs @@ -7,10 +7,7 @@ using AndroidX.Navigation.UI; using Binding; using Java.Lang; using ReactiveUI; -using System.Application.Mvvm; using System.Application.UI.ViewModels; -using System.Collections.Generic; -using System.Reactive.Disposables; namespace System.Application.UI.Activities { @@ -18,11 +15,8 @@ namespace System.Application.UI.Activities [Activity(Theme = ManifestConstants.MainTheme_NoActionBar, LaunchMode = LaunchMode.SingleTask, ConfigurationChanges = ManifestConstants.ConfigurationChanges)] - internal sealed class MainActivity : BaseActivity, IDisposableHolder, IReadOnlyViewFor + internal sealed class MainActivity : BaseActivity, IReadOnlyViewFor { - readonly CompositeDisposable disposables = new(); - ICollection IDisposableHolder.CompositeDisposable => disposables; - protected override int? LayoutResource => Resource.Layout.activity_main; protected override bool BackToHome => true; @@ -75,7 +69,6 @@ namespace System.Application.UI.Activities { base.OnDestroy(); navController = null; - disposables.Dispose(); } } } \ No newline at end of file diff --git a/src/ST.Client.Mobile.Droid/UI/Adapters/MyPreferenceButtonAdapter.cs b/src/ST.Client.Mobile.Droid/UI/Adapters/LargePreferenceButtonAdapter.cs similarity index 52% rename from src/ST.Client.Mobile.Droid/UI/Adapters/MyPreferenceButtonAdapter.cs rename to src/ST.Client.Mobile.Droid/UI/Adapters/LargePreferenceButtonAdapter.cs index 34e379ad4..a6b680ad1 100644 --- a/src/ST.Client.Mobile.Droid/UI/Adapters/MyPreferenceButtonAdapter.cs +++ b/src/ST.Client.Mobile.Droid/UI/Adapters/LargePreferenceButtonAdapter.cs @@ -2,14 +2,18 @@ using Android.Views; using Binding; using ReactiveUI; using System.Application.UI.ViewModels; -using TViewHolder = System.Application.UI.Adapters.MyPreferenceButtonViewHolder; -using TViewModel = System.Application.UI.ViewModels.MyPageViewModel.PreferenceButtonViewModel; +using System.Collections.ObjectModel; namespace System.Application.UI.Adapters { - internal sealed class MyPreferenceButtonAdapter : BaseReactiveRecycleViewAdapter + internal sealed class LargePreferenceButtonAdapter : BaseReactiveRecycleViewAdapter, TViewModel> + where TViewModel : RIdTitleIconViewModel { - public MyPreferenceButtonAdapter(MyPageViewModel viewModel) : base(viewModel.PreferenceButtons) + public LargePreferenceButtonAdapter(ObservableCollection collection) : base(collection) + { + } + + public LargePreferenceButtonAdapter(ReadOnlyObservableCollection collection) : base(collection) { } @@ -19,11 +23,12 @@ namespace System.Application.UI.Adapters } } - internal sealed class MyPreferenceButtonViewHolder : BaseReactiveViewHolder + internal sealed class LargePreferenceButtonViewHolder : BaseReactiveViewHolder + where TViewModel : RIdTitleIconViewModel { readonly layout_preference_large_button binding; - public MyPreferenceButtonViewHolder(View itemView) : base(itemView) + public LargePreferenceButtonViewHolder(View itemView) : base(itemView) { binding = new(itemView); } diff --git a/src/ST.Client.Mobile.Droid/UI/Adapters/SmallPreferenceButtonAdapter.cs b/src/ST.Client.Mobile.Droid/UI/Adapters/SmallPreferenceButtonAdapter.cs new file mode 100644 index 000000000..237004925 --- /dev/null +++ b/src/ST.Client.Mobile.Droid/UI/Adapters/SmallPreferenceButtonAdapter.cs @@ -0,0 +1,46 @@ +using Android.Views; +using Binding; +using ReactiveUI; +using System.Application.UI.ViewModels; +using System.Collections.ObjectModel; + +namespace System.Application.UI.Adapters +{ + internal sealed class SmallPreferenceButtonAdapter : BaseReactiveRecycleViewAdapter, TViewModel> + where TViewModel : RIdTitleViewModel + { + public SmallPreferenceButtonAdapter(ObservableCollection collection) : base(collection) + { + } + + public SmallPreferenceButtonAdapter(ReadOnlyObservableCollection collection) : base(collection) + { + } + + public override int? GetLayoutResource(int viewType) + { + return Resource.Layout.layout_preference_small_button; + } + } + + internal sealed class SmallPreferenceButtonViewHolder : BaseReactiveViewHolder + where TViewModel : RIdTitleViewModel + { + readonly layout_preference_small_button binding; + + public SmallPreferenceButtonViewHolder(View itemView) : base(itemView) + { + binding = new(itemView); + } + + public override void OnBind() + { + base.OnBind(); + + ViewModel!.WhenAnyValue(x => x.Title).Subscribe(value => + { + binding.tvTitle.Text = value; + }).AddTo(this); + } + } +} \ No newline at end of file diff --git a/src/ST.Client.Mobile.Droid/UI/Base/BaseActivity.cs b/src/ST.Client.Mobile.Droid/UI/Base/BaseActivity.cs index 8cca7dfc0..53cc367fe 100644 --- a/src/ST.Client.Mobile.Droid/UI/Base/BaseActivity.cs +++ b/src/ST.Client.Mobile.Droid/UI/Base/BaseActivity.cs @@ -5,7 +5,10 @@ using Android.Views; using AndroidX.AppCompat.App; using ReactiveUI; using ReactiveUI.AndroidX; +using System.Application.Mvvm; using System.Application.UI.ViewModels; +using System.Collections.Generic; +using System.Reactive.Disposables; using XEPlatform = Xamarin.Essentials.Platform; // ReSharper disable once CheckNamespace @@ -54,8 +57,12 @@ namespace System.Application.UI.Activities } /// - public abstract partial class BaseActivity : BaseActivity where TViewBinding : class + public abstract partial class BaseActivity : BaseActivity, IDisposableHolder + where TViewBinding : class { + readonly CompositeDisposable disposables = new(); + ICollection IDisposableHolder.CompositeDisposable => disposables; + protected override void OnCreate(Bundle? savedInstanceState) { base.OnCreate(savedInstanceState); @@ -65,6 +72,7 @@ namespace System.Application.UI.Activities protected override void OnDestroy() { base.OnDestroy(); + disposables.Dispose(); binding = null; } } @@ -81,9 +89,13 @@ namespace System.Application.UI.Activities /// /// /// - public abstract partial class BaseActivity : ReactiveAppCompatActivity, IReadOnlyViewFor where TViewBinding : class + public abstract partial class BaseActivity : ReactiveAppCompatActivity, IReadOnlyViewFor, IDisposableHolder + where TViewBinding : class where TViewModel : ViewModelBase { + readonly CompositeDisposable disposables = new(); + ICollection IDisposableHolder.CompositeDisposable => disposables; + protected override void OnCreate(Bundle? savedInstanceState) { base.OnCreate(savedInstanceState); @@ -113,6 +125,7 @@ namespace System.Application.UI.Activities ClearOnClickListener(); if (navController != null) navController = null; base.OnDestroy(); + disposables.Dispose(); binding = null; } } diff --git a/src/ST.Client.Mobile.Droid/UI/Base/BaseFragment.cs b/src/ST.Client.Mobile.Droid/UI/Base/BaseFragment.cs index f61167f47..561341b59 100644 --- a/src/ST.Client.Mobile.Droid/UI/Base/BaseFragment.cs +++ b/src/ST.Client.Mobile.Droid/UI/Base/BaseFragment.cs @@ -1,9 +1,10 @@ using Android.OS; using Android.Views; using ReactiveUI.AndroidX; -using System.Application; using System.Application.Mvvm; using System.Application.UI.ViewModels; +using System.Collections.Generic; +using System.Reactive.Disposables; using Fragment = AndroidX.Fragment.App.Fragment; // ReSharper disable once CheckNamespace @@ -37,7 +38,7 @@ namespace System.Application.UI.Fragments public override void OnDestroyView() { ClearOnClickListener(); - base.OnDestroy(); + base.OnDestroyView(); } } @@ -64,10 +65,13 @@ namespace System.Application.UI.Fragments /// /// /// - public abstract partial class BaseFragment : ReactiveFragment, ReactiveUI.IReadOnlyViewFor + public abstract partial class BaseFragment : ReactiveFragment, ReactiveUI.IReadOnlyViewFor, IDisposableHolder where TViewBinding : class where TViewModel : ViewModelBase { + readonly CompositeDisposable disposables = new(); + ICollection IDisposableHolder.CompositeDisposable => disposables; + public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { var view = BaseFragment.CreateView(LayoutResource, inflater, container); @@ -81,7 +85,8 @@ namespace System.Application.UI.Fragments public override void OnDestroyView() { ClearOnClickListener(); - base.OnDestroy(); + base.OnDestroyView(); + disposables.Dispose(); binding = null; } } diff --git a/src/ST.Client.Mobile.Droid/UI/Fragments/FastLoginOrRegisterFragment.cs b/src/ST.Client.Mobile.Droid/UI/Fragments/FastLoginOrRegisterFragment.cs index df13480be..c8146a914 100644 --- a/src/ST.Client.Mobile.Droid/UI/Fragments/FastLoginOrRegisterFragment.cs +++ b/src/ST.Client.Mobile.Droid/UI/Fragments/FastLoginOrRegisterFragment.cs @@ -1,26 +1,19 @@ using Android.Runtime; -using Android.Text.Method; using Android.Views; using AndroidX.RecyclerView.Widget; using Binding; using ReactiveUI; -using System.Application.Mvvm; using System.Application.UI.Activities; using System.Application.UI.Adapters; using System.Application.UI.Resx; using System.Application.UI.ViewModels; -using System.Collections.Generic; -using System.Reactive.Disposables; using static System.Application.UI.ViewModels.LoginOrRegisterPageViewModel; namespace System.Application.UI.Fragments { [Register(JavaPackageConstants.Fragments + nameof(FastLoginOrRegisterFragment))] - internal sealed class FastLoginOrRegisterFragment : BaseFragment, IDisposableHolder + internal sealed class FastLoginOrRegisterFragment : BaseFragment { - readonly CompositeDisposable disposables = new(); - ICollection IDisposableHolder.CompositeDisposable => disposables; - protected override int? LayoutResource => Resource.Layout.fragment_login_and_register_by_fast; public override void OnCreateView(View view) diff --git a/src/ST.Client.Mobile.Droid/UI/Fragments/MyFragment.cs b/src/ST.Client.Mobile.Droid/UI/Fragments/MyFragment.cs index 807a343bd..cdb524423 100644 --- a/src/ST.Client.Mobile.Droid/UI/Fragments/MyFragment.cs +++ b/src/ST.Client.Mobile.Droid/UI/Fragments/MyFragment.cs @@ -3,23 +3,17 @@ using Android.Views; using AndroidX.RecyclerView.Widget; using Binding; using ReactiveUI; -using System.Application.Mvvm; using System.Application.Services; using System.Application.UI.Activities; using System.Application.UI.Adapters; using System.Application.UI.ViewModels; -using System.Collections.Generic; -using System.Reactive.Disposables; using static System.Application.UI.ViewModels.MyPageViewModel; namespace System.Application.UI.Fragments { [Register(JavaPackageConstants.Fragments + nameof(MyFragment))] - internal sealed class MyFragment : BaseFragment, IDisposableHolder + internal sealed class MyFragment : BaseFragment { - readonly CompositeDisposable disposables = new(); - ICollection IDisposableHolder.CompositeDisposable => disposables; - protected override int? LayoutResource => Resource.Layout.fragment_my; public override void OnCreateView(View view) @@ -34,7 +28,7 @@ namespace System.Application.UI.Fragments SetOnClickListener(binding!.layoutUser); - var adapter = new MyPreferenceButtonAdapter(ViewModel!); + var adapter = new LargePreferenceButtonAdapter(ViewModel!.PreferenceButtons); adapter.ItemClick += (_, e) => { if (e.Current.Authentication && !UserService.Current.IsAuthenticated) @@ -60,12 +54,6 @@ namespace System.Application.UI.Fragments binding.rvPreferenceButtons.SetAdapter(adapter); } - public override void OnDestroyView() - { - base.OnDestroyView(); - disposables.Dispose(); - } - protected override bool OnClick(View view) { if (view.Id == Resource.Id.layoutUser) diff --git a/src/ST.Client.Mobile.Droid/UI/Fragments/PhoneNumberLoginOrRegisterFragment.cs b/src/ST.Client.Mobile.Droid/UI/Fragments/PhoneNumberLoginOrRegisterFragment.cs index 87d022b2f..6b236c15b 100644 --- a/src/ST.Client.Mobile.Droid/UI/Fragments/PhoneNumberLoginOrRegisterFragment.cs +++ b/src/ST.Client.Mobile.Droid/UI/Fragments/PhoneNumberLoginOrRegisterFragment.cs @@ -5,22 +5,16 @@ using Android.Views.InputMethods; using Android.Widget; using Binding; using ReactiveUI; -using System.Application.Mvvm; using System.Application.Services; using System.Application.UI.Activities; using System.Application.UI.Resx; using System.Application.UI.ViewModels; -using System.Collections.Generic; -using System.Reactive.Disposables; namespace System.Application.UI.Fragments { [Register(JavaPackageConstants.Fragments + nameof(PhoneNumberLoginOrRegisterFragment))] - internal sealed class PhoneNumberLoginOrRegisterFragment : BaseFragment, IDisposableHolder, TextView.IOnEditorActionListener + internal sealed class PhoneNumberLoginOrRegisterFragment : BaseFragment, TextView.IOnEditorActionListener { - readonly CompositeDisposable disposables = new(); - ICollection IDisposableHolder.CompositeDisposable => disposables; - protected override int? LayoutResource => Resource.Layout.fragment_login_and_register_by_phone_number; public override void OnCreateView(View view) diff --git a/src/ST.Client.Mobile.Droid/UI/MainApplication.cs b/src/ST.Client.Mobile.Droid/UI/MainApplication.cs index adf66cee6..97931d904 100644 --- a/src/ST.Client.Mobile.Droid/UI/MainApplication.cs +++ b/src/ST.Client.Mobile.Droid/UI/MainApplication.cs @@ -1,10 +1,15 @@ using Android.App; using Android.Runtime; +using Android.Text; using System.Application.Security; using System.Application.UI.ViewModels; +using System.Collections.Generic; +using System.Text; using Xamarin.Essentials; using _ThisAssembly = System.Properties.ThisAssembly; using AndroidApplication = Android.App.Application; +using AppTheme = System.Application.Models.AppTheme; +using JObject = Java.Lang.Object; using XEFileProvider = Xamarin.Essentials.FileProvider; using XEPlatform = Xamarin.Essentials.Platform; using XEVersionTracking = Xamarin.Essentials.VersionTracking; @@ -71,5 +76,27 @@ namespace System.Application.UI } return true; } + + public static string GetTheme() + { + if (DarkModeUtil.IsDarkMode(Context)) + { + return AppTheme.Dark.ToString2(); + } + return AppTheme.Light.ToString2(); + } + + public static SpannableString CreateSpannableString(Func, StringBuilder> func) + { + var linkTextIndexs = new List<(JObject what, int start, int end, SpanTypes flags)>(); + var sb = func(linkTextIndexs); + var str = sb.ToString(); + SpannableString spannable = new(str); + foreach (var (what, start, end, flags) in linkTextIndexs) + { + spannable.SetSpan(what, start, end, flags); + } + return spannable; + } } } \ No newline at end of file diff --git a/src/ST.Client.Mobile/ST.Client.Mobile.csproj b/src/ST.Client.Mobile/ST.Client.Mobile.csproj index 9f52d4044..74e511d9e 100644 --- a/src/ST.Client.Mobile/ST.Client.Mobile.csproj +++ b/src/ST.Client.Mobile/ST.Client.Mobile.csproj @@ -64,6 +64,12 @@ Services\Implementation + + UI\ViewModels\AboutPageViewModel.shared.cs + + + Models\Enum + diff --git a/src/ST.Client.Mobile/UI/ViewModels/AboutPageViewModel.cs b/src/ST.Client.Mobile/UI/ViewModels/AboutPageViewModel.cs new file mode 100644 index 000000000..a5d461363 --- /dev/null +++ b/src/ST.Client.Mobile/UI/ViewModels/AboutPageViewModel.cs @@ -0,0 +1,102 @@ +using ReactiveUI; +using System.Application.Mvvm; +using System.Application.UI.Resx; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Drawing; +using System.Linq; +using Xamarin.Essentials; + +namespace System.Application.UI.ViewModels +{ + partial class AboutPageViewModel : ViewModelBase + { + public string Title => TitleName; + + ObservableCollection preferenceButtons; + public ObservableCollection PreferenceButtons + { + get => preferenceButtons; + set => this.RaiseAndSetIfChanged(ref preferenceButtons, value); + } + + public enum PreferenceButton + { + 检查更新 = 1, + 更新日志, + 常见问题疑难解答, + 开放源代码许可, + 源码仓库, + 产品官网, + 联系我们, + Bug反馈, + 账号注销, + } + + 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, + _ => string.Empty, + }; + 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 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); + } +} \ No newline at end of file diff --git a/src/ST.Client.Mobile/UI/ViewModels/Base/RIdTitleIconViewModel.cs b/src/ST.Client.Mobile/UI/ViewModels/Base/RIdTitleIconViewModel.cs index 593693041..e51c5c8ee 100644 --- a/src/ST.Client.Mobile/UI/ViewModels/Base/RIdTitleIconViewModel.cs +++ b/src/ST.Client.Mobile/UI/ViewModels/Base/RIdTitleIconViewModel.cs @@ -61,4 +61,11 @@ namespace System.Application.UI.ViewModels GC.SuppressFinalize(this); } } + + public abstract class RIdTitleViewModel : RIdTitleIconViewModel + { + protected sealed override bool IgnoreIcon => true; + + protected sealed override byte GetIconById(TId? id) => default; + } } \ No newline at end of file diff --git a/src/ST.Client/Services/Implementation/AppUpdateServiceImpl.cs b/src/ST.Client/Services/Implementation/AppUpdateServiceImpl.cs index f6f0cb1a3..7b61eefed 100644 --- a/src/ST.Client/Services/Implementation/AppUpdateServiceImpl.cs +++ b/src/ST.Client/Services/Implementation/AppUpdateServiceImpl.cs @@ -600,7 +600,7 @@ namespace System.Application.Services.Implementation /// /// 在应用商店中打开 /// - protected virtual void OpenInAppStore() => BrowserOpen("https://steampp.net/"); + protected virtual void OpenInAppStore() => BrowserOpen(UrlConstants.OfficialWebsite); void StartUpdate() { diff --git a/src/ST.Client/UrlConstants.cs b/src/ST.Client/UrlConstants.cs new file mode 100644 index 000000000..8271987f9 --- /dev/null +++ b/src/ST.Client/UrlConstants.cs @@ -0,0 +1,29 @@ +namespace System.Application +{ + public static class UrlConstants + { + public const string GitHub_User_Rmbadmin = "https://github.com/rmbadmin"; + public const string GitHub_User_AigioL = "https://github.com/AigioL"; + public const string GitHub_User_Mossimos = "https://github.com/Mossimos"; + + public const string BILI_User_Cliencer = "https://space.bilibili.com/30031316"; + + public const string OfficialWebsite = "https://steampp.net"; + public const string OfficialWebsite_Privacy = "https://steampp.net/privacy"; + public const string OfficialWebsite_Agreement = "https://steampp.net/agreement"; + public const string OfficialWebsite_Contact = "https://steampp.net/contact"; + public const string OfficialWebsite_Faq = "https://steampp.net/faq"; + public const string OfficialWebsite_Box_Changelog_ = "https://steampp.net/changelogbox?theme={0}&language={1}"; + public const string OfficialWebsite_Box_Faq_ = "https://steampp.net/faqbox?theme={0}&language={1}"; + public const string OfficialWebsite_Box_Privacy = "https://steampp.net/PrivacyBox"; + public const string OfficialWebsite_Box_Agreement = "https://steampp.net/AgreementBox"; + + public const string GitHub_Repository = "https://github.com/SteamTools-Team/SteamTools"; + public const string Gitee_Repository = "https://gitee.com/rmbgame/SteamTools"; + + public const string GitHub_Issues = "https://github.com/SteamTools-Team/SteamTools"; + public const string Gitee_Issues = "https://gitee.com/rmbgame/SteamTools"; + + public const string License_GPLv3 = "https://www.gnu.org/licenses/gpl-3.0.html"; + } +} \ No newline at end of file