rename from Pixiview to Gallery

This commit is contained in:
2021-08-03 19:16:54 +08:00
parent 98676ce8b2
commit c41282a4b7
206 changed files with 7900 additions and 7891 deletions

193
Gallery/UI/AdaptedPage.cs Normal file
View File

@ -0,0 +1,193 @@
using System;
using Gallery.UI.Theme;
using Gallery.Utils;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Gallery.UI
{
public class AdaptedPage : ContentPage
{
public static readonly BindableProperty PageTopMarginProperty = BindableProperty.Create(
nameof(PageTopMargin), typeof(Thickness), typeof(AdaptedPage));
public static readonly BindableProperty PanelTopMarginProperty = BindableProperty.Create(
nameof(PanelTopMargin), typeof(Thickness), typeof(AdaptedPage));
public Thickness PageTopMargin
{
get => (Thickness)GetValue(PageTopMarginProperty);
protected set => SetValue(PageTopMarginProperty, value);
}
public Thickness PanelTopMargin
{
get => (Thickness)GetValue(PanelTopMarginProperty);
protected set => SetValue(PanelTopMarginProperty, value);
}
public event EventHandler Load;
public event EventHandler Unload;
protected static readonly bool isPhone = DeviceInfo.Idiom == DeviceIdiom.Phone;
public AdaptedPage()
{
SetDynamicResource(Screen.StatusBarStyleProperty, ThemeBase.StatusBarStyle);
Shell.SetNavBarHasShadow(this, true);
}
public virtual void OnLoad()
{
Load?.Invoke(this, EventArgs.Empty);
}
public virtual void OnUnload()
{
Unload?.Invoke(this, EventArgs.Empty);
}
#if __IOS__
public virtual void OnOrientationChanged(bool landscape)
{
var oldMargin = PageTopMargin;
var oldPanelMargin = PanelTopMargin;
Thickness newMargin;
Thickness newPanelMargin;
if (StyleDefinition.IsFullscreenDevice)
{
var iPhone12 =
#if DEBUG
DeviceInfo.Name.StartsWith("iPhone 12") ||
#endif
DeviceInfo.Model.StartsWith("iPhone13,");
newMargin = landscape ?
AppShell.NavigationBarOffset :
AppShell.TotalBarOffset;
newPanelMargin = landscape ?
AppShell.HalfNavigationBarOffset :
AppShell.NavigationBarOffset;
}
else if (isPhone)
{
newMargin = landscape ?
StyleDefinition.TopOffset32 :
AppShell.TotalBarOffset;
newPanelMargin = landscape ?
StyleDefinition.TopOffset16 :
StyleDefinition.TopOffset32;
}
else
{
// iPad
newMargin = AppShell.TotalBarOffset;
newPanelMargin = StyleDefinition.TopOffset37;
}
if (oldMargin != newMargin)
{
PageTopMargin = newMargin;
OnPageTopMarginChanged(oldMargin, newMargin);
}
if (oldPanelMargin != newPanelMargin)
{
PanelTopMargin = newPanelMargin;
}
}
protected virtual void OnPageTopMarginChanged(Thickness old, Thickness @new) { }
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
OnOrientationChanged(width > height);
}
protected void AnimateToMargin(View element, Thickness margin, bool animate = true)
{
var m = margin;
var start = element.Margin.Top - m.Top;
#if DEBUG
if (start != 0)
{
App.DebugPrint($"{element.GetType()}, margin-top from {element.Margin.Top} to {margin.Top}");
}
#endif
element.Margin = m;
if (start > 0 && animate)
{
ViewExtensions.CancelAnimations(element);
element.Animate("margin", top =>
{
element.TranslationY = top;
},
start, 0,
#if DEBUG
length: 500,
#else
length: 300,
#endif
easing: Easing.SinInOut,
finished: (v, r) =>
{
element.TranslationY = 0;
});
}
}
#endif
protected void Start(Action action)
{
if (Tap.IsBusy)
{
#if LOG
App.DebugPrint("gesture recognizer is now busy...");
#endif
return;
}
using (Tap.Start())
{
action();
}
}
private class Tap : IDisposable
{
public static bool IsBusy
{
get
{
lock (sync)
{
return _instance?.isBusy == true;
}
}
}
private static readonly object sync = new object();
private static readonly Tap _instance = new Tap();
private Tap() { }
public static Tap Start()
{
lock (sync)
{
_instance.isBusy = true;
}
return _instance;
}
private bool isBusy = false;
public void Dispose()
{
isBusy = false;
}
}
}
public class ThicknessEventArgs : EventArgs
{
public Thickness OldMargin { get; set; }
public Thickness NewMargin { get; set; }
}
}

