1003 lines
35 KiB
C#
1003 lines
35 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
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 IllustDataCollectionPage : IllustCollectionPage<IllustData> { }
|
|
public abstract class IllustRankingDataCollectionPage : IllustScrollableCollectionPage<IllustRankingData> { }
|
|
public abstract class IllustRecommendsCollectionPage : IllustScrollableCollectionPage<IllustRecommendsData> { }
|
|
public abstract class IllustUserDataCollectionPage : IllustScrollableCollectionPage<IllustUserData> { }
|
|
public abstract class FavoriteIllustCollectionPage : IllustScrollableCollectionPage<IllustItem[]> { }
|
|
|
|
public interface IIllustCollectionPage
|
|
{
|
|
IllustCollection IllustCollection { get; set; }
|
|
}
|
|
public abstract class IllustCollectionPage<T> : AdaptedPage, IIllustCollectionPage
|
|
{
|
|
protected const double loadingOffset = -40;
|
|
|
|
#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 static readonly BindableProperty IsBottomLoadingProperty = BindableProperty.Create(
|
|
nameof(IsBottomLoading), typeof(bool), typeof(IllustCollectionPage<T>), false);
|
|
|
|
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);
|
|
}
|
|
public bool IsBottomLoading
|
|
{
|
|
get => (bool)GetValue(IsBottomLoadingProperty);
|
|
set => SetValue(IsBottomLoadingProperty, value);
|
|
}
|
|
|
|
#endregion
|
|
|
|
protected static DateTime LastUpdated { get; private set; } = DateTime.Now;
|
|
|
|
public IllustCollection IllustCollection { get; set; }
|
|
|
|
protected virtual bool IsFavoriteVisible => true;
|
|
protected virtual bool IsAutoReload => true;
|
|
protected virtual bool IsRedirectLogin => false;
|
|
protected virtual bool NeedCookie => false;
|
|
protected virtual ActivityIndicator LoadingIndicator => null;
|
|
protected virtual double IndicatorMarginTop => 16;
|
|
|
|
protected readonly Command<IllustItem> commandIllustImageTapped;
|
|
protected readonly Command<IIllustItem> commandUserTapped;
|
|
protected DateTime lastUpdated;
|
|
protected double topOffset;
|
|
protected string lastError;
|
|
|
|
private readonly object sync = new object();
|
|
private readonly Stack<ParallelTask> tasks;
|
|
private T illustData;
|
|
|
|
public IllustCollectionPage()
|
|
{
|
|
commandIllustImageTapped = new Command<IllustItem>(OnIllustImageTapped);
|
|
commandUserTapped = new Command<IIllustItem>(OnIllustUserItemTapped);
|
|
tasks = new Stack<ParallelTask>();
|
|
|
|
BindingContext = this;
|
|
}
|
|
|
|
private void OnIllustImageTapped(IllustItem illust)
|
|
{
|
|
if (illust == null)
|
|
{
|
|
return;
|
|
}
|
|
Start(async () =>
|
|
{
|
|
var page = new ViewIllustPage(illust);
|
|
await Navigation.PushAsync(page);
|
|
});
|
|
}
|
|
|
|
private void OnIllustUserItemTapped(IIllustItem item)
|
|
{
|
|
if (item == null)
|
|
{
|
|
return;
|
|
}
|
|
Start(async () =>
|
|
{
|
|
var page = new UserIllustPage(item);
|
|
await Navigation.PushAsync(page);
|
|
});
|
|
}
|
|
|
|
#region - Overrides -
|
|
|
|
public override void OnUnload()
|
|
{
|
|
lock (sync)
|
|
{
|
|
while (tasks.TryPop(out var task))
|
|
{
|
|
if (task != null)
|
|
{
|
|
task.Dispose();
|
|
}
|
|
}
|
|
}
|
|
InvalidateCollection();
|
|
Illusts = null;
|
|
lastUpdated = default;
|
|
}
|
|
|
|
protected override void OnAppearing()
|
|
{
|
|
base.OnAppearing();
|
|
|
|
if (lastUpdated == default || (IsAutoReload && lastUpdated != LastUpdated))
|
|
{
|
|
StartLoad();
|
|
}
|
|
else if (IllustCollection != null)
|
|
{
|
|
var favorites = Stores.Favorites;
|
|
foreach (var item in IllustCollection)
|
|
{
|
|
item.IsFavorite =
|
|
item.BookmarkId != null ||
|
|
favorites.Any(i => i.Id == item.Id);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if __IOS__
|
|
public override void OnOrientationChanged(bool landscape)
|
|
{
|
|
base.OnOrientationChanged(landscape);
|
|
if (StyleDefinition.IsFullscreenDevice)
|
|
{
|
|
topOffset = landscape ?
|
|
AppShell.NavigationBarOffset.Top :
|
|
AppShell.TotalBarOffset.Top;
|
|
}
|
|
else if (isPhone)
|
|
{
|
|
topOffset = landscape ?
|
|
StyleDefinition.TopOffset32.Top :
|
|
AppShell.TotalBarOffset.Top;
|
|
}
|
|
else
|
|
{
|
|
// iPad
|
|
topOffset = AppShell.TotalBarOffset.Top;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
protected override void OnSizeAllocated(double width, double height)
|
|
{
|
|
base.OnSizeAllocated(width, height);
|
|
int columns;
|
|
if (width > height)
|
|
{
|
|
columns = isPhone ? 4 : 6;
|
|
}
|
|
else
|
|
{
|
|
columns = isPhone ? 2 : 4;
|
|
}
|
|
if (Columns != columns)
|
|
{
|
|
Columns = columns;
|
|
#if DEBUG
|
|
App.DebugPrint($"change columns to {columns}");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
protected abstract T DoLoadIllustData(bool force);
|
|
protected abstract IEnumerable<IllustItem> DoGetIllustList(T data, out int tag);
|
|
|
|
protected virtual IllustCollection GetIllustsLoadedCollection(IllustCollection collection, bool bottom)
|
|
{
|
|
IllustCollection = collection;
|
|
return collection;
|
|
}
|
|
|
|
protected void InvalidateCollection()
|
|
{
|
|
var collection = IllustCollection;
|
|
if (collection != null)
|
|
{
|
|
collection.Running = false;
|
|
IllustCollection = null;
|
|
}
|
|
}
|
|
|
|
protected virtual void StartLoad(bool force = false, bool isBottom = false)
|
|
{
|
|
if (force || lastUpdated != LastUpdated)
|
|
{
|
|
lastUpdated = LastUpdated;
|
|
|
|
var indicator = LoadingIndicator;
|
|
if (indicator == null || isBottom)
|
|
{
|
|
if (isBottom)
|
|
{
|
|
IsBottomLoading = true;
|
|
}
|
|
else
|
|
{
|
|
InvalidateCollection();
|
|
IsLoading = true;
|
|
}
|
|
#if __IOS__
|
|
Device.StartTimer(TimeSpan.FromMilliseconds(48), () =>
|
|
#else
|
|
Device.StartTimer(TimeSpan.FromMilliseconds(150), () =>
|
|
#endif
|
|
{
|
|
Task.Run(() => DoLoadIllusts(force, isBottom));
|
|
return false;
|
|
});
|
|
}
|
|
else
|
|
{
|
|
InvalidateCollection();
|
|
IsLoading = true;
|
|
|
|
var offset = 16 - IndicatorMarginTop;
|
|
indicator.Animate("margin", top =>
|
|
{
|
|
indicator.Margin = new Thickness(0, top, 0, offset);
|
|
},
|
|
loadingOffset - offset, 16 - offset, easing: Easing.CubicOut, finished: (v, r) =>
|
|
{
|
|
_ = Task.Run(() => DoLoadIllusts(force, isBottom));
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual void DoIllustsLoaded(IllustCollection collection, bool bottom)
|
|
{
|
|
collection = GetIllustsLoadedCollection(collection, bottom);
|
|
|
|
var indicator = LoadingIndicator;
|
|
if (indicator == null || bottom)
|
|
{
|
|
IsLoading = false;
|
|
IsBottomLoading = false;
|
|
#if __IOS__
|
|
Device.StartTimer(TimeSpan.FromMilliseconds(48), () =>
|
|
#else
|
|
Device.StartTimer(TimeSpan.FromMilliseconds(150), () =>
|
|
#endif
|
|
{
|
|
Illusts = collection;
|
|
return false;
|
|
});
|
|
}
|
|
else
|
|
{
|
|
var offset = 16 - IndicatorMarginTop;
|
|
indicator.Animate("margin", top =>
|
|
{
|
|
indicator.Margin = new Thickness(0, top, 0, offset);
|
|
},
|
|
16 - offset, loadingOffset - offset, easing: Easing.CubicIn, finished: (v, r) =>
|
|
{
|
|
indicator.Margin = new Thickness(0, v, 0, offset);
|
|
IsLoading = false;
|
|
IsBottomLoading = false;
|
|
#if __IOS__
|
|
Device.StartTimer(TimeSpan.FromMilliseconds(48), () =>
|
|
#else
|
|
Device.StartTimer(TimeSpan.FromMilliseconds(150), () =>
|
|
#endif
|
|
{
|
|
Illusts = collection;
|
|
return false;
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
protected async Task ScrollToTopAsync(ScrollView scrollView)
|
|
{
|
|
if (scrollView.ScrollY > -topOffset)
|
|
{
|
|
#if __IOS__
|
|
await scrollView.ScrollToAsync(scrollView.ScrollX, -topOffset, true);
|
|
#else
|
|
await scrollView.ScrollToAsync(0, -topOffset, false);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
protected DataTemplate GetCardViewTemplate(bool hideUser = false, string titleBinding = null)
|
|
{
|
|
return new DataTemplate(() =>
|
|
{
|
|
#region - components -
|
|
|
|
// image
|
|
var image = new RoundImage
|
|
{
|
|
BackgroundColor = StyleDefinition.ColorDownloadBackground,
|
|
CornerRadius = 10,
|
|
CornerMasks = CornerMask.Top,
|
|
HorizontalOptions = LayoutOptions.Fill,
|
|
Aspect = Aspect.AspectFill,
|
|
GestureRecognizers =
|
|
{
|
|
new TapGestureRecognizer
|
|
{
|
|
Command = commandIllustImageTapped
|
|
}
|
|
.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: is anime
|
|
var anime = new RoundLabel
|
|
{
|
|
Text = StyleDefinition.IconPlay,
|
|
BackgroundColor = StyleDefinition.ColorDeepShadow,
|
|
Margin = new Thickness(0, 0, 6, 6),
|
|
Padding = new Thickness(12, 9, 0, 0),
|
|
WidthRequest = 36,
|
|
HeightRequest = 36,
|
|
CornerRadius = 18,
|
|
HorizontalOptions = LayoutOptions.End,
|
|
VerticalOptions = LayoutOptions.End,
|
|
FontSize = StyleDefinition.FontSizeTitle,
|
|
TextColor = Color.White
|
|
}
|
|
.Binding(IsVisibleProperty, nameof(IllustItem.IsAnimeVisible))
|
|
.DynamicResource(Label.FontFamilyProperty, ThemeBase.IconSolidFontFamily);
|
|
|
|
// label: title
|
|
var title = new Label
|
|
{
|
|
Padding = new Thickness(8, 2),
|
|
HorizontalOptions = LayoutOptions.FillAndExpand,
|
|
VerticalOptions = LayoutOptions.Center,
|
|
LineBreakMode = LineBreakMode.TailTruncation,
|
|
FontSize = StyleDefinition.FontSizeSmall
|
|
}.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,
|
|
IsVisible = false
|
|
}
|
|
.Binding(Label.TextProperty, nameof(IllustItem.BookmarkId), converter: new FavoriteIconConverter(IsFavoriteVisible))
|
|
.Binding(IsVisibleProperty, nameof(IllustItem.IsFavorite))
|
|
.DynamicResource(Label.FontFamilyProperty, ThemeBase.IconSolidFontFamily);
|
|
|
|
#endregion
|
|
|
|
if (hideUser)
|
|
{
|
|
return new CardView
|
|
{
|
|
Padding = 0,
|
|
Margin = 0,
|
|
CornerRadius = 10,
|
|
ShadowColor = StyleDefinition.ColorLightShadow,
|
|
ShadowOffset = new Size(1, 1),
|
|
Content = new Grid
|
|
{
|
|
HorizontalOptions = LayoutOptions.Fill,
|
|
RowSpacing = 0,
|
|
RowDefinitions =
|
|
{
|
|
new RowDefinition().Binding(RowDefinition.HeightProperty, nameof(IllustItem.ImageHeight)),
|
|
new RowDefinition { Height = 30 }
|
|
},
|
|
Children =
|
|
{
|
|
image,
|
|
r18,
|
|
pages,
|
|
anime,
|
|
|
|
// stacklayout: title
|
|
new Grid
|
|
{
|
|
ColumnDefinitions =
|
|
{
|
|
new ColumnDefinition(),
|
|
new ColumnDefinition { Width = 20 }
|
|
},
|
|
VerticalOptions = LayoutOptions.Center,
|
|
Padding = new Thickness(0, 0, 8, 0),
|
|
Children =
|
|
{
|
|
title.Binding(Label.TextProperty, nameof(IllustItem.Title)),
|
|
favorite.GridColumn(1)
|
|
}
|
|
}
|
|
.GridRow(1)
|
|
}
|
|
}
|
|
}
|
|
.DynamicResource(BackgroundColorProperty, ThemeBase.CardBackgroundColor);
|
|
}
|
|
|
|
return new CardView
|
|
{
|
|
Padding = 0,
|
|
Margin = 0,
|
|
CornerRadius = 10,
|
|
ShadowColor = StyleDefinition.ColorLightShadow,
|
|
ShadowOffset = new Size(1, 1),
|
|
Content = new Grid
|
|
{
|
|
HorizontalOptions = LayoutOptions.Fill,
|
|
RowSpacing = 0,
|
|
RowDefinitions =
|
|
{
|
|
new RowDefinition().Binding(RowDefinition.HeightProperty, nameof(IllustItem.ImageHeight)),
|
|
new RowDefinition { Height = 30 },
|
|
new RowDefinition { Height = 40 }
|
|
},
|
|
Children =
|
|
{
|
|
image,
|
|
r18,
|
|
pages,
|
|
anime,
|
|
|
|
title.Binding(Label.TextProperty, titleBinding ?? nameof(IllustItem.Title)).GridRow(1),
|
|
|
|
// stacklayout: user
|
|
new Grid
|
|
{
|
|
ColumnDefinitions =
|
|
{
|
|
new ColumnDefinition { Width = 30 },
|
|
new ColumnDefinition(),
|
|
new ColumnDefinition { Width = 20 }
|
|
},
|
|
Padding = new Thickness(8, 0, 8, 8),
|
|
Children =
|
|
{
|
|
// user icon
|
|
new CircleImage
|
|
{
|
|
WidthRequest = 30,
|
|
HeightRequest = 30,
|
|
Aspect = Aspect.AspectFill
|
|
}
|
|
.Binding(Image.SourceProperty, nameof(IIllustItem.ProfileImage)),
|
|
|
|
// user name
|
|
new Label
|
|
{
|
|
HorizontalOptions = LayoutOptions.FillAndExpand,
|
|
VerticalOptions = LayoutOptions.Center,
|
|
LineBreakMode = LineBreakMode.TailTruncation,
|
|
FontSize = StyleDefinition.FontSizeMicro
|
|
}
|
|
.Binding(Label.TextProperty, nameof(IIllustItem.UserName))
|
|
.DynamicResource(Label.TextColorProperty, ThemeBase.SubTextColor)
|
|
.GridColumn(1),
|
|
|
|
// label: favorite
|
|
favorite.GridColumn(2)
|
|
},
|
|
GestureRecognizers =
|
|
{
|
|
new TapGestureRecognizer
|
|
{
|
|
Command = commandUserTapped
|
|
}
|
|
.Binding(TapGestureRecognizer.CommandParameterProperty, ".")
|
|
}
|
|
}
|
|
.GridRow(2)
|
|
}
|
|
}
|
|
}
|
|
.DynamicResource(BackgroundColorProperty, ThemeBase.CardBackgroundColor);
|
|
});
|
|
}
|
|
|
|
#region - Illust Tasks -
|
|
|
|
private async void RedirectFailed()
|
|
{
|
|
if (!IsRedirectLogin)
|
|
{
|
|
string extra;
|
|
if (lastError != null)
|
|
{
|
|
if (lastError.Length > 40)
|
|
{
|
|
extra = $"\n{lastError.Substring(0, 40)}...";
|
|
}
|
|
else
|
|
{
|
|
extra = $"\n{lastError}";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
extra = string.Empty;
|
|
}
|
|
await DisplayAlert(ResourceHelper.Title,
|
|
ResourceHelper.FailedResponse + extra,
|
|
ResourceHelper.Ok);
|
|
}
|
|
else
|
|
{
|
|
var result = await DisplayAlert(
|
|
ResourceHelper.Title,
|
|
ResourceHelper.ConfirmLogin,
|
|
ResourceHelper.Yes, ResourceHelper.No);
|
|
if (result)
|
|
{
|
|
AppShell.Current.PushToLogin(() =>
|
|
{
|
|
Task.Run(() => AppShell.Current.DoLoginInformation(true));
|
|
StartLoad(true);
|
|
});
|
|
}
|
|
}
|
|
IsLoading = false;
|
|
IsBottomLoading = false;
|
|
}
|
|
|
|
protected void DoLoadIllusts(bool force = false, bool bottom = false)
|
|
{
|
|
#if DEBUG
|
|
App.DebugPrint($"start loading data, force: {force}");
|
|
#endif
|
|
if (force && NeedCookie && string.IsNullOrEmpty(Configs.Cookie))
|
|
{
|
|
MainThread.BeginInvokeOnMainThread(RedirectFailed);
|
|
App.DebugPrint($"no cookie found");
|
|
return;
|
|
}
|
|
illustData = DoLoadIllustData(force);
|
|
if (illustData == null)
|
|
{
|
|
MainThread.BeginInvokeOnMainThread(RedirectFailed);
|
|
//App.DebugError("illusts.load", "failed to load illusts data.");
|
|
return;
|
|
}
|
|
if (force && IsFavoriteVisible)
|
|
{
|
|
var now = DateTime.Now;
|
|
LastUpdated = now;
|
|
lastUpdated = now;
|
|
}
|
|
|
|
var r18 = Configs.IsOnR18;
|
|
var data = DoGetIllustList(illustData, out int tag).Where(i => i != null && (r18 || !i.IsRestrict));
|
|
|
|
var collection = new IllustCollection(data);
|
|
var favorites = Stores.Favorites;
|
|
foreach (var item in collection)
|
|
{
|
|
if (item.Image == null)
|
|
{
|
|
string url;
|
|
try
|
|
{
|
|
url = Configs.GetThumbnailUrl(item.ImageUrl);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
App.DebugError("image.getthumbnail", $"{item.ImageUrl}, {ex}");
|
|
continue;
|
|
}
|
|
var image = Stores.LoadPreviewImage(url, false);
|
|
if (image == null)
|
|
{
|
|
image = Stores.LoadThumbnailImage(url, false);
|
|
}
|
|
if (image != null)
|
|
{
|
|
item.Image = image;
|
|
}
|
|
item.ImageUrl = url;
|
|
}
|
|
if (item.ProfileImage == null)
|
|
{
|
|
if (item.ProfileUrl == null)
|
|
{
|
|
item.ProfileImage = StyleDefinition.ProfileNone;
|
|
}
|
|
else
|
|
{
|
|
var image = Stores.LoadUserProfileImage(item.ProfileUrl, false);
|
|
if (image != null)
|
|
{
|
|
item.ProfileImage = image;
|
|
}
|
|
}
|
|
}
|
|
item.IsFavorite =
|
|
item.BookmarkId != null ||
|
|
favorites.Any(i => i.Id == item.Id);
|
|
}
|
|
DoIllustsLoaded(collection, bottom);
|
|
|
|
DoLoadImages(collection, tag);
|
|
}
|
|
|
|
private void DoLoadImages(IllustCollection collection, int tag)
|
|
{
|
|
lock (sync)
|
|
{
|
|
if (tasks.TryPeek(out var peek))
|
|
{
|
|
if (peek.TagIndex >= tag)
|
|
{
|
|
App.DebugPrint($"tasks expired ({tasks.Count}), peek: {peek.TagIndex}, now: {tag}, will be disposing.");
|
|
while (tasks.TryPop(out var t))
|
|
{
|
|
if (t != null)
|
|
{
|
|
t.Dispose();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var list = collection.Where(i => i.Image == null || i.ProfileImage == null).ToArray();
|
|
|
|
var task = ParallelTask.Start("collection.load", 0, list.Length, Configs.MaxThreads, i =>
|
|
{
|
|
if (!collection.Running)
|
|
{
|
|
return false;
|
|
}
|
|
var illust = list[i];
|
|
|
|
if (illust.Image == null && illust.ImageUrl != null)
|
|
{
|
|
illust.Image = StyleDefinition.DownloadBackground;
|
|
var image = Stores.LoadThumbnailImage(illust.ImageUrl, true, force: true);
|
|
if (image != null)
|
|
{
|
|
illust.Image = image;
|
|
}
|
|
}
|
|
|
|
if (illust.ProfileImage == null && illust.ProfileUrl != null)
|
|
{
|
|
illust.ProfileImage = StyleDefinition.ProfileNone;
|
|
var userImage = Stores.LoadUserProfileImage(illust.ProfileUrl, true, force: true);
|
|
if (userImage != null)
|
|
{
|
|
illust.ProfileImage = userImage;
|
|
}
|
|
}
|
|
return true;
|
|
}, tagIndex: tag);
|
|
|
|
if (task != null)
|
|
{
|
|
lock (sync)
|
|
{
|
|
tasks.Push(task);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
public abstract class IllustScrollableCollectionPage<T> : IllustCollectionPage<T>
|
|
{
|
|
protected const int SCROLL_OFFSET = 33;
|
|
protected ScrollDirection scrollDirection = ScrollDirection.Stop;
|
|
protected double lastScrollY = double.MinValue;
|
|
|
|
private double lastRefreshY = double.MinValue;
|
|
private double offset;
|
|
|
|
protected bool IsScrollingDown(double y)
|
|
{
|
|
if (y > lastScrollY)
|
|
{
|
|
if (scrollDirection != ScrollDirection.Down)
|
|
{
|
|
scrollDirection = ScrollDirection.Down;
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (scrollDirection != ScrollDirection.Up)
|
|
{
|
|
scrollDirection = ScrollDirection.Up;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
protected void SetOffset(double off)
|
|
{
|
|
offset = off;
|
|
}
|
|
|
|
protected abstract bool CheckRefresh();
|
|
|
|
protected override void StartLoad(bool force = false, bool isBottom = false)
|
|
{
|
|
if (!isBottom)
|
|
{
|
|
lastRefreshY = double.MinValue;
|
|
}
|
|
base.StartLoad(force, isBottom);
|
|
}
|
|
|
|
protected override IllustCollection GetIllustsLoadedCollection(IllustCollection collection, bool bottom)
|
|
{
|
|
var now = IllustCollection;
|
|
if (now == null)
|
|
{
|
|
now = collection;
|
|
IllustCollection = now;
|
|
}
|
|
else
|
|
{
|
|
//now = new IllustCollection(now.Concat(collection));
|
|
now.AddRange(collection);
|
|
}
|
|
return now;
|
|
}
|
|
|
|
protected void OnScrolled(double y)
|
|
{
|
|
lastScrollY = y;
|
|
|
|
if (scrollDirection == ScrollDirection.Up)
|
|
{
|
|
return;
|
|
}
|
|
if (y > 0 && offset > 0 && y - topOffset > offset)
|
|
{
|
|
if (IsLoading || IsBottomLoading)
|
|
{
|
|
return;
|
|
}
|
|
if (y - lastRefreshY > 200)
|
|
{
|
|
if (CheckRefresh())
|
|
{
|
|
lastRefreshY = y;
|
|
#if DEBUG
|
|
App.DebugPrint("start to load next page");
|
|
#endif
|
|
StartLoad(true, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum ScrollDirection
|
|
{
|
|
Stop,
|
|
Up,
|
|
Down
|
|
}
|
|
|
|
public class IllustCollection : List<IllustItem>, IIllustCollectionChanged
|
|
{
|
|
private static IllustCollection empty;
|
|
|
|
public static IllustCollection Empty
|
|
{
|
|
get
|
|
{
|
|
if (empty == null)
|
|
{
|
|
empty = new IllustCollection();
|
|
}
|
|
return empty;
|
|
}
|
|
}
|
|
|
|
public event EventHandler<CollectionChangedEventArgs> CollectionChanged;
|
|
|
|
public IllustCollection() : base()
|
|
{
|
|
Running = true;
|
|
}
|
|
public IllustCollection(IEnumerable<IllustItem> illusts) : base(illusts)
|
|
{
|
|
Running = true;
|
|
}
|
|
|
|
public void AddRange(List<IllustItem> items)
|
|
{
|
|
var e = new CollectionChangedEventArgs
|
|
{
|
|
NewStartingIndex = Count,
|
|
NewItems = items
|
|
};
|
|
base.AddRange(items);
|
|
if (MainThread.IsMainThread)
|
|
{
|
|
CollectionChanged?.Invoke(this, e);
|
|
}
|
|
else
|
|
{
|
|
MainThread.BeginInvokeOnMainThread(() =>
|
|
CollectionChanged?.Invoke(this, e));
|
|
}
|
|
}
|
|
|
|
public bool Running { get; set; }
|
|
}
|
|
|
|
public enum IllustType
|
|
{
|
|
Illust = 0,
|
|
Manga = 1,
|
|
Anime = 2
|
|
}
|
|
|
|
public interface IIllustItem
|
|
{
|
|
string UserId { get; }
|
|
string UserName { get; }
|
|
ImageSource ProfileImage { get; }
|
|
}
|
|
|
|
[JsonObject(MemberSerialization.OptIn)]
|
|
public class IllustItem : BindableObject, IIllustItem
|
|
{
|
|
private static IllustItem empty;
|
|
public static IllustItem Empty
|
|
{
|
|
get
|
|
{
|
|
if (empty == null)
|
|
{
|
|
empty = new IllustItem();
|
|
}
|
|
return empty;
|
|
}
|
|
}
|
|
|
|
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 static readonly BindableProperty BookmarkIdProperty = BindableProperty.Create(
|
|
nameof(BookmarkId), typeof(string), typeof(IllustItem));
|
|
|
|
[JsonProperty]
|
|
public string Title { get; set; }
|
|
public int Rank { get; set; }
|
|
public string RankTitle => Rank > 0 ? $"#{Rank} {Title}" : Title;
|
|
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 bool IsPlaying { get; set; }
|
|
|
|
[JsonProperty]
|
|
public string Id { get; set; }
|
|
[JsonProperty]
|
|
public string BookmarkId
|
|
{
|
|
get => (string)GetValue(BookmarkIdProperty);
|
|
set => SetValue(BookmarkIdProperty, value);
|
|
}
|
|
[JsonProperty]
|
|
public DateTime FavoriteDateUtc { get; set; }
|
|
[JsonProperty]
|
|
public string ImageUrl { get; set; }
|
|
[JsonProperty]
|
|
public bool IsRestrict { get; set; }
|
|
[JsonProperty]
|
|
public string[] Tags { 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;
|
|
}
|
|
[JsonProperty]
|
|
public int YesRank { get; set; }
|
|
[JsonProperty]
|
|
public int RatingCount { get; set; }
|
|
[JsonProperty]
|
|
public int ViewCount { get; set; }
|
|
[JsonProperty]
|
|
public long UploadTimestamp { get; set; }
|
|
[JsonProperty]
|
|
public IllustType IllustType { get; set; }
|
|
|
|
public string PageCountText => $"{StyleDefinition.IconLayer} {PageCount}";
|
|
public bool IsPageVisible => PageCount > 1;
|
|
public bool IsAnimeVisible => IllustType == IllustType.Anime;
|
|
}
|
|
}
|