using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Gallery.Resources; using Gallery.UI; using Gallery.Utils; using Xamarin.Essentials; using Xamarin.Forms; namespace Gallery.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 DoGetIllustList(IllustItem[] data, out int tag) { tag = startIndex; return data; } protected override IllustItem[] DoLoadIllustData(bool force) { IEnumerable 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) }); } } }