8
Gallery/UI/BlurryPanel.cs Executable file
View File

@ -0,0 +1,8 @@
using Xamarin.Forms;
namespace Gallery.UI
{
public class BlurryPanel : ContentView
{
}
}

57
Gallery/UI/CardView.cs Executable file
View File

@ -0,0 +1,57 @@
using Gallery.Illust;
using Xamarin.Forms;
namespace Gallery.UI
{
public class CardView : ContentView
{
public static readonly BindableProperty CornerRadiusProperty = BindableProperty.Create(
nameof(CornerRadius), typeof(float), typeof(CardView));
public static readonly BindableProperty ShadowColorProperty = BindableProperty.Create(
nameof(ShadowColor), typeof(Color), typeof(CardView));
public static readonly BindableProperty ShadowRadiusProperty = BindableProperty.Create(
nameof(ShadowRadius), typeof(float), typeof(CardView), 3f);
public static readonly BindableProperty ShadowOffsetProperty = BindableProperty.Create(
nameof(ShadowOffset), typeof(Size), typeof(CardView));
public static readonly BindableProperty RankProperty = BindableProperty.Create(
nameof(Rank), typeof(int), typeof(CardView));
public float CornerRadius
{
get => (float)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
public Color ShadowColor
{
get => (Color)GetValue(ShadowColorProperty);
set => SetValue(ShadowColorProperty, value);
}
public float ShadowRadius
{
get => (float)GetValue(ShadowRadiusProperty);
set => SetValue(ShadowRadiusProperty, value);
}
public Size ShadowOffset
{
get => (Size)GetValue(ShadowOffsetProperty);
set => SetValue(ShadowOffsetProperty, value);
}
public int Rank
{
get => (int)GetValue(RankProperty);
set => SetValue(RankProperty, value);
}
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
if (BindingContext is IllustItem illust &&
illust.Width > 0 &&
illust.ImageHeight.IsAuto)
{
illust.ImageHeight = widthConstraint * illust.Height / illust.Width;
}
return base.OnMeasure(widthConstraint, heightConstraint);
}
}
}

68
Gallery/UI/CircleUIs.cs Executable file
View File

