utilities feature
This commit is contained in:
		
							
								
								
									
										85
									
								
								Gallery.Share/App.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								Gallery.Share/App.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| using Xamarin.Forms; | ||||
| using Gallery.Services; | ||||
| using Xamarin.Essentials; | ||||
| using Gallery.Resources; | ||||
| using Gallery.Util; | ||||
| using Gallery.Resources.Theme; | ||||
|  | ||||
| namespace Gallery | ||||
| { | ||||
|     public partial class App : Application | ||||
|     { | ||||
|         public static AppTheme CurrentTheme { get; private set; } | ||||
|         public static PlatformCulture CurrentCulture { get; private set; } | ||||
|  | ||||
|         public App() | ||||
|         { | ||||
|             DependencyService.Register<MockDataStore>(); | ||||
|         } | ||||
|  | ||||
|         private void InitResource() | ||||
|         { | ||||
|             var theme = AppInfo.RequestedTheme; | ||||
|             SetTheme(theme, true); | ||||
|         } | ||||
|  | ||||
|         private void InitPreference() | ||||
|         { | ||||
|  | ||||
|         } | ||||
|  | ||||
|         private void InitLanguage() | ||||
|         { | ||||
|             var ci = Environment.GetCurrentCultureInfo(); | ||||
|             Environment.SetCultureInfo(ci); | ||||
|             CurrentCulture = new PlatformCulture(ci.Name.ToLower()); | ||||
|         } | ||||
|  | ||||
|         private void SetTheme(AppTheme theme, bool force = false) | ||||
|         { | ||||
|             if (force || theme != CurrentTheme) | ||||
|             { | ||||
|                 CurrentTheme = theme; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
| #if DEBUG | ||||
|             Log.Print($"application theme: {theme}"); | ||||
| #endif | ||||
|             Theme themeInstance = theme switch | ||||
|             { | ||||
|                 AppTheme.Dark => DarkTheme.Instance, | ||||
|                 _ => LightTheme.Instance | ||||
|             }; | ||||
| #if __IOS__ | ||||
|             var style = (StatusBarStyles)themeInstance[Theme.StatusBarStyle]; | ||||
|             Environment.SetStatusBarStyle(style); | ||||
| #elif __ANDROID__ | ||||
|             var color = (Color)themeInstance[Theme.NavigationColor]; | ||||
|             Environment.SetStatusBarColor(color); | ||||
| #endif | ||||
|             Resources = themeInstance; | ||||
|         } | ||||
|  | ||||
|         protected override void OnStart() | ||||
|         { | ||||
|             InitLanguage(); | ||||
|             MainPage = new AppShell(); | ||||
|  | ||||
|             InitResource(); | ||||
|             InitPreference(); | ||||
|         } | ||||
|  | ||||
|         protected override void OnSleep() | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         protected override void OnResume() | ||||
|         { | ||||
|             var theme = AppInfo.RequestedTheme; | ||||
|             SetTheme(theme); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,32 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8" ?> | ||||
| <Application xmlns="http://xamarin.com/schemas/2014/forms" | ||||
|              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | ||||
|              x:Class="Gallery.App"> | ||||
|     <!-- | ||||
|         Define global resources and styles here, that apply to all pages in your app. | ||||
|     --> | ||||
|     <Application.Resources> | ||||
|         <ResourceDictionary> | ||||
|             <Color x:Key="Primary">#2196F3</Color> | ||||
|             <Style TargetType="Button"> | ||||
|                 <Setter Property="TextColor" Value="White"></Setter> | ||||
|                 <Setter Property="VisualStateManager.VisualStateGroups"> | ||||
|                     <VisualStateGroupList> | ||||
|                         <VisualStateGroup x:Name="CommonStates"> | ||||
|                             <VisualState x:Name="Normal"> | ||||
|                                 <VisualState.Setters> | ||||
|                                     <Setter Property="BackgroundColor" Value="{StaticResource Primary}" /> | ||||
|                                 </VisualState.Setters> | ||||
|                             </VisualState> | ||||
|                             <VisualState x:Name="Disabled"> | ||||
|                                 <VisualState.Setters> | ||||
|                                     <Setter Property="BackgroundColor" Value="#332196F3" /> | ||||
|                                 </VisualState.Setters> | ||||
|                             </VisualState> | ||||
|                         </VisualStateGroup> | ||||
|                     </VisualStateGroupList> | ||||
|                 </Setter> | ||||
|             </Style> | ||||
|         </ResourceDictionary>         | ||||
|     </Application.Resources> | ||||
| </Application> | ||||
| @@ -1,32 +0,0 @@ | ||||
| using System; | ||||
| using Xamarin.Forms; | ||||
| using Xamarin.Forms.Xaml; | ||||
| using Gallery.Services; | ||||
| using Gallery.Views; | ||||
|  | ||||
| namespace Gallery | ||||
| { | ||||
|     public partial class App : Application | ||||
|     { | ||||
|  | ||||
|         public App() | ||||
|         { | ||||
|             InitializeComponent(); | ||||
|  | ||||
|             DependencyService.Register<MockDataStore>(); | ||||
|             MainPage = new AppShell(); | ||||
|         } | ||||
|  | ||||
|         protected override void OnStart() | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         protected override void OnSleep() | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         protected override void OnResume() | ||||
|         { | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -14,12 +14,12 @@ | ||||
|     <Shell.Resources> | ||||
|         <ResourceDictionary> | ||||
|             <Style x:Key="BaseStyle" TargetType="Element"> | ||||
|                 <Setter Property="Shell.BackgroundColor" Value="{StaticResource Primary}" /> | ||||
|                 <Setter Property="Shell.BackgroundColor" Value="{DynamicResource Primary}" /> | ||||
|                 <Setter Property="Shell.ForegroundColor" Value="White" /> | ||||
|                 <Setter Property="Shell.TitleColor" Value="White" /> | ||||
|                 <Setter Property="Shell.DisabledColor" Value="#B4FFFFFF" /> | ||||
|                 <Setter Property="Shell.UnselectedColor" Value="#95FFFFFF" /> | ||||
|                 <Setter Property="Shell.TabBarBackgroundColor" Value="{StaticResource Primary}" /> | ||||
|                 <Setter Property="Shell.TabBarBackgroundColor" Value="{DynamicResource Primary}" /> | ||||
|                 <Setter Property="Shell.TabBarForegroundColor" Value="White"/> | ||||
|                 <Setter Property="Shell.TabBarUnselectedColor" Value="#95FFFFFF"/> | ||||
|                 <Setter Property="Shell.TabBarTitleColor" Value="White"/> | ||||
| @@ -41,12 +41,12 @@ | ||||
|                             <VisualState x:Name="Normal"> | ||||
|                                 <VisualState.Setters> | ||||
|                                     <Setter Property="BackgroundColor" Value="{x:OnPlatform UWP=Transparent, iOS=White}" /> | ||||
|                                     <Setter TargetName="FlyoutItemLabel" Property="Label.TextColor" Value="{StaticResource Primary}" /> | ||||
|                                     <Setter TargetName="FlyoutItemLabel" Property="Label.TextColor" Value="{DynamicResource Primary}" /> | ||||
|                                 </VisualState.Setters> | ||||
|                             </VisualState> | ||||
|                             <VisualState x:Name="Selected"> | ||||
|                                 <VisualState.Setters> | ||||
|                                     <Setter Property="BackgroundColor" Value="{StaticResource Primary}" /> | ||||
|                                     <Setter Property="BackgroundColor" Value="{DynamicResource Primary}" /> | ||||
|                                 </VisualState.Setters> | ||||
|                             </VisualState> | ||||
|                         </VisualStateGroup> | ||||
| @@ -63,7 +63,7 @@ | ||||
|                         <VisualStateGroup x:Name="CommonStates"> | ||||
|                             <VisualState x:Name="Normal"> | ||||
|                                 <VisualState.Setters> | ||||
|                                     <Setter TargetName="FlyoutItemLabel" Property="Label.TextColor" Value="{StaticResource Primary}" /> | ||||
|                                     <Setter TargetName="FlyoutItemLabel" Property="Label.TextColor" Value="{DynamicResource Primary}" /> | ||||
|                                 </VisualState.Setters> | ||||
|                             </VisualState> | ||||
|                         </VisualStateGroup> | ||||
|   | ||||
| @@ -1,12 +1,10 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using Gallery.ViewModels; | ||||
| using Gallery.Views; | ||||
| using Xamarin.Forms; | ||||
|  | ||||
| namespace Gallery | ||||
| { | ||||
|     public partial class AppShell : Xamarin.Forms.Shell | ||||
|     public partial class AppShell : Shell | ||||
|     { | ||||
|         public AppShell() | ||||
|         { | ||||
| @@ -17,7 +15,7 @@ namespace Gallery | ||||
|  | ||||
|         private async void OnMenuItemClicked(object sender, EventArgs e) | ||||
|         { | ||||
|             await Shell.Current.GoToAsync("//LoginPage"); | ||||
|             await Current.GoToAsync("//LoginPage"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -6,9 +6,10 @@ | ||||
|     <SharedGUID>{E72B5C40-090B-4A1C-9170-BD33C14A9A91}</SharedGUID> | ||||
|   </PropertyGroup> | ||||
|   <PropertyGroup Label="Configuration"> | ||||
|     <Import_RootNamespace>Gallery.Share</Import_RootNamespace> | ||||
|     <Import_RootNamespace>Gallery</Import_RootNamespace> | ||||
|   </PropertyGroup> | ||||
|   <ItemGroup> | ||||
|     <Compile Include="$(MSBuildThisFileDirectory)App.cs" /> | ||||
|     <Compile Include="$(MSBuildThisFileDirectory)Models\Item.cs" /> | ||||
|     <Compile Include="$(MSBuildThisFileDirectory)Services\IDataStore.cs" /> | ||||
|     <Compile Include="$(MSBuildThisFileDirectory)Services\MockDataStore.cs" /> | ||||
| @@ -33,18 +34,26 @@ | ||||
|     <Compile Include="$(MSBuildThisFileDirectory)Views\NewItemPage.xaml.cs"> | ||||
|       <DependentUpon>Views\NewItemPage.xaml</DependentUpon> | ||||
|     </Compile> | ||||
|     <Compile Include="$(MSBuildThisFileDirectory)App.xaml.cs"> | ||||
|       <DependentUpon>App.xaml</DependentUpon> | ||||
|     </Compile> | ||||
|     <Compile Include="$(MSBuildThisFileDirectory)AppShell.xaml.cs"> | ||||
|       <DependentUpon>AppShell.xaml</DependentUpon> | ||||
|     </Compile> | ||||
|     <Compile Include="$(MSBuildThisFileDirectory)Services\Environment.cs" /> | ||||
|     <Compile Include="$(MSBuildThisFileDirectory)Resources\PlatformCulture.cs" /> | ||||
|     <Compile Include="$(MSBuildThisFileDirectory)Resources\Helper.cs" /> | ||||
|     <Compile Include="$(MSBuildThisFileDirectory)Resources\Theme\Theme.cs" /> | ||||
|     <Compile Include="$(MSBuildThisFileDirectory)Resources\Theme\LightTheme.cs" /> | ||||
|     <Compile Include="$(MSBuildThisFileDirectory)Resources\Theme\DarkTheme.cs" /> | ||||
|     <Compile Include="$(MSBuildThisFileDirectory)Resources\UI\Definition.cs" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <Folder Include="$(MSBuildThisFileDirectory)Models\" /> | ||||
|     <Folder Include="$(MSBuildThisFileDirectory)Services\" /> | ||||
|     <Folder Include="$(MSBuildThisFileDirectory)ViewModels\" /> | ||||
|     <Folder Include="$(MSBuildThisFileDirectory)Views\" /> | ||||
|     <Folder Include="$(MSBuildThisFileDirectory)Resources\" /> | ||||
|     <Folder Include="$(MSBuildThisFileDirectory)Resources\Theme\" /> | ||||
|     <Folder Include="$(MSBuildThisFileDirectory)Resources\Languages\" /> | ||||
|     <Folder Include="$(MSBuildThisFileDirectory)Resources\UI\" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\AboutPage.xaml"> | ||||
| @@ -67,13 +76,12 @@ | ||||
|       <SubType>Designer</SubType> | ||||
|       <Generator>MSBuild:UpdateDesignTimeXaml</Generator> | ||||
|     </EmbeddedResource> | ||||
|     <EmbeddedResource Include="$(MSBuildThisFileDirectory)App.xaml"> | ||||
|       <SubType>Designer</SubType> | ||||
|       <Generator>MSBuild:UpdateDesignTimeXaml</Generator> | ||||
|     </EmbeddedResource> | ||||
|     <EmbeddedResource Include="$(MSBuildThisFileDirectory)AppShell.xaml"> | ||||
|       <SubType>Designer</SubType> | ||||
|       <Generator>MSBuild:UpdateDesignTimeXaml</Generator> | ||||
|     </EmbeddedResource> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <None Include="$(MSBuildThisFileDirectory)Resources\Languages\zh-CN.xml" /> | ||||
|   </ItemGroup> | ||||
| </Project> | ||||
							
								
								
									
										100
									
								
								Gallery.Share/Resources/Helper.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								Gallery.Share/Resources/Helper.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Reflection; | ||||
| using System.Xml; | ||||
| using Gallery.Util; | ||||
| using Xamarin.Forms.Xaml; | ||||
|  | ||||
| namespace Gallery.Resources | ||||
| { | ||||
|     public class Helper | ||||
|     { | ||||
|         private static readonly Dictionary<string, LanguageResource> dict = new(); | ||||
|  | ||||
|         public static string GetResource(string name, params object[] args) | ||||
|         { | ||||
|             if (!dict.TryGetValue(App.CurrentCulture.PlatformString, out LanguageResource lang)) | ||||
|             { | ||||
|                 lang = new LanguageResource(App.CurrentCulture); | ||||
|                 dict.Add(App.CurrentCulture.PlatformString, lang); | ||||
|             } | ||||
|             if (args == null || args.Length == 0) | ||||
|             { | ||||
|                 return lang[name]; | ||||
|             } | ||||
|             return string.Format(lang[name], args); | ||||
|         } | ||||
|  | ||||
|         private class LanguageResource | ||||
|         { | ||||
|             private readonly Dictionary<string, string> strings; | ||||
|  | ||||
|             public string this[string key] | ||||
|             { | ||||
|                 get | ||||
|                 { | ||||
|                     if (strings != null && strings.TryGetValue(key, out string val)) | ||||
|                     { | ||||
|                         return val; | ||||
|                     } | ||||
|                     return key; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             public LanguageResource(PlatformCulture lang) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     const string PROJECT = "Gallery"; | ||||
|                     const string PLATFORM = | ||||
| #if __IOS__ | ||||
|                         "iOS"; | ||||
| #elif __ANDROID__ | ||||
|                         "Droid"; | ||||
| #endif | ||||
|                     var langId = $"{PROJECT}.{PLATFORM}.Resources.Languages.{lang.Language}.xml"; | ||||
|                     var langCodeId = $"{PROJECT}.{PLATFORM}.Resources.Languages.{lang.LanguageCode}.xml"; | ||||
|                     var assembly = IntrospectionExtensions.GetTypeInfo(typeof(LanguageResource)).Assembly; | ||||
|                     var names = assembly.GetManifestResourceNames(); | ||||
|                     var name = names.FirstOrDefault(n => | ||||
|                         string.Equals(n, langId, StringComparison.OrdinalIgnoreCase) || | ||||
|                         string.Equals(n, langCodeId, StringComparison.OrdinalIgnoreCase)); | ||||
|                     if (name == null) | ||||
|                     { | ||||
|                         name = $"{PROJECT}.{PLATFORM}.Resources.Languages.zh-CN.xml"; | ||||
|                     } | ||||
|                     var xml = new XmlDocument(); | ||||
|                     using (var stream = assembly.GetManifestResourceStream(name)) | ||||
|                     { | ||||
|                         xml.Load(stream); | ||||
|                     } | ||||
|                     strings = new Dictionary<string, string>(); | ||||
|                     foreach (XmlElement ele in xml.DocumentElement) | ||||
|                     { | ||||
|                         strings[ele.Name] = ele.InnerText; | ||||
|                     } | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     // load failed | ||||
|                     Log.Error("language.ctor", $"failed to load xml resource: {ex.Message}"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public class TextExtension : IMarkupExtension | ||||
|     { | ||||
|         public string Text { get; set; } | ||||
|  | ||||
|         public object ProvideValue(IServiceProvider serviceProvider) | ||||
|         { | ||||
|             if (Text == null) | ||||
|             { | ||||
|                 return string.Empty; | ||||
|             } | ||||
|             return Helper.GetResource(Text); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										4
									
								
								Gallery.Share/Resources/Languages/zh-CN.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								Gallery.Share/Resources/Languages/zh-CN.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" ?> | ||||
| <root> | ||||
|     <Title>Gallery</Title> | ||||
| </root> | ||||
							
								
								
									
										36
									
								
								Gallery.Share/Resources/PlatformCulture.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								Gallery.Share/Resources/PlatformCulture.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| namespace Gallery.Resources | ||||
| { | ||||
|     public class PlatformCulture | ||||
|     { | ||||
|         public string PlatformString { get; private set; } | ||||
|         public string LanguageCode { get; private set; } | ||||
|         public string LocaleCode { get; private set; } | ||||
|  | ||||
|         public string Language => string.IsNullOrEmpty(LocaleCode) ? LanguageCode : $"{LanguageCode}-{LocaleCode}"; | ||||
|  | ||||
|         public PlatformCulture() : this(null) { } | ||||
|         public PlatformCulture(string cultureString) | ||||
|         { | ||||
|             if (string.IsNullOrEmpty(cultureString)) | ||||
|             { | ||||
|                 cultureString = "zh-CN"; | ||||
|             } | ||||
|  | ||||
|             PlatformString = cultureString.Replace('_', '-'); | ||||
|             var index = PlatformString.IndexOf('-'); | ||||
|             if (index > 0) | ||||
|             { | ||||
|                 var parts = PlatformString.Split('-'); | ||||
|                 LanguageCode = parts[0]; | ||||
|                 LocaleCode = parts[^1]; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 LanguageCode = PlatformString; | ||||
|                 LocaleCode = string.Empty; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public override string ToString() => PlatformString; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										35
									
								
								Gallery.Share/Resources/Theme/DarkTheme.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								Gallery.Share/Resources/Theme/DarkTheme.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| using Gallery.Services; | ||||
| using Xamarin.Forms; | ||||
|  | ||||
| namespace Gallery.Resources.Theme | ||||
| { | ||||
|     public class DarkTheme : Theme | ||||
|     { | ||||
|         private static DarkTheme instance; | ||||
|         public static DarkTheme Instance | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if (instance == null) | ||||
|                 { | ||||
|                     instance = new DarkTheme(); | ||||
|                 } | ||||
|                 return instance; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public DarkTheme() | ||||
|         { | ||||
|             InitColors(); | ||||
|             InitResources(); | ||||
|         } | ||||
|  | ||||
|         private void InitColors() | ||||
|         { | ||||
|             Add(StatusBarStyle, StatusBarStyles.WhiteText); | ||||
|             Add(NavigationColor, Color.FromRgb(0x11, 0x11, 0x11)); | ||||
|  | ||||
|             Add(Primary, Color.FromRgb(33, 150, 243)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										35
									
								
								Gallery.Share/Resources/Theme/LightTheme.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								Gallery.Share/Resources/Theme/LightTheme.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| using Gallery.Services; | ||||
| using Xamarin.Forms; | ||||
|  | ||||
| namespace Gallery.Resources.Theme | ||||
| { | ||||
|     public class LightTheme : Theme | ||||
|     { | ||||
|         private static LightTheme instance; | ||||
|         public static LightTheme Instance | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if (instance == null) | ||||
|                 { | ||||
|                     instance = new LightTheme(); | ||||
|                 } | ||||
|                 return instance; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public LightTheme() | ||||
|         { | ||||
|             InitColors(); | ||||
|             InitResources(); | ||||
|         } | ||||
|  | ||||
|         private void InitColors() | ||||
|         { | ||||
|             Add(StatusBarStyle, StatusBarStyles.DarkText); | ||||
|             Add(NavigationColor, Color.FromRgb(0xf0, 0xf0, 0xf0)); | ||||
|  | ||||
|             Add(Primary, Color.FromRgb(33, 150, 243)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										44
									
								
								Gallery.Share/Resources/Theme/Theme.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								Gallery.Share/Resources/Theme/Theme.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| using Gallery.Resources.UI; | ||||
| using Xamarin.Forms; | ||||
|  | ||||
| namespace Gallery.Resources.Theme | ||||
| { | ||||
|     public abstract class Theme : ResourceDictionary | ||||
|     { | ||||
|         public const string StatusBarStyle = nameof(StatusBarStyle); | ||||
|         public const string NavigationColor = nameof(NavigationColor); | ||||
|  | ||||
|         public const string IconLightFamily = nameof(IconLightFamily); | ||||
|         public const string IconRegularFamily = nameof(IconRegularFamily); | ||||
|         public const string IconSolidFamily = nameof(IconSolidFamily); | ||||
|         public const string ScreenBottomPadding = nameof(ScreenBottomPadding); | ||||
|  | ||||
|         public const string IconClose = nameof(IconClose); | ||||
|         public const string FontIconRefresh = nameof(FontIconRefresh); | ||||
|  | ||||
|         public const string Primary = nameof(Primary); | ||||
|  | ||||
|         protected void InitResources() | ||||
|         { | ||||
|             Add(IconLightFamily, Definition.IconLightFamily); | ||||
|             Add(IconRegularFamily, Definition.IconRegularFamily); | ||||
|             Add(IconSolidFamily, Definition.IconSolidFamily); | ||||
|             Add(ScreenBottomPadding, Definition.ScreenBottomPadding); | ||||
|  | ||||
|             Add(FontIconRefresh, GetFontIcon(Definition.IconRefresh, Definition.IconSolidFamily)); | ||||
|  | ||||
|             Add(IconClose, Definition.IconClose); | ||||
|         } | ||||
|  | ||||
|         private FontImageSource GetFontIcon(string icon, string family, Color color = default) | ||||
|         { | ||||
|             return new FontImageSource | ||||
|             { | ||||
|                 FontFamily = family, | ||||
|                 Glyph = icon, | ||||
|                 Size = Definition.FontSizeTitle, | ||||
|                 Color = color | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										129
									
								
								Gallery.Share/Resources/UI/Definition.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								Gallery.Share/Resources/UI/Definition.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | ||||
| using System; | ||||
| using Gallery.Util; | ||||
| using Xamarin.Essentials; | ||||
| using Xamarin.Forms; | ||||
|  | ||||
| namespace Gallery.Resources.UI | ||||
| { | ||||
|     public static class Definition | ||||
|     { | ||||
|         public const double FontSizeTitle = 18.0; | ||||
|  | ||||
|         public static readonly Thickness ScreenBottomPadding; | ||||
|  | ||||
| #if __IOS__ | ||||
|         public const string IconLightFamily = "FontAwesome5Pro-Light"; | ||||
|         public const string IconRegularFamily = "FontAwesome5Pro-Regular"; | ||||
|         public const string IconSolidFamily = "FontAwesome5Pro-Solid"; | ||||
|  | ||||
|         public const string IconLeft = "\uf104"; | ||||
| #elif __ANDROID__ | ||||
|         public const string IconLightFamily = "fa-light-300.ttf#FontAwesome5Pro-Light"; | ||||
|         public const string IconRegularFamily = "fa-regular-400.ttf#FontAwesome5Pro-Regular"; | ||||
|         public const string IconSolidFamily = "fa-solid-900.ttf#FontAwesome5Pro-Solid"; | ||||
|  | ||||
|         public const string IconLeft = "\uf053"; | ||||
| #endif | ||||
|  | ||||
|         public const string IconRefresh = "\uf2f9"; | ||||
|         public const string IconClose = "\uf057"; | ||||
|  | ||||
|         static Definition() | ||||
|         { | ||||
|             _ = IsFullscreenDevice; | ||||
|             if (_isBottomPadding) | ||||
|             { | ||||
|                 if (DeviceInfo.Idiom == DeviceIdiom.Phone) | ||||
|                 { | ||||
|                     ScreenBottomPadding = new Thickness(0, 0, 0, 26); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     ScreenBottomPadding = new Thickness(0, 0, 0, 16); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private static bool _isBottomPadding; | ||||
|         private static bool? _isFullscreenDevice; | ||||
|         public static bool IsFullscreenDevice | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if (_isFullscreenDevice != null) | ||||
|                 { | ||||
|                     return _isFullscreenDevice.Value; | ||||
|                 } | ||||
| #if __IOS__ | ||||
|                 try | ||||
|                 { | ||||
|                     var model = DeviceInfo.Model; | ||||
|                     if (model == "iPhone10,3") | ||||
|                     { | ||||
|                         // iPhone X | ||||
|                         _isFullscreenDevice = true; | ||||
|                         _isBottomPadding = true; | ||||
|                     } | ||||
|                     else if (model.StartsWith("iPhone")) | ||||
|                     { | ||||
|                         var vs = model[6..].Split(','); | ||||
|                         if (vs.Length == 2 && int.TryParse(vs[0], out int main) && int.TryParse(vs[1], out int sub)) | ||||
|                         { | ||||
|                             // iPhone X | ||||
|                             // iPhone XS/XR/11/12 or newer | ||||
|                             var flag = (main == 10 && sub == 6) || main > 10; | ||||
|                             _isFullscreenDevice = flag; | ||||
|                             _isBottomPadding = flag; | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             _isFullscreenDevice = false; | ||||
|                         } | ||||
|                     } | ||||
|                     else if (model.StartsWith("iPad")) | ||||
|                     { | ||||
|                         var vs = model[4..].Split(','); | ||||
|                         if (vs.Length == 2 && int.TryParse(vs[0], out int main) && int.TryParse(vs[1], out int sub)) | ||||
|                         { | ||||
|                             // iPad Pro 11-inch (gen 1~3) | ||||
|                             // iPad Pro 12.9-inch (gen 3~5) | ||||
|                             var flag = main == 8 || (main == 13 && sub >= 4 && sub < 12); | ||||
|                             _isBottomPadding = flag; | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             _isFullscreenDevice = false; | ||||
|                         } | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
| #if DEBUG | ||||
|                         // simulator | ||||
|                         var name = DeviceInfo.Name; | ||||
|                         var flag = | ||||
|                             name.StartsWith("iPhone X") || | ||||
|                             name.StartsWith("iPhone 11") || | ||||
|                             name.StartsWith("iPhone 12"); | ||||
|                         _isFullscreenDevice = flag; | ||||
|                         _isBottomPadding = flag || | ||||
|                             name.StartsWith("iPad Pro (11-inch)") || | ||||
|                             name.StartsWith("iPad Pro (12.9-inch) (3rd generation)") || | ||||
|                             name.StartsWith("iPad Pro (12.9-inch) (4th generation)") || | ||||
|                             name.StartsWith("iPad Pro (12.9-inch) (5th generation)"); | ||||
| #else | ||||
|                         _isFullscreenDevice = false; | ||||
| #endif | ||||
|                     } | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     Log.Error("device.model.get", $"failed to get the device model. {ex.Message}"); | ||||
|                 } | ||||
| #else | ||||
|                 _isFullscreenDevice = false; | ||||
| #endif | ||||
|                 return _isFullscreenDevice.Value; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										216
									
								
								Gallery.Share/Services/Environment.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								Gallery.Share/Services/Environment.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,216 @@ | ||||
| #if __ANDROID_21__ | ||||
| using Xamarin.Forms.Platform.Android; | ||||
| #endif | ||||
| using System.Globalization; | ||||
| using System.Threading; | ||||
| using Gallery.Resources; | ||||
| using Gallery.Util; | ||||
| using Xamarin.Forms; | ||||
|  | ||||
| namespace Gallery.Services | ||||
| { | ||||
|     public class Environment | ||||
|     { | ||||
|         private static readonly CultureInfo DefaultCulture = new("zh-CN"); | ||||
|  | ||||
| #if __ANDROID_21__ | ||||
|         public static void SetStatusBarColor(Color color) | ||||
|         { | ||||
|             if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.Lollipop) | ||||
|             { | ||||
|                 Android.OS.Droid.MainActivity.Main.SetStatusBarColor(color.ToAndroid()); | ||||
|                 Android.OS.Droid.MainActivity.Main.Window.DecorView.SystemUiVisibility = | ||||
|                     App.CurrentTheme == Xamarin.Essentials.AppTheme.Dark ? | ||||
|                     Android.Views.StatusBarVisibility.Visible : | ||||
|                     (Android.Views.StatusBarVisibility)Android.Views.SystemUiFlags.LightStatusBar; | ||||
|             } | ||||
|         } | ||||
| #endif | ||||
|  | ||||
|         public static void SetStatusBarStyle(StatusBarStyles style) | ||||
|         { | ||||
| #if __IOS__ | ||||
|             SetStatusBarStyle(ConvertStyle(style)); | ||||
|         } | ||||
|  | ||||
|         public static void SetStatusBarStyle(UIKit.UIStatusBarStyle style) | ||||
|         { | ||||
|             if (UIKit.UIApplication.SharedApplication.StatusBarStyle == style) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|             if (style == UIKit.UIStatusBarStyle.BlackOpaque) | ||||
|             { | ||||
|                 UIKit.UIApplication.SharedApplication.SetStatusBarHidden(true, true); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 if (UIKit.UIApplication.SharedApplication.StatusBarHidden) | ||||
|                 { | ||||
|                     UIKit.UIApplication.SharedApplication.SetStatusBarStyle(style, false); | ||||
|                     UIKit.UIApplication.SharedApplication.SetStatusBarHidden(false, true); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     UIKit.UIApplication.SharedApplication.SetStatusBarStyle(style, true); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public static UIKit.UIStatusBarStyle ConvertStyle(StatusBarStyles style) | ||||
|         { | ||||
|             return style switch | ||||
|             { | ||||
|                 StatusBarStyles.DarkText => UIKit.UIStatusBarStyle.DarkContent, | ||||
|                 StatusBarStyles.WhiteText => UIKit.UIStatusBarStyle.LightContent, | ||||
|                 StatusBarStyles.Hidden => UIKit.UIStatusBarStyle.BlackOpaque, | ||||
|                 _ => UIKit.UIStatusBarStyle.Default, | ||||
|             }; | ||||
| #endif | ||||
|         } | ||||
|  | ||||
|         public static void SetCultureInfo(CultureInfo ci) | ||||
|         { | ||||
|             Thread.CurrentThread.CurrentCulture = ci; | ||||
|             Thread.CurrentThread.CurrentUICulture = ci; | ||||
| #if DEBUG | ||||
|             Log.Print($"CurrentCulture set: {ci.Name}"); | ||||
| #endif | ||||
|         } | ||||
|  | ||||
|         public static CultureInfo GetCurrentCultureInfo() | ||||
|         { | ||||
|             string lang; | ||||
|  | ||||
| #if __IOS__ | ||||
|             if (Foundation.NSLocale.PreferredLanguages.Length > 0) | ||||
|             { | ||||
|                 var pref = Foundation.NSLocale.PreferredLanguages[0]; | ||||
|                 lang = ToDotnetLanguage(pref); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 lang = "zh-CN"; | ||||
|             } | ||||
| #elif __ANDROID__ | ||||
|             var locale = Java.Util.Locale.Default; | ||||
|             lang = ToDotnetLanguage(locale.ToString().Replace('_', '-')); | ||||
| #endif | ||||
|  | ||||
|             CultureInfo ci; | ||||
|             var platform = new PlatformCulture(lang); | ||||
|             try | ||||
|             { | ||||
|                 ci = new CultureInfo(platform.Language); | ||||
|             } | ||||
|             catch (CultureNotFoundException e) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     var fallback = ToDotnetFallbackLanguage(platform); | ||||
|                     Log.Print($"{lang} failed, trying {fallback} ({e.Message})"); | ||||
|                     ci = new CultureInfo(fallback); | ||||
|                 } | ||||
|                 catch (CultureNotFoundException e1) | ||||
|                 { | ||||
|                     Log.Error("culture.get", $"{lang} couldn't be set, using 'zh-CN' ({e1.Message})"); | ||||
|                     ci = DefaultCulture; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return ci; | ||||
|         } | ||||
|  | ||||
| #if __IOS__ | ||||
|         private static string ToDotnetLanguage(string iOSLanguage) | ||||
|         { | ||||
|             //certain languages need to be converted to CultureInfo equivalent | ||||
|             string netLanguage = iOSLanguage switch | ||||
|             { | ||||
|                 // "Malaysian (Malaysia)" not supported .NET culture | ||||
|                 // "Malaysian (Singapore)" not supported .NET culture | ||||
|                 "ms-MY" or "ms-SG" => "ms", // closest supported | ||||
|                 // "Schwiizertüütsch (Swiss German)" not supported .NET culture | ||||
|                 "gsw-CH" => "de-CH", // closest supported | ||||
|                 // add more application-specific cases here (if required) | ||||
|                 // ONLY use cultures that have been tested and known to work | ||||
|                 _ => iOSLanguage | ||||
|             }; | ||||
|  | ||||
| #if DEBUG | ||||
|             Log.Print($"iOS Language: {iOSLanguage}, .NET Language/Locale: {netLanguage}"); | ||||
| #endif | ||||
|             return netLanguage; | ||||
|         } | ||||
| #elif __ANDROID__ | ||||
|         private static string ToDotnetLanguage(string androidLanguage) | ||||
|         { | ||||
|             //certain languages need to be converted to CultureInfo equivalent | ||||
|             string netLanguage = androidLanguage switch | ||||
|             { | ||||
|                 // "Malaysian (Brunei)" not supported .NET culture | ||||
|                 // "Malaysian (Malaysia)" not supported .NET culture | ||||
|                 // "Malaysian (Singapore)" not supported .NET culture | ||||
|                 "ms-BN" or "ms-MY" or "ms-SG" => "ms", // closest supported | ||||
|                 // "Indonesian (Indonesia)" has different code in  .NET  | ||||
|                 "in-ID" => "id-ID", // correct code for .NET | ||||
|                 // "Schwiizertüütsch (Swiss German)" not supported .NET culture | ||||
|                 "gsw-CH" => "de-CH", // closest supported | ||||
|                 // add more application-specific cases here (if required) | ||||
|                 // ONLY use cultures that have been tested and known to work | ||||
|                 _ => androidLanguage | ||||
|             }; | ||||
|  | ||||
| #if DEBUG | ||||
|             Log.Print($"Android Language: {androidLanguage}, .NET Language/Locale: {netLanguage}"); | ||||
| #endif | ||||
|             return netLanguage; | ||||
|         } | ||||
| #endif | ||||
|  | ||||
|         private static string ToDotnetFallbackLanguage(PlatformCulture platCulture) | ||||
|         { | ||||
|             string netLanguage = platCulture.LanguageCode switch | ||||
|             { | ||||
|                 "pt" => "pt-PT", // fallback to Portuguese (Portugal) | ||||
|                 "gsw" => "de-CH", // equivalent to German (Switzerland) for this app | ||||
|                 // add more application-specific cases here (if required) | ||||
|                 // ONLY use cultures that have been tested and known to work | ||||
|                 _ => platCulture.LanguageCode // use the first part of the identifier (two chars, usually); | ||||
|             }; | ||||
|  | ||||
| #if DEBUG | ||||
|             Log.Print($".NET Fallback Language/Locale: {platCulture.LanguageCode} to {netLanguage} (application-specific)"); | ||||
| #endif | ||||
|             return netLanguage; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static class Screen | ||||
|     { | ||||
|         private const string StatusBarStyle = nameof(StatusBarStyle); | ||||
|         private const string HomeIndicatorAutoHidden = nameof(HomeIndicatorAutoHidden); | ||||
|  | ||||
|         public static readonly BindableProperty StatusBarStyleProperty = BindableProperty.CreateAttached(StatusBarStyle, typeof(StatusBarStyles), typeof(Page), StatusBarStyles.WhiteText); | ||||
|         public static StatusBarStyles GetStatusBarStyle(VisualElement page) => (StatusBarStyles)page.GetValue(StatusBarStyleProperty); | ||||
|         public static void SetStatusBarStyle(VisualElement page, StatusBarStyles value) => page.SetValue(StatusBarStyleProperty, value); | ||||
|  | ||||
|         public static readonly BindableProperty HomeIndicatorAutoHiddenProperty = BindableProperty.CreateAttached(HomeIndicatorAutoHidden, typeof(bool), typeof(Shell), false); | ||||
|         public static bool GetHomeIndicatorAutoHidden(VisualElement page) => (bool)page.GetValue(HomeIndicatorAutoHiddenProperty); | ||||
|         public static void SetHomeIndicatorAutoHidden(VisualElement page, bool value) => page.SetValue(HomeIndicatorAutoHiddenProperty, value); | ||||
|     } | ||||
|  | ||||
|     public enum StatusBarStyles | ||||
|     { | ||||
|         Default, | ||||
|         // Will behave as normal.  | ||||
|         // White text on black NavigationBar/in iOS Dark mode and  | ||||
|         // Black text on white NavigationBar/in iOS Light mode | ||||
|         DarkText, | ||||
|         // Will switch the color of content of StatusBar to black.  | ||||
|         WhiteText, | ||||
|         // Will switch the color of content of StatusBar to white.  | ||||
|         Hidden | ||||
|         // Will hide the StatusBar | ||||
|     } | ||||
| } | ||||
| @@ -43,7 +43,7 @@ | ||||
|                 </Label> | ||||
|                 <Button Margin="0,10,0,0" Text="Learn more" | ||||
|                         Command="{Binding OpenWebCommand}" | ||||
|                         BackgroundColor="{StaticResource Primary}" | ||||
|                         BackgroundColor="{DynamicResource Primary}" | ||||
|                         TextColor="White" /> | ||||
|             </StackLayout> | ||||
|         </ScrollView> | ||||
|   | ||||
							
								
								
									
										99
									
								
								Gallery.Util/Extensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								Gallery.Util/Extensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | ||||
| using System; | ||||
| using Xamarin.Forms; | ||||
|  | ||||
| namespace Gallery.Util | ||||
| { | ||||
|     public static class Extensions | ||||
|     { | ||||
|         public static T Binding<T>(this T view, BindableProperty property, string name, BindingMode mode = BindingMode.Default, IValueConverter converter = null) where T : BindableObject | ||||
|         { | ||||
|             if (name == null) | ||||
|             { | ||||
|                 view.SetValue(property, property.DefaultValue); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 view.SetBinding(property, name, mode, converter); | ||||
|             } | ||||
|             return view; | ||||
|         } | ||||
|  | ||||
|         public static T DynamicResource<T>(this T view, BindableProperty property, string key) where T : Element | ||||
|         { | ||||
|             view.SetDynamicResource(property, key); | ||||
|             return view; | ||||
|         } | ||||
|  | ||||
|         public static T GridRow<T>(this T view, int row) where T : BindableObject | ||||
|         { | ||||
|             Grid.SetRow(view, row); | ||||
|             return view; | ||||
|         } | ||||
|  | ||||
|         public static T GridRowSpan<T>(this T view, int rowSpan) where T : BindableObject | ||||
|         { | ||||
|             Grid.SetRowSpan(view, rowSpan); | ||||
|             return view; | ||||
|         } | ||||
|  | ||||
|         public static T GridColumn<T>(this T view, int column) where T : BindableObject | ||||
|         { | ||||
|             Grid.SetColumn(view, column); | ||||
|             return view; | ||||
|         } | ||||
|  | ||||
|         public static T GridColumnSpan<T>(this T view, int columnSpan) where T : BindableObject | ||||
|         { | ||||
|             Grid.SetColumnSpan(view, columnSpan); | ||||
|             return view; | ||||
|         } | ||||
|  | ||||
|         public static int IndexOf<T>(this T[] array, Predicate<T> predicate) | ||||
|         { | ||||
|             for (var i = 0; i < array.Length; i++) | ||||
|             { | ||||
|                 if (predicate(array[i])) | ||||
|                 { | ||||
|                     return i; | ||||
|                 } | ||||
|             } | ||||
|             return -1; | ||||
|         } | ||||
|  | ||||
|         public static int LastIndexOf<T>(this T[] array, Predicate<T> predicate) | ||||
|         { | ||||
|             for (var i = array.Length - 1; i >= 0; i--) | ||||
|             { | ||||
|                 if (predicate(array[i])) | ||||
|                 { | ||||
|                     return i; | ||||
|                 } | ||||
|             } | ||||
|             return -1; | ||||
|         } | ||||
|  | ||||
|         public static bool All<T>(this T[] array, Predicate<T> predicate) | ||||
|         { | ||||
|             for (var i = 0; i < array.Length; i++) | ||||
|             { | ||||
|                 if (!predicate(array[i])) | ||||
|                 { | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         public static bool AnyFor<T>(this T[] array, int from, int to, Predicate<T> predicate) | ||||
|         { | ||||
|             for (var i = from; i <= to; i++) | ||||
|             { | ||||
|                 if (predicate(array[i])) | ||||
|                 { | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -6,6 +6,12 @@ | ||||
|     <AssemblyOriginatorKeyFile>..\Ref\Tsanie.snk</AssemblyOriginatorKeyFile> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | ||||
|     <LangVersion>9.0</LangVersion> | ||||
|   </PropertyGroup> | ||||
|   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> | ||||
|     <LangVersion>9.0</LangVersion> | ||||
|   </PropertyGroup> | ||||
|   <ItemGroup> | ||||
|     <None Remove="System.Text.Json" /> | ||||
|     <None Remove="Xamarin.Forms" /> | ||||
|   | ||||
| @@ -4,7 +4,7 @@ namespace Gallery.Util.Interface | ||||
| { | ||||
|     public interface IGallerySource | ||||
|     { | ||||
|         string GetCookie(); | ||||
|         void SetCookie(); | ||||
|  | ||||
|         GalleryItem[] GetRecentItems(int page); | ||||
|     } | ||||
|   | ||||
							
								
								
									
										41
									
								
								Gallery.Util/Log.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								Gallery.Util/Log.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| using System; | ||||
| using System.Diagnostics; | ||||
|  | ||||
| namespace Gallery.Util | ||||
| { | ||||
|     public static class Log  | ||||
|     { | ||||
|         public static ILog Logger { get; set; } = new DefaultLogger(); | ||||
|  | ||||
|         public static void Print(string message) | ||||
|         { | ||||
|             Logger?.Print(message); | ||||
|         } | ||||
|  | ||||
|         public static void Error(string category, string message) | ||||
|         { | ||||
|             Logger?.Error(category, message); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public class DefaultLogger : ILog | ||||
|     { | ||||
|         public void Print(string message) | ||||
|         { | ||||
| #if DEBUG | ||||
|             Debug.WriteLine("[{0:HH:mm:ss.fff}] - {1}", DateTime.Now, message); | ||||
| #endif | ||||
|         } | ||||
|  | ||||
|         public void Error(string category, string message) | ||||
|         { | ||||
|             Debug.Fail(string.Format("[{0:HH:mm:ss.fff}] - {1} - {2}", DateTime.Now, category, message)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public interface ILog | ||||
|     { | ||||
|         void Print(string message); | ||||
|         void Error(string category, string message); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										148
									
								
								Gallery.Util/ParallelTask.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								Gallery.Util/ParallelTask.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,148 @@ | ||||
| using System; | ||||
| using System.Threading; | ||||
|  | ||||
| namespace Gallery.Util | ||||
| { | ||||
|     public class ParallelTask : IDisposable | ||||
|     { | ||||
|         public static ParallelTask Start(string tag, int from, int toExclusive, int maxCount, Predicate<int> action, int tagIndex = -1, WaitCallback complete = null) | ||||
|         { | ||||
|             if (toExclusive <= from) | ||||
|             { | ||||
|                 if (complete != null) | ||||
|                 { | ||||
|                     ThreadPool.QueueUserWorkItem(complete); | ||||
|                 } | ||||
|                 return null; | ||||
|             } | ||||
|             var task = new ParallelTask(tag, from, toExclusive, maxCount, action, tagIndex, complete); | ||||
|             task.Start(); | ||||
|             return task; | ||||
|         } | ||||
|  | ||||
|         private readonly object sync = new(); | ||||
|         private int count; | ||||
|         private bool disposed; | ||||
|  | ||||
|         public int TagIndex { get; private set; } | ||||
|         private readonly string tag; | ||||
|         private readonly int max; | ||||
|         private readonly int from; | ||||
|         private readonly int to; | ||||
|         private readonly Predicate<int> action; | ||||
|         private readonly WaitCallback complete; | ||||
|  | ||||
|         private ParallelTask(string tag, int from, int to, int maxCount, Predicate<int> action, int tagIndex, WaitCallback complete) | ||||
|         { | ||||
|             if (maxCount <= 0) | ||||
|             { | ||||
|                 throw new ArgumentOutOfRangeException(nameof(maxCount)); | ||||
|             } | ||||
|             max = maxCount; | ||||
|             if (from >= to) | ||||
|             { | ||||
|                 throw new ArgumentOutOfRangeException(nameof(from)); | ||||
|             } | ||||
|             TagIndex = tagIndex; | ||||
|             this.tag = tag; | ||||
|             this.from = from; | ||||
|             this.to = to; | ||||
|             this.action = action; | ||||
|             this.complete = complete; | ||||
|         } | ||||
|  | ||||
|         public void Start() | ||||
|         { | ||||
|             ThreadPool.QueueUserWorkItem(DoStart); | ||||
|         } | ||||
|  | ||||
|         public void Dispose() | ||||
|         { | ||||
|             disposed = true; | ||||
|         } | ||||
|  | ||||
|         private void DoStart(object state) | ||||
|         { | ||||
| #if DEBUG | ||||
|             const long TIMEOUT = 60000L; | ||||
|             var sw = new System.Diagnostics.Stopwatch(); | ||||
|             long lastElapsed = 0; | ||||
|             sw.Start(); | ||||
| #endif | ||||
|             for (int i = from; i < to; i++) | ||||
|             { | ||||
|                 var index = i; | ||||
|                 while (count >= max) | ||||
|                 { | ||||
| #if DEBUG | ||||
|                     var elapsed = sw.ElapsedMilliseconds; | ||||
|                     if (elapsed - lastElapsed > TIMEOUT) | ||||
|                     { | ||||
|                         lastElapsed = elapsed; | ||||
|                         Log.Print($"WARNING: parallel task ({tag}), {count} tasks in queue, cost too much time ({elapsed:n0}ms)"); | ||||
|                     } | ||||
| #endif | ||||
|                     if (disposed) | ||||
|                     { | ||||
| #if DEBUG | ||||
|                         sw.Stop(); | ||||
|                         Log.Print($"parallel task determinate, disposed ({tag}), cost time ({elapsed:n0}ms)"); | ||||
| #endif | ||||
|                         return; | ||||
|                     } | ||||
|                     Thread.Sleep(16); | ||||
|                 } | ||||
|                 lock (sync) | ||||
|                 { | ||||
|                     count++; | ||||
|                 } | ||||
|                 ThreadPool.QueueUserWorkItem(o => | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         if (!action(index)) | ||||
|                         { | ||||
|                             disposed = true; | ||||
|                         } | ||||
|                     } | ||||
|                     catch (Exception ex) | ||||
|                     { | ||||
|                         Log.Error($"parallel.start ({tag})", $"failed to run action, index: {index}, error: {ex}"); | ||||
|                     } | ||||
|                     finally | ||||
|                     { | ||||
|                         lock (sync) | ||||
|                         { | ||||
|                             count--; | ||||
|                         } | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|             while (count > 0) | ||||
|             { | ||||
| #if DEBUG | ||||
|                 var elapsed = sw.ElapsedMilliseconds; | ||||
|                 if (elapsed - lastElapsed > TIMEOUT) | ||||
|                 { | ||||
|                     lastElapsed = elapsed; | ||||
|                     Log.Print($"WARNING: parallel task ({tag}), {count} ending tasks in queue, cost too much time ({elapsed:n0}ms)"); | ||||
|                 } | ||||
| #endif | ||||
|                 if (disposed) | ||||
|                 { | ||||
| #if DEBUG | ||||
|                     sw.Stop(); | ||||
|                     Log.Print($"parallel task determinate, disposed ({tag}), ending cost time ({elapsed:n0}ms)"); | ||||
| #endif | ||||
|                     return; | ||||
|                 } | ||||
|                 Thread.Sleep(16); | ||||
|             } | ||||
| #if DEBUG | ||||
|             sw.Stop(); | ||||
|             Log.Print($"parallel task done ({tag}), cost time ({sw.ElapsedMilliseconds:n0}ms)"); | ||||
| #endif | ||||
|             complete?.Invoke(null); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -30,6 +30,7 @@ | ||||
|     <MtouchDebug>true</MtouchDebug> | ||||
|     <CodesignProvision>Gallery.Dev</CodesignProvision> | ||||
|     <CodesignKey>Apple Development: Li Chen (5559SN7Z38)</CodesignKey> | ||||
|     <LangVersion>9.0</LangVersion> | ||||
|   </PropertyGroup> | ||||
|   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhoneSimulator' "> | ||||
|     <DebugType>none</DebugType> | ||||
| @@ -41,6 +42,7 @@ | ||||
|     <MtouchArch>x86_64</MtouchArch> | ||||
|     <CodesignProvision>Gallery.Ad-Hoc</CodesignProvision> | ||||
|     <CodesignKey>Apple Distribution: Li Chen (7HSM5CKPJ2)</CodesignKey> | ||||
|     <LangVersion>9.0</LangVersion> | ||||
|   </PropertyGroup> | ||||
|   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhone' "> | ||||
|     <DebugSymbols>true</DebugSymbols> | ||||
| @@ -57,6 +59,7 @@ | ||||
|     <MtouchLink>None</MtouchLink> | ||||
|     <MtouchInterpreter>-all</MtouchInterpreter> | ||||
|     <CodesignProvision>Gallery.Dev</CodesignProvision> | ||||
|     <LangVersion>9.0</LangVersion> | ||||
|   </PropertyGroup> | ||||
|   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' "> | ||||
|     <DebugType>none</DebugType> | ||||
| @@ -69,6 +72,7 @@ | ||||
|     <CodesignEntitlements>Entitlements.plist</CodesignEntitlements> | ||||
|     <CodesignProvision>Gallery.Ad-Hoc</CodesignProvision> | ||||
|     <MtouchLink>SdkOnly</MtouchLink> | ||||
|     <LangVersion>9.0</LangVersion> | ||||
|   </PropertyGroup> | ||||
|   <ItemGroup> | ||||
|     <Compile Include="Main.cs" /> | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										19
									
								
								Gallery.sln
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								Gallery.sln
									
									
									
									
									
								
							| @@ -9,6 +9,10 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Gallery.Share", "Gallery.Sh | ||||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gallery.Util", "Gallery.Util\Gallery.Util.csproj", "{222C22EC-3A47-4CF5-B9FB-CA28DE9F4BC8}" | ||||
| EndProject | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GallerySources", "GallerySources", "{F37B4FEC-D2B1-4289-BA6D-A154F783572A}" | ||||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gallery.Yandere", "GallerySources\Gallery.Yandere\Gallery.Yandere.csproj", "{F7ECCC03-28AC-4326-B0D1-F24C08808B9F}" | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		Debug|iPhoneSimulator = Debug|iPhoneSimulator | ||||
| @@ -43,6 +47,18 @@ Global | ||||
| 		{222C22EC-3A47-4CF5-B9FB-CA28DE9F4BC8}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{222C22EC-3A47-4CF5-B9FB-CA28DE9F4BC8}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{222C22EC-3A47-4CF5-B9FB-CA28DE9F4BC8}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{F7ECCC03-28AC-4326-B0D1-F24C08808B9F}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU | ||||
| 		{F7ECCC03-28AC-4326-B0D1-F24C08808B9F}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU | ||||
| 		{F7ECCC03-28AC-4326-B0D1-F24C08808B9F}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU | ||||
| 		{F7ECCC03-28AC-4326-B0D1-F24C08808B9F}.Release|iPhoneSimulator.Build.0 = Release|Any CPU | ||||
| 		{F7ECCC03-28AC-4326-B0D1-F24C08808B9F}.Debug|iPhone.ActiveCfg = Debug|Any CPU | ||||
| 		{F7ECCC03-28AC-4326-B0D1-F24C08808B9F}.Debug|iPhone.Build.0 = Debug|Any CPU | ||||
| 		{F7ECCC03-28AC-4326-B0D1-F24C08808B9F}.Release|iPhone.ActiveCfg = Release|Any CPU | ||||
| 		{F7ECCC03-28AC-4326-B0D1-F24C08808B9F}.Release|iPhone.Build.0 = Release|Any CPU | ||||
| 		{F7ECCC03-28AC-4326-B0D1-F24C08808B9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{F7ECCC03-28AC-4326-B0D1-F24C08808B9F}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{F7ECCC03-28AC-4326-B0D1-F24C08808B9F}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{F7ECCC03-28AC-4326-B0D1-F24C08808B9F}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(SolutionProperties) = preSolution | ||||
| 		HideSolutionNode = FALSE | ||||
| @@ -50,4 +66,7 @@ Global | ||||
| 	GlobalSection(ExtensibilityGlobals) = postSolution | ||||
| 		SolutionGuid = {A969B750-3E3E-4815-B336-02B32908D0C4} | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(NestedProjects) = preSolution | ||||
| 		{F7ECCC03-28AC-4326-B0D1-F24C08808B9F} = {F37B4FEC-D2B1-4289-BA6D-A154F783572A} | ||||
| 	EndGlobalSection | ||||
| EndGlobal | ||||
|   | ||||
							
								
								
									
										10
									
								
								GallerySources/Gallery.Yandere/Gallery.Yandere.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								GallerySources/Gallery.Yandere/Gallery.Yandere.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>netstandard2.1</TargetFramework> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\..\Gallery.Util\Gallery.Util.csproj" /> | ||||
|   </ItemGroup> | ||||
| </Project> | ||||
		Reference in New Issue
	
	Block a user