Pixiview/Pixiview/Illust/FavoritesPage.xaml.cs

464 lines
15 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Pixiview.Resources;
using Pixiview.UI;
using Pixiview.Utils;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Pixiview.Illust
{
public partial class FavoritesPage : FavoriteIllustCollectionPage
{
private const int STEP = 20;
public static readonly BindableProperty SegmentTypeProperty = BindableProperty.Create(
nameof(SegmentType), typeof(int), typeof(FavoritesPage), propertyChanged: OnSegmentTypePropertyChanged);
private static void OnSegmentTypePropertyChanged(BindableObject obj, object old, object @new)
{
var page = (FavoritesPage)obj;
MainThread.BeginInvokeOnMainThread(page.ChangeFilter);
}
public int SegmentType
{
get => (int)GetValue(SegmentTypeProperty);
set => SetValue(SegmentTypeProperty, value);
}
private bool isFilterVisible;
private int startIndex;
private int nextIndex;
private bool flag = false;
private ParallelTask task;
public FavoritesPage()
{
Resources.Add("cardView", GetCardViewTemplate());
InitializeComponent();
gridFilter.TranslationY = -60;
panelFilter.TranslationY = -60;
SegmentType = Preferences.Get(Configs.FavoriteTypeKey, 0);
startIndex = -1;
nextIndex = 0;
}
protected override bool IsFavoriteVisible => false;
protected override ActivityIndicator LoadingIndicator => activityLoading;
protected override void OnAppearing()
{
if (lastUpdated != LastUpdated)
{
startIndex = -1;
StartLoad();
}
else
{
var favorites = Stores.Favorites;
if (favorites.Changed)
{
lastUpdated = default;
startIndex = -1;
StartLoad();
}
}
}
protected override void OnDisappearing()
{
base.OnDisappearing();
// saving state
Preferences.Set(Configs.FavoriteTypeKey, SegmentType);
}
public override void OnUnload()
{
if (task != null)
{
task.Dispose();
task = null;
}
base.OnUnload();
}
protected override IEnumerable<IllustItem> DoGetIllustList(IllustItem[] data, out int tag)
{
tag = startIndex;
return data;
}
protected override IllustItem[] DoLoadIllustData(bool force)
{
IEnumerable<IllustItem> favs;
if (startIndex < 0)
{
var favorites = Stores.GetFavoriteObject(flag);
flag = false;
if (favorites == null)
{
return null;
}
favs = favorites.Illusts.Reload();
startIndex = 0;
}
else
{
favs = Stores.Favorites;
}
switch (SegmentType)
{
case 1: // general (non r-18)
favs = favs.Where(f => !f.IsRestrict);
break;
case 2: // animation
favs = favs.Where(f => f.IllustType == IllustType.Anime);
break;
case 3: // online
favs = favs.Where(f => f.BookmarkId != null);
break;
}
var illusts = favs.Skip(startIndex).Take(STEP).ToArray();
nextIndex = startIndex + STEP;
if (illusts.Length == 0 || nextIndex >= Stores.Favorites.Count)
{
// reach the bottom
startIndex = nextIndex;
}
return illusts;
}
private async void ToggleFilterPanel(bool flag)
{
ViewExtensions.CancelAnimations(gridFilter);
ViewExtensions.CancelAnimations(panelFilter);
if (flag)
{
isFilterVisible = true;
if (scrollDirection == ScrollDirection.Down)
{
// stop the scrolling
await scrollView.ScrollToAsync(scrollView.ScrollX, scrollView.ScrollY, false);
}
await Task.WhenAll(
labelCaret.RotateTo(180, easing: Easing.CubicOut),
gridFilter.TranslateTo(0, 0, easing: Easing.CubicOut),
gridFilter.FadeTo(1, easing: Easing.CubicOut),
panelFilter.TranslateTo(0, 0, easing: Easing.CubicOut),
panelFilter.FadeTo(1, easing: Easing.CubicOut)
);
}
else
{
isFilterVisible = false;
await Task.WhenAll(
labelCaret.RotateTo(0, easing: Easing.CubicIn),
gridFilter.TranslateTo(0, -60, easing: Easing.CubicIn),
gridFilter.FadeTo(0, easing: Easing.CubicIn),
panelFilter.TranslateTo(0, -60, easing: Easing.CubicIn),
panelFilter.FadeTo(0, easing: Easing.CubicIn)
);
}
}
private async void ChangeFilter()
{
ToggleFilterPanel(false);
await ScrollToTopAsync(scrollView);
lastUpdated = default;
startIndex = 0;
StartLoad();
}
private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
ToggleFilterPanel(!isFilterVisible);
}
private void FlowLayout_MaxHeightChanged(object sender, HeightEventArgs e)
{
SetOffset(e.ContentHeight - scrollView.Bounds.Height - SCROLL_OFFSET);
}
protected override bool CheckRefresh()
{
if (nextIndex > startIndex)
{
startIndex = nextIndex;
return true;
}
return false;
}
private void ScrollView_Scrolled(object sender, ScrolledEventArgs e)
{
var y = e.ScrollY;
if (IsScrollingDown(y))
{
// down
if (isFilterVisible)
{
ToggleFilterPanel(false);
}
}
OnScrolled(y);
}
public void Reload(bool force = false)
{
if (force)
{
flag = true;
}
lastUpdated = default;
startIndex = -1;
StartLoad(force);
}
private async void Refresh_Clicked(object sender, EventArgs e)
{
if (IsLoading)
{
return;
}
await ScrollToTopAsync(scrollView);
IsLoading = true;
var offset = 16 - IndicatorMarginTop;
activityLoading.Margin = new Thickness(0, loadingOffset - offset, 0, offset);
activityLoading.Animate("margin", top =>
{
activityLoading.Margin = new Thickness(0, top, 0, offset);
},
loadingOffset - offset, 16 - offset, easing: Easing.CubicOut, finished: (v, r) =>
{
Task.Run(() =>
{
var list = Stores.LoadOnlineFavorites();
if (list != null && list.Length > 0)
{
MainThread.BeginInvokeOnMainThread(() => ConfirmNext(list));
}
else
{
flag = false;
lastUpdated = default;
startIndex = -1;
MainThread.BeginInvokeOnMainThread(() => StartLoad(true));
}
});
});
}
private void CloseLoading(Action next = null)
{
var offset = 16 - IndicatorMarginTop;
activityLoading.Animate("margin", top =>
{
activityLoading.Margin = new Thickness(0, top, 0, offset);
},
16 - offset, loadingOffset - offset, easing: Easing.CubicIn, finished: (v, r) =>
{
IsLoading = false;
next?.Invoke();
});
}
private async void ConfirmNext(IllustItem[] list)
{
var cancel = ResourceHelper.Cancel;
var combine = ResourceHelper.FavoritesCombine;
var replace = ResourceHelper.FavoritesReplace;
var result = await DisplayActionSheet(
ResourceHelper.FavoritesOperation,
cancel,
combine,
replace);
if (result == cancel)
{
return;
}
if (result == replace)
{
Stores.GetFavoriteObject().Illusts = new FavoriteList(list);
}
else if (result == combine)
{
// combine
var nows = Stores.GetFavoriteObject().Illusts;
// sync bookmarks from remote
for (var i = nows.Count - 1; i >= 0; i--)
{
var b = nows[i];
var bookmarkId = b.BookmarkId;
var bookmark = list.FirstOrDefault(f => f.Id == b.Id);
if (bookmark == null)
{
if (bookmarkId != null)
{
// not exists in remote any more
#if LOG
App.DebugPrint($"remove bookmark ({bookmarkId}) - {b.Id}: {b.Title}");
#endif
nows.RemoveAt(i);
}
}
else if (bookmarkId != bookmark.BookmarkId)
{
// update bookmark id
#if LOG
App.DebugPrint($"change bookmark ({bookmarkId}) to ({bookmark.BookmarkId}) - {b.Id}: {b.Title}");
#endif
b.BookmarkId = bookmark.BookmarkId;
}
}
// add bookmarks that exists in remote only
list = list.Where(f => !nows.Any(i => i.Id == f.Id)).ToArray();
if (list.Length > 0)
{
#if LOG
for (var i = 0; i < list.Length; i++)
{
var item = list[i];
App.DebugPrint($"add bookmark ({item.BookmarkId}) - {item.Id}: {item.Title}");
}
#endif
nows.InsertRange(0, list);
}
}
else
{
return;
}
SyncRemoteFavorites(list);
}
private void SyncRemoteFavorites(IllustItem[] list)
{
for (var i = 0; i < list.Length; i++)
{
var item = list[i];
var data = Stores.LoadIllustPreloadData(item.Id, false);
if (data != null && data.illust.TryGetValue(item.Id, out var illust))
{
illust.CopyToItem(item);
if (data.user.TryGetValue(illust.userId, out var user))
{
item.ProfileUrl = user.image;
}
var url = Configs.GetThumbnailUrl(item.ImageUrl);
if (url != null)
{
var image = Stores.LoadPreviewImage(url, false);
if (image == null)
{
image = Stores.LoadThumbnailImage(url, false);
}
if (image != null)
{
item.Image = image;
}
}
url = item.ProfileUrl;
if (url == null)
{
item.ProfileImage = StyleDefinition.ProfileNone;
}
else
{
var image = Stores.LoadUserProfileImage(url, false);
if (image != null)
{
item.ProfileImage = image;
}
}
}
}
list = list.Where(i => i.Image == null || i.ProfileImage == null).ToArray();
if (task != null)
{
task.Dispose();
task = null;
}
task = ParallelTask.Start("favorite.loadimages", 0, list.Length, Configs.MaxPageThreads, i =>
{
var item = list[i];
if (item.ImageUrl == null || item.ProfileUrl == null)
{
var data = Stores.LoadIllustPreloadData(item.Id, true, force: true);
if (data != null && data.illust.TryGetValue(item.Id, out var illust))
{
illust.CopyToItem(item);
if (data.user.TryGetValue(illust.userId, out var user))
{
item.ProfileUrl = user.image;
}
}
else
{
App.DebugError("load.favorite", $"cannot fetch preload data, {item.Id}, disposed.");
return false;
}
}
if (item.Image == null && item.ImageUrl != null)
{
var url = Configs.GetThumbnailUrl(item.ImageUrl);
item.Image = StyleDefinition.DownloadBackground;
var image = Stores.LoadThumbnailImage(url, true, force: true);
if (image != null)
{
item.Image = image;
}
}
if (item.ProfileImage == null && item.ProfileUrl != null)
{
item.ProfileImage = StyleDefinition.ProfileNone;
var userImage = Stores.LoadUserProfileImage(item.ProfileUrl, true, force: true);
if (userImage != null)
{
item.ProfileImage = userImage;
}
}
return true;
},
complete: () =>
{
Stores.SaveFavoritesIllusts();
MainThread.BeginInvokeOnMainThread(() =>
{
CloseLoading(() =>
{
flag = false;
lastUpdated = default;
startIndex = -1;
StartLoad();
});
});
});
}
private async void ShareFavorites_Clicked(object sender, EventArgs e)
{
var file = Stores.FavoritesPath;
await Share.RequestAsync(new ShareFileRequest
{
Title = ResourceHelper.Favorites,
File = new ShareFile(file)
});
}
}
}