@ -0,0 +1,68 @@
using Xamarin.Forms;
namespace Gallery.UI
{
public class CircleImage : Image { }
public class RoundImage : Image
{
public static readonly BindableProperty CornerRadiusProperty = BindableProperty.Create(
nameof(CornerRadius), typeof(float), typeof(RoundImage));
public static readonly BindableProperty CornerMasksProperty = BindableProperty.Create(
nameof(CornerMasks), typeof(CornerMask), typeof(RoundImage));
public float CornerRadius
{
get => (float)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
public CornerMask CornerMasks
{
get => (CornerMask)GetValue(CornerMasksProperty);
set => SetValue(CornerMasksProperty, value);
}
}
public enum CornerMask
{
None = 0,
LeftTop = 1,
RightTop = 2,
LeftBottom = 4,
RightBottom = 8,
Top = LeftTop | RightTop, // 3
Left = LeftTop | LeftBottom, // 5
Slash = RightTop | LeftBottom, // 6
BackSlash = LeftTop | RightBottom, // 9
Right = RightTop | RightBottom, // 10
Bottom = LeftBottom | RightBottom, // 12
ExceptRightBottom = LeftTop | RightTop | LeftBottom, // 7
ExceptLeftBottom = LeftTop | RightTop | RightBottom, // 11
ExceptRightTop = LeftTop | LeftBottom | RightBottom, // 13
ExceptLeftTop = RightTop | LeftBottom | RightBottom, // 14
All = LeftTop | RightTop | LeftBottom | RightBottom // 15
}
public class RoundLabel : Label
{
public static readonly BindableProperty CornerRadiusProperty = BindableProperty.Create(
nameof(CornerRadius), typeof(float), typeof(RoundLabel));
public static new readonly BindableProperty BackgroundColorProperty = BindableProperty.Create(
nameof(BackgroundColor), typeof(Color), typeof(RoundLabel), Color.Transparent);
public float CornerRadius
{
get => (float)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
public new Color BackgroundColor
{
get => (Color)GetValue(BackgroundColorProperty);
set => SetValue(BackgroundColorProperty, value);
}
}
}

269
Gallery/UI/FlowLayout.cs Executable file
View File

@ -0,0 +1,269 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Xamarin.Forms;
namespace Gallery.UI
{
public class FlowLayout : Layout<View>
{
public static readonly BindableProperty ColumnProperty = BindableProperty.Create(
nameof(Column), typeof(int), typeof(FlowLayout), 2, propertyChanged: OnColumnPropertyChanged);
public static readonly BindableProperty RowSpacingProperty = BindableProperty.Create(
nameof(RowSpacing), typeof(double), typeof(FlowLayout), 10.0);
public static readonly BindableProperty ColumnSpacingProperty = BindableProperty.Create(
nameof(ColumnSpacing), typeof(double), typeof(FlowLayout), 10.0);
private static void OnColumnPropertyChanged(BindableObject obj, object oldValue, object newValue)
{
var flowLayout = (FlowLayout)obj;
if (oldValue is int column && column != flowLayout.Column)
{
flowLayout.UpdateChildrenLayout();
flowLayout.InvalidateLayout();
}
}
public int Column
{
get => (int)GetValue(ColumnProperty);
set => SetValue(ColumnProperty, value);
}
public double RowSpacing
{
get => (double)GetValue(RowSpacingProperty);
set => SetValue(RowSpacingProperty, value);
}
public double ColumnSpacing
{
get => (double)GetValue(ColumnSpacingProperty);
set => SetValue(ColumnSpacingProperty, value);
}
public event EventHandler<HeightEventArgs> MaxHeightChanged;
public double ColumnWidth { get; private set; }
private bool freezed;
private double maximumHeight;
private readonly Dictionary<View, Rectangle> cachedLayout = new Dictionary<View, Rectangle>();
protected override void LayoutChildren(double x, double y, double width, double height)
{
if (freezed)
{
return;
}
var column = Column;
if (column <= 0)
{
return;
}
var source = ItemsSource;
if (source == null || source.Count <= 0)
{
return;
}
var columnSpacing = ColumnSpacing;
var rowSpacing = RowSpacing;
var columnHeights = new double[column];
var columnSpacingTotal = columnSpacing * (column - 1);
var columnWidth = (width - columnSpacingTotal) / column;
ColumnWidth = columnWidth;
foreach (var item in Children)
{
var measured = item.Measure(columnWidth, height, MeasureFlags.IncludeMargins);
var col = 0;
for (var i = 1; i < column; i++)
{
if (columnHeights[i] < columnHeights[col])
{
col = i;
}
}
var rect = new Rectangle(
col * (columnWidth + columnSpacing),
columnHeights[col],
columnWidth,
measured.Request.Height);
if (cachedLayout.TryGetValue(item, out var v))
{
if (v != rect)
{
cachedLayout[item] = rect;
item.Layout(rect);
}
}
else
{
cachedLayout.Add(item, rect);
item.Layout(rect);
}
columnHeights[col] += measured.Request.Height + rowSpacing;
}
}
private double lastWidth = -1;
private SizeRequest lastSizeRequest;
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
var column = Column;
if (column <= 0)
{
return base.OnMeasure(widthConstraint, heightConstraint);
}
if (lastWidth == widthConstraint)
{
return lastSizeRequest;
}
lastWidth = widthConstraint;
var columnSpacing = ColumnSpacing;
var rowSpacing = RowSpacing;
var columnHeights = new double[column];
var columnSpacingTotal = columnSpacing * (column - 1);
var columnWidth = (widthConstraint - columnSpacingTotal) / column;
ColumnWidth = columnWidth;
foreach (var item in Children)
{
var measured = item.Measure(columnWidth, heightConstraint, MeasureFlags.IncludeMargins);
var col = 0;
for (var i = 1; i < column; i++)
{
if (columnHeights[i] < columnHeights[col])
{
col = i;
}
}
columnHeights[col] += measured.Request.Height + rowSpacing;
}
maximumHeight = columnHeights.Max();
if (maximumHeight > 0)
{
MaxHeightChanged?.Invoke(this, new HeightEventArgs { ContentHeight = maximumHeight });
}
lastSizeRequest = new SizeRequest(new Size(widthConstraint, maximumHeight));
return lastSizeRequest;
}
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
nameof(ItemTemplate), typeof(DataTemplate), typeof(FlowLayout));
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
nameof(ItemsSource), typeof(IList), typeof(FlowLayout), propertyChanged: OnItemsSourcePropertyChanged);
public DataTemplate ItemTemplate
{
get => (DataTemplate)GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
public IList ItemsSource
{
get => (IList)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
private static void OnItemsSourcePropertyChanged(BindableObject obj, object oldValue, object newValue)
{
var flowLayout = (FlowLayout)obj;
if (oldValue is IIllustCollectionChanged oldNotify)
{
oldNotify.CollectionChanged -= flowLayout.OnCollectionChanged;
}
flowLayout.lastWidth = -1;
if (newValue == null)
{
flowLayout.cachedLayout.Clear();
flowLayout.Children.Clear();
flowLayout.InvalidateLayout();
}
else if (newValue is IList newList)
{
flowLayout.freezed = true;
flowLayout.cachedLayout.Clear();
flowLayout.Children.Clear();
for (var i = 0; i < newList.Count; i++)
{
var child = flowLayout.ItemTemplate.CreateContent();
if (child is View view)
{
view.BindingContext = newList[i];
flowLayout.Children.Add(view);
}
}
if (newValue is IIllustCollectionChanged newNotify)
{
newNotify.CollectionChanged += flowLayout.OnCollectionChanged;
}
flowLayout.freezed = false;
flowLayout.UpdateChildrenLayout();
flowLayout.InvalidateLayout();
}
}
private void OnCollectionChanged(object sender, CollectionChangedEventArgs e)
{
lastWidth = -1;
if (e.OldItems != null)
{
freezed = true;
cachedLayout.Clear();
var index = e.OldStartingIndex;
for (var i = index + e.OldItems.Count - 1; i >= index; i--)
{
Children.RemoveAt(i);
}
freezed = false;
UpdateChildrenLayout();
InvalidateLayout();
}
if (e.NewItems == null)
{
return;
}
freezed = true;
var start = e.NewStartingIndex;
for (var i = 0; i < e.NewItems.Count; i++)
{
var child = ItemTemplate.CreateContent();
if (child is View view)
{
view.BindingContext = e.NewItems[i];
Children.Insert(start + i, view);
}
}
freezed = false;
UpdateChildrenLayout();
//InvalidateLayout();
}
}
public interface IIllustCollectionChanged
{
event EventHandler<CollectionChangedEventArgs> CollectionChanged;
}
public class CollectionChangedEventArgs : EventArgs
{
public int OldStartingIndex { get; set; }
public IList OldItems { get; set; }
public int NewStartingIndex { get; set; }
public IList NewItems { get; set; }
}
public class HeightEventArgs : EventArgs
{
public double ContentHeight { get; set; }
}
}

159
Gallery/UI/OptionCell.cs Executable file
View File

@ -0,0 +1,159 @@
using System.Collections;
using Gallery.UI.Theme;
using Gallery.Utils;
using Xamarin.Forms;
namespace Gallery.UI
{
public class OptionEntry : Entry { }
public class OptionPicker : Picker { }
public abstract class OptionCell : ViewCell
{
public static readonly BindableProperty TitleProperty = BindableProperty.Create(
nameof(Title), typeof(string), typeof(OptionCell));
public string Title
{
get => (string)GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
protected abstract View Content { get; }
public OptionCell()
{
View = new Grid
{
BindingContext = this,
Padding = new Thickness(20, 0),
ColumnDefinitions =
{
new ColumnDefinition { Width = new GridLength(.3, GridUnitType.Star) },
new ColumnDefinition { Width = new GridLength(.7, GridUnitType.Star) }
},
Children =
{
new Label
{
LineBreakMode = LineBreakMode.TailTruncation,
VerticalOptions = LayoutOptions.Center
}
.Binding(Label.TextProperty, nameof(Title))
.DynamicResource(Label.TextColorProperty, ThemeBase.TextColor),
Content.GridColumn(1)
}
}
.DynamicResource(VisualElement.BackgroundColorProperty, ThemeBase.OptionTintColor);
}
}
public class OptionTextCell : OptionCell
{
public static readonly BindableProperty DetailProperty = BindableProperty.Create(
nameof(Detail), typeof(string), typeof(OptionCell));
public string Detail
{
get => (string)GetValue(DetailProperty);
set => SetValue(DetailProperty, value);
}
protected override View Content => new Label
{
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.Center
}
.Binding(Label.TextProperty, nameof(Detail))
.DynamicResource(Label.TextColorProperty, ThemeBase.SubTextColor);
}
public class OptionSwitchCell : OptionCell
{
public static readonly BindableProperty IsToggledProperty = BindableProperty.Create(
nameof(IsToggled), typeof(bool), typeof(OptionSwitchCell));
public bool IsToggled
{
get => (bool)GetValue(IsToggledProperty);
set => SetValue(IsToggledProperty, value);
}
protected override View Content => new Switch
{
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.Center
}
.Binding(Switch.IsToggledProperty, nameof(IsToggled), BindingMode.TwoWay);
}
public class OptionDropCell : OptionCell
{
public static readonly BindableProperty ItemsProperty = BindableProperty.Create(
nameof(Items), typeof(IList), typeof(OptionDropCell));
public static readonly BindableProperty SelectedIndexProperty = BindableProperty.Create(
nameof(SelectedIndex), typeof(int), typeof(OptionDropCell));
public IList Items
{
get => (IList)GetValue(ItemsProperty);
set => SetValue(ItemsProperty, value);
}
public int SelectedIndex
{
get => (int)GetValue(SelectedIndexProperty);
set => SetValue(SelectedIndexProperty, value);
}
protected override View Content => new OptionPicker
{
HorizontalOptions = LayoutOptions.Fill,
VerticalOptions = LayoutOptions.Center
}
.Binding(Picker.ItemsSourceProperty, nameof(Items))
.Binding(Picker.SelectedIndexProperty, nameof(SelectedIndex), BindingMode.TwoWay)
.DynamicResource(Picker.TextColorProperty, ThemeBase.TextColor)
.DynamicResource(VisualElement.BackgroundColorProperty, ThemeBase.OptionTintColor);
}
public class OptionEntryCell : OptionCell
{
public static readonly BindableProperty TextProperty = BindableProperty.Create(
nameof(Text), typeof(string), typeof(OptionSwitchCell));
public static readonly BindableProperty KeyboardProperty = BindableProperty.Create(
nameof(Keyboard), typeof(Keyboard), typeof(OptionSwitchCell));
public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create(
nameof(Placeholder), typeof(string), typeof(OptionSwitchCell));
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public Keyboard Keyboard
{
get => (Keyboard)GetValue(KeyboardProperty);
set => SetValue(KeyboardProperty, value);
}
public string Placeholder
{
get => (string)GetValue(PlaceholderProperty);
set => SetValue(PlaceholderProperty, value);
}
protected override View Content => new OptionEntry
{
HorizontalOptions = LayoutOptions.Fill,
HorizontalTextAlignment = TextAlignment.End,
VerticalOptions = LayoutOptions.Center,
ReturnType = ReturnType.Next
}
.Binding(Entry.TextProperty, nameof(Text), BindingMode.TwoWay)
.Binding(InputView.KeyboardProperty, nameof(Keyboard))
.Binding(Entry.PlaceholderProperty, nameof(Placeholder))
.DynamicResource(Entry.TextColorProperty, ThemeBase.TextColor)
.DynamicResource(Entry.PlaceholderColorProperty, ThemeBase.SubTextColor)
.DynamicResource(VisualElement.BackgroundColorProperty, ThemeBase.OptionTintColor);
}
}

75
Gallery/UI/SegmentedControl.cs Executable file
View File

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using Xamarin.Forms;
namespace Gallery.UI
{
public class SegmentedControl : View, IViewContainer<SegmentedControlOption>
{
public IList<SegmentedControlOption> Children { get; set; }
public SegmentedControl()
{
Children = new List<SegmentedControlOption>();
}
public static readonly BindableProperty TintColorProperty = BindableProperty.Create(
nameof(TintColor), typeof(Color), typeof(SegmentedControl));
public static readonly BindableProperty DisabledColorProperty = BindableProperty.Create(
nameof(DisabledColor), typeof(Color), typeof(SegmentedControl));
public static readonly BindableProperty SelectedTextColorProperty = BindableProperty.Create(
nameof(SelectedTextColor), typeof(Color), typeof(SegmentedControl));
public static readonly BindableProperty SelectedSegmentIndexProperty = BindableProperty.Create(
nameof(SelectedSegmentIndex), typeof(int), typeof(SegmentedControl));
public Color TintColor
{
get => (Color)GetValue(TintColorProperty);
set => SetValue(TintColorProperty, value);
}
public Color DisabledColor
{
get => (Color)GetValue(DisabledColorProperty);
set => SetValue(DisabledColorProperty, value);
}
public Color SelectedTextColor
{
get => (Color)GetValue(SelectedTextColorProperty);
set => SetValue(SelectedTextColorProperty, value);
}
public int SelectedSegmentIndex
{
get => (int)GetValue(SelectedSegmentIndexProperty);
set => SetValue(SelectedSegmentIndexProperty, value);
}
public SegmentedControlOption SelectedSegment => Children[SelectedSegmentIndex];
public event EventHandler<ValueChangedEventArgs> ValueChanged;
//[EditorBrowsable(EditorBrowsableState.Never)]
public void SendValueChanged()
{
ValueChanged?.Invoke(this, new ValueChangedEventArgs { NewValue = SelectedSegmentIndex });
}
}
public class SegmentedControlOption : View
{
public static readonly BindableProperty TextProperty = BindableProperty.Create(
nameof(Text), typeof(string), typeof(SegmentedControlOption));
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public object Value { get; set; }
}
public class ValueChangedEventArgs : EventArgs
{
public int NewValue { get; set; }
}
}

View File

@ -0,0 +1,149 @@
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Gallery.UI
{
public static class StyleDefinition
{
public const double FontSizeTitle = 18.0;
public static readonly Thickness ScreenBottomPadding;
public static readonly Thickness TopOffset16 = new Thickness(0, 16, 0, 0);
public static readonly Thickness TopOffset32 = new Thickness(0, 32, 0, 0);
public static readonly Thickness TopOffset37 = new Thickness(0, 37, 0, 0);
public static readonly Color ColorLightShadow = Color.FromRgba(0, 0, 0, 0x20);
public static readonly Color ColorDeepShadow = Color.FromRgba(0, 0, 0, 0x50);
public static readonly Color ColorRedBackground = Color.FromRgb(0xfd, 0x43, 0x63);
public static readonly Color ColorDownloadBackground = Color.FromRgb(0xd7, 0xd9, 0xe0);
public static readonly ImageSource DownloadBackground = ImageSource.FromFile("download.png");
public static readonly ImageSource ProfileNone = ImageSource.FromFile("no_profile.png");
public static readonly double FontSizeMicro = Device.GetNamedSize(NamedSize.Micro, typeof(Label));
public static readonly double FontSizeSmall = Device.GetNamedSize(NamedSize.Small, typeof(Label));
#if __IOS__
public const string IconLightFontFamily = "FontAwesome5Pro-Light";
public const string IconRegularFontFamily = "FontAwesome5Pro-Regular";
public const string IconSolidFontFamily = "FontAwesome5Pro-Solid";
public const string IconLeft = "\uf104";
#elif __ANDROID__
public const string IconLightFontFamily = "fa-light-300.ttf#FontAwesome5Pro-Light";
public const string IconRegularFontFamily = "fa-regular-400.ttf#FontAwesome5Pro-Regular";
public const string IconSolidFontFamily = "fa-solid-900.ttf#FontAwesome5Pro-Solid";
public const string IconLeft = "\uf053";
#endif
public const string IconUser = "\uf007";
public const string IconSparkles = "\uf890";
public const string IconOrder = "\uf88f";
public const string IconLayer = "\uf302";
public const string IconRefresh = "\uf2f9";
public const string IconLove = "\uf004";
public const string IconCircleLove = "\uf4c7";
public const string IconOption = "\uf013";
public const string IconFavorite = "\uf02e";
public const string IconShare = "\uf1e0";
public const string IconCaretDown = "\uf0d7";
//public const string IconCaretUp = "\uf0d8";
public const string IconCircleCheck = "\uf058";
public const string IconPlay = "\uf04b";
public const string IconPause = "\uf04c";
public const string IconMore = "\uf142";
public const string IconCaretCircleLeft = "\uf32e";
public const string IconCaretCircleRight = "\uf330";
public const string IconCalendarDay = "\uf783";
public const string IconClose = "\uf057";
public const string IconCloudDownload = "\uf381";
static StyleDefinition()
{
_ = 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.Substring(6).Split(',');
if (vs.Length == 2 && int.TryParse(vs[0], out int main) && int.TryParse(vs[1], out int sub))
{
// iPhone X/XS/XR or iPhone 11
var flag = (main == 10 && sub >= 6) || (main > 10);
_isFullscreenDevice = flag;
_isBottomPadding = flag;
}
else
{
_isFullscreenDevice = false;
}
}
else if (model.StartsWith("iPad8,"))
{
// iPad 11-inch or 12.9-inch (3rd+)
//_isFullscreenDevice = true;
_isBottomPadding = true;
}
#if DEBUG
else
{
// iPad or 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)");
}
#endif
}
catch (System.Exception ex)
{
App.DebugError("device.get", $"failed to get the device model. {ex.Message}");
}
#else
// TODO:
_isFullscreenDevice = false;
_isBottomPadding = false;
#endif
if (_isFullscreenDevice == null)
{
_isFullscreenDevice = false;
}
return _isFullscreenDevice.Value;
}
}
}
}

