Pixiview/Pixiview/Illust/IllustCollectionPage.cs

534 lines
19 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Newtonsoft.Json;
using Pixiview.Resources;
using Pixiview.UI;
using Pixiview.UI.Theme;
using Pixiview.Utils;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Pixiview.Illust
{
public abstract class FavoriteIllustCollectionPage : IllustCollectionPage<IllustItem[]>
{
public override void OnUnload() { }
}
public abstract class IllustDataCollectionPage : IllustCollectionPage<IllustData> { }
public abstract class IllustUserDataCollectionPage : IllustCollectionPage<IllustUserData> { }
public interface IIllustCollectionPage
{
IllustCollection IllustCollection { get; set; }
}
public abstract class IllustCollectionPage<T> : AdaptedPage, IIllustCollectionPage
{
#region - Properties -
public static readonly BindableProperty IllustsProperty = BindableProperty.Create(
nameof(Illusts), typeof(IllustCollection), typeof(IllustCollectionPage<T>));
public static readonly BindableProperty ColumnsProperty = BindableProperty.Create(
nameof(Columns), typeof(int), typeof(IllustCollectionPage<T>), 2);
public static readonly BindableProperty IsLoadingProperty = BindableProperty.Create(
nameof(IsLoading), typeof(bool), typeof(IllustCollectionPage<T>), true);
public IllustCollection Illusts
{
get => (IllustCollection)GetValue(IllustsProperty);
set => SetValue(IllustsProperty, value);
}
public int Columns
{
get => (int)GetValue(ColumnsProperty);
set => SetValue(ColumnsProperty, value);
}
public bool IsLoading
{
get => (bool)GetValue(IsLoadingProperty);
set => SetValue(IsLoadingProperty, value);
}
#endregion
protected static bool NeedUpdate { get; private set; } = true;
public IllustCollection IllustCollection { get; set; }
protected virtual bool IsFavoriteVisible => true;
protected Thickness totalBarOffset;
protected Thickness navigationBarOffset;
protected bool loaded;
private T illustData;
private readonly ParallelOptions parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = Configs.MaxThreads };
private readonly Command<IllustItem> commandIllustImageTapped;
public IllustCollectionPage()
{
commandIllustImageTapped = new Command<IllustItem>(IllustImageTapped);
BindingContext = this;
}
private void IllustImageTapped(IllustItem illust)
{
Start(() => OnIllustImageTapped(illust));
}
#region - Overrides -
public override void OnUnload()
{
Illusts = IllustCollection.Empty;
loaded = false;
}
protected override void OnAppearing()
{
base.OnAppearing();
Connectivity.ConnectivityChanged += Connectivity_ConnectivityChanged;
if (NeedUpdate || !loaded)
{
NeedUpdate = false;
loaded = false;
StartLoad();
}
else if (IsFavoriteVisible)
{
var favorites = Stores.Favorites;
foreach (var item in IllustCollection)
{
item.IsFavorite = favorites.Any(i => i.Id == item.Id);
}
}
}
protected override void OnDisappearing()
{
Connectivity.ConnectivityChanged -= Connectivity_ConnectivityChanged;
base.OnDisappearing();
}
private void Connectivity_ConnectivityChanged(object sender, ConnectivityChangedEventArgs e)
{
if (e.NetworkAccess == NetworkAccess.Internet)
{
if (!loaded)
{
StartLoad(true);
}
}
}
public override void OnOrientationChanged(Orientation orientation)
{
int columns;
switch (orientation)
{
case Orientation.Portrait:
columns = 2;
break;
case Orientation.PortraitUpsideDown:
columns = isPhone ? 4 : 2;
break;
case Orientation.Unknown:
case Orientation.LandscapeLeft:
case Orientation.LandscapeRight:
default:
columns = 4;
break;
}
if (Columns != columns)
{
App.DebugPrint($"change columns to {columns}");
Columns = columns;
}
base.OnOrientationChanged(orientation);
}
#endregion
protected abstract T DoLoadIllustData(bool force);
protected abstract IEnumerable<IllustItem> DoGetIllustList(T data, ICommand command);
protected virtual void OnIllustImageTapped(IllustItem illust)
{
var page = new ViewIllustPage(illust, this);
Navigation.PushAsync(page);
}
protected void StartLoad(bool force = false)
{
if (force || !loaded)
{
loaded = true;
IsLoading = true;
Task.Run(() => DoLoadIllusts(force));
}
}
protected DataTemplate GetCardViewTemplate(bool hideUser = false)
{
return new DataTemplate(() =>
{
#region - components -
// image
var image = new RoundImage
{
BackgroundColor = Color.LightGray,
CornerRadius = 10,
CornerMasks = CornerMask.Top,
HorizontalOptions = LayoutOptions.Fill,
Aspect = Aspect.AspectFill,
GestureRecognizers =
{
new TapGestureRecognizer()
.Binding(TapGestureRecognizer.CommandProperty, nameof(IllustItem.IllustTapped))
.Binding(TapGestureRecognizer.CommandParameterProperty, ".")
}
}
.Binding(Image.SourceProperty, nameof(IllustItem.Image));
// label: r-18
var r18 = new RoundLabel
{
Text = ResourceHelper.R18,
BackgroundColor = StyleDefinition.ColorRedBackground,
Margin = new Thickness(6, 6, 0, 0),
Padding = new Thickness(6, 2),
CornerRadius = 4,
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Start,
FontSize = StyleDefinition.FontSizeMicro,
TextColor = Color.White
}
.Binding(IsVisibleProperty, nameof(IllustItem.IsRestrict));
// label: pages
var pages = new RoundLabel
{
BackgroundColor = StyleDefinition.ColorDeepShadow,
Margin = new Thickness(0, 6, 6, 0),
Padding = new Thickness(6, 4),
CornerRadius = 6,
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.Start,
FontSize = StyleDefinition.FontSizeMicro,
TextColor = Color.White
}
.Binding(Label.TextProperty, nameof(IllustItem.PageCountText))
.Binding(IsVisibleProperty, nameof(IllustItem.IsPageVisible))
.DynamicResource(Label.FontFamilyProperty, ThemeBase.IconSolidFontFamily);
// label: title
var title = new Label
{
Padding = new Thickness(8, 2),
HorizontalOptions = LayoutOptions.FillAndExpand,
LineBreakMode = LineBreakMode.TailTruncation,
FontSize = StyleDefinition.FontSizeSmall
}
.Binding(Label.TextProperty, nameof(IllustItem.Title))
.DynamicResource(Label.TextColorProperty, ThemeBase.TextColor);
// label: favorite
var favorite = new Label
{
WidthRequest = 26,
HorizontalOptions = LayoutOptions.End,
HorizontalTextAlignment = TextAlignment.End,
VerticalOptions = LayoutOptions.Center,
FontSize = StyleDefinition.FontSizeSmall,
TextColor = StyleDefinition.ColorRedBackground,
Text = StyleDefinition.IconLove
}
.Binding(IsVisibleProperty, IsFavoriteVisible ? nameof(IllustItem.IsFavorite) : null)
.DynamicResource(Label.FontFamilyProperty, ThemeBase.IconSolidFontFamily);
#endregion
if (hideUser)
{
return new CardView
{
Padding = 0,
Margin = 0,
CornerRadius = 10,
ShadowColor = StyleDefinition.ColorLightShadow,
ShadowOffset = new Size(2, 2),
Content = new Grid
{
HorizontalOptions = LayoutOptions.Fill,
RowDefinitions =
{
new RowDefinition().Binding(RowDefinition.HeightProperty, nameof(IllustItem.ImageHeight)),
new RowDefinition { Height = GridLength.Auto }
},
Children =
{
image,
r18,
pages,
title,
// stacklayout: user
new StackLayout
{
Orientation = StackOrientation.Horizontal,
Padding = new Thickness(0, 0, 8, 0),
Children = { title, favorite }
}
.GridRow(1)
}
}
}
.DynamicResource(BackgroundColorProperty, ThemeBase.CardBackgroundColor);
}
return new CardView
{
Padding = 0,
Margin = 0,
CornerRadius = 10,
ShadowColor = StyleDefinition.ColorLightShadow,
ShadowOffset = new Size(2, 2),
Content = new Grid
{
HorizontalOptions = LayoutOptions.Fill,
RowDefinitions =
{
new RowDefinition().Binding(RowDefinition.HeightProperty, nameof(IllustItem.ImageHeight)),
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Auto }
},
Children =
{
image,
r18,
pages,
title.GridRow(1),
// stacklayout: user
new StackLayout
{
Orientation = StackOrientation.Horizontal,
Padding = new Thickness(8, 0, 8, 8),
Children =
{
// user icon
new CircleImage
{
WidthRequest = 30,
HeightRequest = 30,
Aspect = Aspect.AspectFill
}
.Binding(Image.SourceProperty, nameof(IllustItem.ProfileImage)),
// user name
new Label
{
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.Center,
LineBreakMode = LineBreakMode.TailTruncation,
FontSize = StyleDefinition.FontSizeMicro
}
.Binding(Label.TextProperty, nameof(IllustItem.UserName))
.DynamicResource(Label.TextColorProperty, ThemeBase.SubTextColor),
// label: favorite
favorite
}
}
.GridRow(2)
}
}
}
.DynamicResource(BackgroundColorProperty, ThemeBase.CardBackgroundColor);
});
}
#region - Illust Tasks -
protected void DoLoadIllusts(bool force = false)
{
illustData = DoLoadIllustData(force);
if (illustData == null)
{
//App.DebugError("illusts.load", "failed to load illusts data.");
IsLoading = false;
return;
}
if (force)
{
NeedUpdate = true;
}
var data = DoGetIllustList(illustData, commandIllustImageTapped).Where(i => i != null);
var collection = new IllustCollection(data);
if (IsFavoriteVisible)
{
var favorites = Stores.Favorites;
foreach (var item in collection)
{
item.IsFavorite = favorites.Any(i => i.Id == item.Id);
}
}
IllustCollection = collection;
Illusts = collection;
IsLoading = false;
DoLoadImages(collection);
}
void DoLoadImages(IllustCollection collection)
{
Parallel.ForEach(collection, parallelOptions, illust =>
{
if (!collection.Running)
{
return;
}
if (illust.ImageUrl != null)
{
var url = Configs.GetThumbnailUrl(illust.ImageUrl);
var image = Stores.LoadPreviewImage(url, false);
if (image == null)
{
image = Stores.LoadThumbnailImage(url);
}
if (image != null)
{
illust.Image = image;
}
}
if (illust.ProfileUrl != null)
{
var userImage = Stores.LoadUserProfileImage(illust.ProfileUrl);
if (userImage != null)
{
illust.ProfileImage = userImage;
}
}
});
}
#endregion
}
public class IllustCollection : List<IllustItem>
{
private static IllustCollection empty;
public static IllustCollection Empty
{
get
{
if (empty == null)
{
empty = new IllustCollection();
}
return empty;
}
}
public IllustCollection() : base()
{
running = true;
}
public IllustCollection(IEnumerable<IllustItem> illusts) : base(illusts)
{
running = true;
}
private readonly object sync = new object();
private volatile bool running;
public bool Running
{
get => running;
set
{
lock (sync)
{
running = value;
}
}
}
}
public class IllustFavorite
{
public DateTime LastFavoriteUtc { get; set; }
public List<IllustItem> Illusts { get; set; }
}
[JsonObject(MemberSerialization.OptIn)]
public class IllustItem : BindableObject
{
public static readonly BindableProperty ImageProperty = BindableProperty.Create(
nameof(Image), typeof(ImageSource), typeof(IllustItem));
public static readonly BindableProperty ProfileImageProperty = BindableProperty.Create(
nameof(ProfileImage), typeof(ImageSource), typeof(IllustItem));
public static readonly BindableProperty ImageHeightProperty = BindableProperty.Create(
nameof(ImageHeight), typeof(GridLength), typeof(IllustItem), GridLength.Auto);
public static readonly BindableProperty IsFavoriteProperty = BindableProperty.Create(
nameof(IsFavorite), typeof(bool), typeof(IllustItem));
public ImageSource Image
{
get => (ImageSource)GetValue(ImageProperty);
set => SetValue(ImageProperty, value);
}
public ImageSource ProfileImage
{
get => (ImageSource)GetValue(ProfileImageProperty);
set => SetValue(ProfileImageProperty, value);
}
public GridLength ImageHeight
{
get => (GridLength)GetValue(ImageHeightProperty);
set => SetValue(ImageHeightProperty, value);
}
public bool IsFavorite
{
get => (bool)GetValue(IsFavoriteProperty);
set => SetValue(IsFavoriteProperty, value);
}
public ICommand IllustTapped { get; set; }
[JsonProperty]
public string Id { get; set; }
[JsonProperty]
public string ImageUrl { get; set; }
[JsonProperty]
public string Title { get; set; }
[JsonProperty]
public bool IsRestrict { get; set; }
[JsonProperty]
public string ProfileUrl { get; set; }
[JsonProperty]
public string UserId { get; set; }
[JsonProperty]
public string UserName { get; set; }
[JsonProperty]
public int Width { get; set; }
[JsonProperty]
public int Height { get; set; }
[JsonProperty]
public int PageCount { get; set; }
[JsonProperty]
public double ImageHeightValue
{
get => ImageHeight.IsAuto ? -1 : ImageHeight.Value;
set => ImageHeight = value > 0 ? value : GridLength.Auto;
}
public string PageCountText => $"{StyleDefinition.IconLayer} {PageCount}";
public bool IsPageVisible => PageCount > 1;
}
}