diff --git a/Pixiview.iOS/Pixiview.iOS.csproj b/Pixiview.iOS/Pixiview.iOS.csproj index 61894a4..8b5ff94 100644 --- a/Pixiview.iOS/Pixiview.iOS.csproj +++ b/Pixiview.iOS/Pixiview.iOS.csproj @@ -78,6 +78,7 @@ <Compile Include="Services\FileStore.cs" /> <Compile Include="Renderers\AppShellRenderer.cs" /> <Compile Include="Renderers\AppShellSection\AppShellSectionRootHeader.cs" /> + <Compile Include="Renderers\SegmentedControlRenderer.cs" /> </ItemGroup> <ItemGroup> <InterfaceDefinition Include="Resources\LaunchScreen.storyboard" /> diff --git a/Pixiview.iOS/Renderers/AppShellRenderer.cs b/Pixiview.iOS/Renderers/AppShellRenderer.cs index ee384fc..904a200 100644 --- a/Pixiview.iOS/Renderers/AppShellRenderer.cs +++ b/Pixiview.iOS/Renderers/AppShellRenderer.cs @@ -35,7 +35,7 @@ namespace Pixiview.iOS.Renderers if (renderer.ViewController is UITabBarController controller) { var tabBar = controller.TabBar; - tabBar.TintColor = UIColor.SecondaryLabelColor.ColorWithAlpha(1); + tabBar.TintColor = UIColor.LabelColor; tabBar.UnselectedItemTintColor = UIColor.SecondaryLabelColor; } } diff --git a/Pixiview.iOS/Renderers/SegmentedControlRenderer.cs b/Pixiview.iOS/Renderers/SegmentedControlRenderer.cs new file mode 100644 index 0000000..c56a1ca --- /dev/null +++ b/Pixiview.iOS/Renderers/SegmentedControlRenderer.cs @@ -0,0 +1,155 @@ +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using Pixiview.iOS.Renderers; +using Pixiview.UI; +using UIKit; +using Xamarin.Forms; +using Xamarin.Forms.Platform.iOS; + +[assembly: ExportRenderer(typeof(SegmentedControl), typeof(SegmentedControlRenderer))] +namespace Pixiview.iOS.Renderers +{ + [SuppressMessage("Code Notifications", "XI0002:Notifies you from using newer Apple APIs when targeting an older OS version", Justification = "<Pending>")] + public class SegmentedControlRenderer : ViewRenderer<SegmentedControl, UISegmentedControl> + { + private UISegmentedControl nativeControl; + + protected override void OnElementChanged(ElementChangedEventArgs<SegmentedControl> e) + { + base.OnElementChanged(e); + + var element = Element; + if (Control == null && element != null) + { + nativeControl = new UISegmentedControl(); + + for (var i = 0; i < element.Children.Count; i++) + { + nativeControl.InsertSegment(element.Children[i].Text, i, false); + } + + nativeControl.Enabled = element.IsEnabled; + nativeControl.BackgroundColor = element.BackgroundColor.ToUIColor(); + nativeControl.SelectedSegmentTintColor = GetTintColor(element); + SetTextColor(); + nativeControl.SelectedSegment = element.SelectedSegmentIndex; + + SetNativeControl(nativeControl); + } + + if (e.OldElement != null) + { + if (nativeControl != null) + { + nativeControl.ValueChanged -= NativeControl_ValueChanged; + } + } + + if (e.NewElement != null) + { + nativeControl.ValueChanged += NativeControl_ValueChanged; + } + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + var element = Element; + if (nativeControl == null || element == null) + { + return; + } + + switch (e.PropertyName) + { + case "Renderer": + element.SendValueChanged(); + break; + + case nameof(element.BackgroundColor): + nativeControl.BackgroundColor = element.BackgroundColor.ToUIColor(); + break; + + case nameof(element.SelectedSegmentIndex): + nativeControl.SelectedSegment = element.SelectedSegmentIndex; + break; + + case nameof(element.TintColor): + nativeControl.SelectedSegmentTintColor = GetTintColor(element); + break; + + case nameof(element.IsEnabled): + nativeControl.Enabled = element.IsEnabled; + nativeControl.SelectedSegmentTintColor = GetTintColor(element); + break; + + case nameof(element.SelectedTextColor): + SetTextColor(); + break; + } + } + + private void SetTextColor() + { + var color = Element.SelectedTextColor; + UIColor c = color == default ? UIColor.LabelColor : color.ToUIColor(); + var attribute = new UITextAttributes + { + TextColor = c + }; + nativeControl.SetTitleTextAttributes(attribute, UIControlState.Selected); + attribute = new UITextAttributes + { + TextColor = c.ColorWithAlpha(.6f) + }; + nativeControl.SetTitleTextAttributes(attribute, UIControlState.Normal); + } + + private UIColor GetTintColor(SegmentedControl element) + { + if (element.IsEnabled) + { + var tintColor = element.TintColor; + if (tintColor == default) + { + return UIColor.QuaternaryLabelColor; + } + else + { + return tintColor.ToUIColor().ColorWithAlpha(.3f); + } + } + else + { + var disabledColor = element.DisabledColor; + if (disabledColor == default) + { + return UIColor.SecondaryLabelColor; + } + else + { + return disabledColor.ToUIColor(); + } + } + } + + private void NativeControl_ValueChanged(object sender, EventArgs e) + { + Element.SelectedSegmentIndex = (int)nativeControl.SelectedSegment; + } + + protected override void Dispose(bool disposing) + { + if (nativeControl != null) + { + nativeControl.ValueChanged -= NativeControl_ValueChanged; + nativeControl.Dispose(); + nativeControl = null; + } + + base.Dispose(disposing); + } + } +} diff --git a/Pixiview/AppShell.xaml b/Pixiview/AppShell.xaml index a59005e..e686802 100644 --- a/Pixiview/AppShell.xaml +++ b/Pixiview/AppShell.xaml @@ -67,18 +67,9 @@ <ShellContent ContentTemplate="{DataTemplate i:MainPage}"/> </Tab> <Tab Icon="{DynamicResource FontIconSparkles}" - Title="{r:Text Recommends}"> - <ShellContent Title="{r:Text Recommends}" - ContentTemplate="{DataTemplate i:RecommendsPage}" - Route="{x:Static util:Routes.Recommends}"/> - <ShellContent Title="{r:Text ByUser}" - Route="{x:Static util:Routes.ByUser}"> - <ShellContent.ContentTemplate> - <DataTemplate> - <i:RecommendsPage ByUser="True"/> - </DataTemplate> - </ShellContent.ContentTemplate> - </ShellContent> + Title="{r:Text Recommends}" + Route="{x:Static util:Routes.Recommends}"> + <ShellContent ContentTemplate="{DataTemplate i:RecommendsPage}"/> </Tab> <Tab Icon="{DynamicResource FontIconOrder}" Title="{r:Text Ranking}" diff --git a/Pixiview/Illust/IllustCollectionPage.cs b/Pixiview/Illust/IllustCollectionPage.cs index 41f9100..a0212ec 100644 --- a/Pixiview/Illust/IllustCollectionPage.cs +++ b/Pixiview/Illust/IllustCollectionPage.cs @@ -67,6 +67,8 @@ namespace Pixiview.Illust #endregion + protected Thickness totalBarOffset; + protected Thickness navigationBarOffset; protected bool loaded; private T illustData; @@ -297,7 +299,7 @@ namespace Pixiview.Illust #region - Illust Tasks - - void DoLoadIllusts(bool force = false) + protected void DoLoadIllusts(bool force = false, bool skipImage = false) { illustData = DoLoadIllustData(force); if (illustData == null) @@ -321,7 +323,10 @@ namespace Pixiview.Illust Illusts = collection; Loading = false; - DoLoadImages(collection); + if (!skipImage) + { + DoLoadImages(collection); + } } void DoLoadImages(IllustCollection collection) diff --git a/Pixiview/Illust/RankingPage.xaml b/Pixiview/Illust/RankingPage.xaml index 5c9369f..d3d1f4a 100644 --- a/Pixiview/Illust/RankingPage.xaml +++ b/Pixiview/Illust/RankingPage.xaml @@ -23,8 +23,7 @@ CancelButtonColor="{DynamicResource SubTextColor}" Text="{Binding Keywords, Mode=TwoWay}" SearchButtonPressed="SearchBar_SearchButtonPressed" - Unfocused="SearchBar_Unfocused" - /> + Unfocused="SearchBar_Unfocused"/> <Frame HasShadow="False" Margin="0" Padding="20" CornerRadius="8" IsVisible="{Binding Loading}" HorizontalOptions="Center" VerticalOptions="Center" diff --git a/Pixiview/Illust/RankingPage.xaml.cs b/Pixiview/Illust/RankingPage.xaml.cs index fb9848a..a652218 100644 --- a/Pixiview/Illust/RankingPage.xaml.cs +++ b/Pixiview/Illust/RankingPage.xaml.cs @@ -19,9 +19,6 @@ namespace Pixiview.Illust set => SetValue(KeywordsProperty, value); } - private readonly Thickness totalBarOffset; - private readonly Thickness navigationBarOffset; - public RankingPage() { totalBarOffset = new Thickness(0, AppShell.TotalBarOffset.Top + 50, 0, 0); diff --git a/Pixiview/Illust/RecommendsPage.xaml b/Pixiview/Illust/RecommendsPage.xaml index 8993efb..ec1ee17 100644 --- a/Pixiview/Illust/RecommendsPage.xaml +++ b/Pixiview/Illust/RecommendsPage.xaml @@ -11,13 +11,26 @@ <ToolbarItem Order="Primary" Clicked="Refresh_Clicked" IconImageSource="{DynamicResource FontIconRefresh}"/> </ContentPage.ToolbarItems> - <Grid> + <Grid Padding="{Binding PageTopMargin}"> <ScrollView HorizontalOptions="Fill"> <u:FlowLayout ItemsSource="{Binding Illusts}" HorizontalOptions="Fill" Column="{Binding Columns}" - Margin="16" RowSpacing="16" ColumnSpacing="16" + Margin="16, 6, 16, 16" RowSpacing="16" ColumnSpacing="16" ItemTemplate="{StaticResource cardView}"/> </ScrollView> + <Grid Margin="0, -40, 0, 0" VerticalOptions="Start" HeightRequest="40"> + <u:SegmentedControl VerticalOptions="Center" HorizontalOptions="Center" + HeightRequest="30" + BackgroundColor="{DynamicResource WindowColor}" + TintColor="{DynamicResource SubTextColor}" + SelectedTextColor="{DynamicResource TextColor}" + SelectedSegmentIndex="{Binding SegmentIndex, Mode=TwoWay}"> + <u:SegmentedControl.Children> + <u:SegmentedControlOption Text="{r:Text Recommends}"/> + <u:SegmentedControlOption Text="{r:Text ByUser}"/> + </u:SegmentedControl.Children> + </u:SegmentedControl> + </Grid> <Frame HasShadow="False" Margin="0" Padding="20" CornerRadius="8" IsVisible="{Binding Loading}" HorizontalOptions="Center" VerticalOptions="Center" diff --git a/Pixiview/Illust/RecommendsPage.xaml.cs b/Pixiview/Illust/RecommendsPage.xaml.cs index 3c2d2b2..7eca300 100644 --- a/Pixiview/Illust/RecommendsPage.xaml.cs +++ b/Pixiview/Illust/RecommendsPage.xaml.cs @@ -2,16 +2,36 @@ using System.Collections.Generic; using System.Linq; using System.Windows.Input; +using Pixiview.UI; using Pixiview.Utils; +using Xamarin.Forms; namespace Pixiview.Illust { public partial class RecommendsPage : IllustDataCollectionPage { - public bool ByUser { get; set; } + public static readonly BindableProperty SegmentIndexProperty = BindableProperty.Create( + nameof(SegmentIndex), typeof(int), typeof(RecommendsPage), propertyChanged: OnSegmentIndexPropertyChanged); + + private static void OnSegmentIndexPropertyChanged(BindableObject obj, object oldValue, object newValue) + { + var page = (RecommendsPage)obj; + page.DoLoadIllusts(); + } + + public int SegmentIndex + { + get => (int)GetValue(SegmentIndexProperty); + set => SetValue(SegmentIndexProperty, value); + } + + private IllustData illustData; public RecommendsPage() { + totalBarOffset = new Thickness(0, AppShell.TotalBarOffset.Top + 40, 0, 0); + navigationBarOffset = new Thickness(0, AppShell.NavigationBarOffset.Top + 40, 0, 0); + Resources.Add("cardView", GetCardViewTemplate()); InitializeComponent(); } @@ -27,10 +47,39 @@ namespace Pixiview.Illust loaded = false; } + public override void OnOrientationChanged(Orientation orientation) + { + int columns; + switch (orientation) + { + case Orientation.Portrait: + columns = 2; + PageTopMargin = totalBarOffset; + break; + case Orientation.PortraitUpsideDown: + columns = isPhone ? 4 : 2; + PageTopMargin = isPhone ? navigationBarOffset : totalBarOffset; + break; + case Orientation.Unknown: + case Orientation.LandscapeLeft: + case Orientation.LandscapeRight: + default: + columns = 4; + PageTopMargin = navigationBarOffset; + break; + } + if (Columns != columns) + { + App.DebugPrint($"ranking page, change columns to {columns}"); + Columns = columns; + } + } + protected override IEnumerable<IllustItem> DoGetIllustList(IllustData data, ICommand command) { - if (ByUser) + if (SegmentIndex == 1) { + // by user return data.body.page.recommendUser.SelectMany(i => i.illustIds) .Select(id => { @@ -44,6 +93,7 @@ namespace Pixiview.Illust } else { + // recommends return data.body.page.recommend.Select(id => { var item = data.body.thumbnails.illust.FirstOrDefault(l => l.illustId == id)?.ConvertToItem(); @@ -58,7 +108,12 @@ namespace Pixiview.Illust protected override IllustData DoLoadIllustData(bool force) { - return Stores.LoadIllustData(force); + if (illustData != null && !force) + { + return illustData; + } + illustData = Stores.LoadIllustData(force); + return illustData; } private void Refresh_Clicked(object sender, EventArgs e) diff --git a/Pixiview/Illust/ViewIllustPage.xaml.cs b/Pixiview/Illust/ViewIllustPage.xaml.cs index fac6e8b..5a743c5 100644 --- a/Pixiview/Illust/ViewIllustPage.xaml.cs +++ b/Pixiview/Illust/ViewIllustPage.xaml.cs @@ -187,14 +187,14 @@ namespace Pixiview.Illust item.OriginalUrl = p.urls.original; } - DoLoadImage(0); + DoLoadImage(0, true); if (items.Length > 1) { DoLoadImage(1); } } - private void DoLoadImage(int index) + private void DoLoadImage(int index, bool force = false) { var items = Illusts; if (index < 0 || index >= items.Length) @@ -204,10 +204,13 @@ namespace Pixiview.Illust } var item = items[index]; - if (item.Loading || (index > 0 && item.Image != null)) + if (!force) { - App.DebugPrint($"skipped, loading or already loaded, index: {index}, loading: {item.Loading}"); - return; + if (item.Loading || (index > 0 && item.Image != null)) + { + App.DebugPrint($"skipped, loading or already loaded, index: {index}, loading: {item.Loading}"); + return; + } } item.Loading = true; var image = Stores.LoadPreviewImage(item.PreviewUrl); diff --git a/Pixiview/UI/SegmentedControl.cs b/Pixiview/UI/SegmentedControl.cs new file mode 100644 index 0000000..c472618 --- /dev/null +++ b/Pixiview/UI/SegmentedControl.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using Xamarin.Forms; + +namespace Pixiview.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; } + } +}