43
Gallery/UI/Theme/DarkTheme.cs Executable file
View File

@ -0,0 +1,43 @@
using Gallery.Utils;
using Xamarin.Forms;
namespace Gallery.UI.Theme
{
public class DarkTheme : ThemeBase
{
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(WindowColor, Color.Black);
Add(TintColor, Color.FromRgb(0x94, 0x95, 0x9a));
Add(TextColor, Color.White);
Add(SubTextColor, Color.LightGray);
Add(CardBackgroundColor, Color.FromRgb(0x33, 0x33, 0x33));
Add(MaskColor, Color.FromRgba(0xff, 0xff, 0xff, 0x64));
Add(NavColor, Color.FromRgb(0x11, 0x11, 0x11));
Add(NavSelectedColor, Color.FromRgb(0x22, 0x22, 0x22));
Add(OptionBackColor, Color.Black);
Add(OptionTintColor, Color.FromRgb(0x11, 0x11, 0x11));
}
}
}

43
Gallery/UI/Theme/LightTheme.cs Executable file
View File

@ -0,0 +1,43 @@
using Gallery.Utils;
using Xamarin.Forms;
namespace Gallery.UI.Theme
{
public class LightTheme : ThemeBase
{
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(WindowColor, Color.White);
Add(TintColor, Color.FromRgb(0x87, 0x87, 0x8b)); // 0x7f, 0x99, 0xc6
Add(TextColor, Color.Black);
Add(SubTextColor, Color.DimGray);
Add(CardBackgroundColor, Color.FromRgb(0xf3, 0xf3, 0xf3));
Add(MaskColor, Color.FromRgba(0, 0, 0, 0x64));
Add(NavColor, Color.FromRgb(0xf0, 0xf0, 0xf0));
Add(NavSelectedColor, Color.LightGray);
Add(OptionBackColor, Color.FromRgb(0xf0, 0xf0, 0xf0));
Add(OptionTintColor, Color.White);
}
}
}

97
Gallery/UI/Theme/ThemeBase.cs Executable file
View File

@ -0,0 +1,97 @@
using Xamarin.Forms;
namespace Gallery.UI.Theme
{
public abstract class ThemeBase : ResourceDictionary
{
public const string FontIconUserFlyout = nameof(FontIconUserFlyout);
public const string FontIconSparklesFlyout = nameof(FontIconSparklesFlyout);
public const string FontIconOrderFlyout = nameof(FontIconOrderFlyout);
public const string FontIconFavoriteFlyout = nameof(FontIconFavoriteFlyout);
public const string FontIconRefresh = nameof(FontIconRefresh);
public const string FontIconLove = nameof(FontIconLove);
public const string FontIconNotLove = nameof(FontIconNotLove);
public const string FontIconCircleLove = nameof(FontIconCircleLove);
public const string FontIconOption = nameof(FontIconOption);
public const string FontIconShare = nameof(FontIconShare);
public const string FontIconMore = nameof(FontIconMore);
public const string FontIconCaretCircleLeft = nameof(FontIconCaretCircleLeft);
public const string FontIconCaretCircleRight = nameof(FontIconCaretCircleRight);
public const string FontIconCalendarDay = nameof(FontIconCalendarDay);
public const string FontIconCloudDownload = nameof(FontIconCloudDownload);
public const string IconCircleCheck = nameof(IconCircleCheck);
public const string IconCaretDown = nameof(IconCaretDown);
public const string IconClose = nameof(IconClose);
public const string StatusBarStyle = nameof(StatusBarStyle);
public const string WindowColor = nameof(WindowColor);
public const string TintColor = nameof(TintColor);
public const string TextColor = nameof(TextColor);
public const string SubTextColor = nameof(SubTextColor);
public const string CardBackgroundColor = nameof(CardBackgroundColor);
public const string MaskColor = nameof(MaskColor);
public const string NavColor = nameof(NavColor);
public const string NavSelectedColor = nameof(NavSelectedColor);
public const string OptionBackColor = nameof(OptionBackColor);
public const string OptionTintColor = nameof(OptionTintColor);
public const string IconLightFontFamily = nameof(IconLightFontFamily);
public const string IconRegularFontFamily = nameof(IconRegularFontFamily);
public const string IconSolidFontFamily = nameof(IconSolidFontFamily);
//public const string Horizon10 = nameof(Horizon10);
public const string ScreenBottomPadding = nameof(ScreenBottomPadding);
protected void InitResources()
{
//Add(Horizon10, StyleDefinition.Horizon10);
Add(ScreenBottomPadding, StyleDefinition.ScreenBottomPadding);
Add(IconLightFontFamily, StyleDefinition.IconLightFontFamily);
Add(IconRegularFontFamily, StyleDefinition.IconRegularFontFamily);
Add(IconSolidFontFamily, StyleDefinition.IconSolidFontFamily);
var regularFontFamily = StyleDefinition.IconRegularFontFamily;
var solidFontFamily = StyleDefinition.IconSolidFontFamily;
#if __IOS__
Add(FontIconUserFlyout, GetSolidIcon(StyleDefinition.IconUser, solidFontFamily));
Add(FontIconSparklesFlyout, GetSolidIcon(StyleDefinition.IconSparkles, solidFontFamily));
Add(FontIconOrderFlyout, GetSolidIcon(StyleDefinition.IconOrder, solidFontFamily));
Add(FontIconFavoriteFlyout, GetSolidIcon(StyleDefinition.IconFavorite, solidFontFamily));
Add(FontIconOption, GetSolidIcon(StyleDefinition.IconOption, solidFontFamily));
#elif __ANDROID__
Add(FontIconUserFlyout, ImageSource.FromFile("ic_user"));
Add(FontIconSparklesFlyout, ImageSource.FromFile("ic_sparkles"));
Add(FontIconOrderFlyout, ImageSource.FromFile("ic_rank"));
Add(FontIconFavoriteFlyout, ImageSource.FromFile("ic_bookmark"));
Add(FontIconOption, ImageSource.FromFile("ic_option"));
#endif
Add(FontIconLove, GetSolidIcon(StyleDefinition.IconLove, solidFontFamily, StyleDefinition.ColorRedBackground));
Add(FontIconCircleLove, GetSolidIcon(StyleDefinition.IconCircleLove, solidFontFamily, StyleDefinition.ColorRedBackground));
Add(FontIconRefresh, GetSolidIcon(StyleDefinition.IconRefresh, solidFontFamily));
Add(FontIconNotLove, GetSolidIcon(StyleDefinition.IconLove, regularFontFamily));
Add(FontIconShare, GetSolidIcon(StyleDefinition.IconShare, solidFontFamily));
Add(FontIconMore, GetSolidIcon(StyleDefinition.IconMore, regularFontFamily));
Add(FontIconCaretCircleLeft, GetSolidIcon(StyleDefinition.IconCaretCircleLeft, solidFontFamily));
Add(FontIconCaretCircleRight, GetSolidIcon(StyleDefinition.IconCaretCircleRight, solidFontFamily));
Add(FontIconCalendarDay, GetSolidIcon(StyleDefinition.IconCalendarDay, regularFontFamily));
Add(FontIconCloudDownload, GetSolidIcon(StyleDefinition.IconCloudDownload, solidFontFamily));
Add(IconCircleCheck, StyleDefinition.IconCircleCheck);
Add(IconCaretDown, StyleDefinition.IconCaretDown);
Add(IconClose, StyleDefinition.IconClose);
}
private FontImageSource GetSolidIcon(string icon, string family, Color color = default)
{
return new FontImageSource
{
FontFamily = family,
Glyph = icon,
Size = StyleDefinition.FontSizeTitle,
Color = color
};
}
}
}