rename from Pixiview to Gallery
This commit is contained in:
81
Gallery/Illust/FavoritesPage.xaml
Executable file
81
Gallery/Illust/FavoritesPage.xaml
Executable file
@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<i:FavoriteIllustCollectionPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:i="clr-namespace:Gallery.Illust"
|
||||
xmlns:u="clr-namespace:Gallery.UI"
|
||||
xmlns:r="clr-namespace:Gallery.Resources"
|
||||
x:Class="Gallery.Illust.FavoritesPage"
|
||||
BackgroundColor="{DynamicResource WindowColor}"
|
||||
Title="{r:Text Favorites}">
|
||||
<Shell.TitleView>
|
||||
<Grid VerticalOptions="Fill" ColumnSpacing="6"
|
||||
HorizontalOptions="{x:OnPlatform Android=Start, iOS=Fill}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{OnPlatform Android=Auto}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackLayout Grid.Column="1" Orientation="Horizontal" Spacing="6">
|
||||
<StackLayout.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"/>
|
||||
</StackLayout.GestureRecognizers>
|
||||
<Label Text="{Binding Title}"
|
||||
TextColor="{DynamicResource TextColor}"
|
||||
FontSize="{OnPlatform Android=18}"
|
||||
LineBreakMode="HeadTruncation"
|
||||
VerticalTextAlignment="Center" FontAttributes="Bold"/>
|
||||
<Label x:Name="labelCaret"
|
||||
Text="{DynamicResource IconCaretDown}"
|
||||
TextColor="{DynamicResource TextColor}"
|
||||
FontFamily="{DynamicResource IconSolidFontFamily}"
|
||||
FontSize="Small"
|
||||
VerticalTextAlignment="Center"/>
|
||||
</StackLayout>
|
||||
</Grid>
|
||||
</Shell.TitleView>
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Order="Primary" Clicked="Refresh_Clicked"
|
||||
IconImageSource="{DynamicResource FontIconCloudDownload}"/>
|
||||
<ToolbarItem Order="Primary" Clicked="ShareFavorites_Clicked"
|
||||
IconImageSource="{DynamicResource FontIconShare}"/>
|
||||
</ContentPage.ToolbarItems>
|
||||
<Grid>
|
||||
<ScrollView x:Name="scrollView" Scrolled="ScrollView_Scrolled"
|
||||
HorizontalOptions="Fill" HorizontalScrollBarVisibility="Never">
|
||||
<StackLayout>
|
||||
<ActivityIndicator x:Name="activityLoading" Margin="0, -40, 0, 0"
|
||||
HeightRequest="40"
|
||||
IsRunning="{Binding IsLoading}"
|
||||
IsVisible="{Binding IsLoading}"/>
|
||||
<u:FlowLayout ItemsSource="{Binding Illusts}" MaxHeightChanged="FlowLayout_MaxHeightChanged"
|
||||
HorizontalOptions="Fill" Column="{Binding Columns}"
|
||||
Margin="16" RowSpacing="16" ColumnSpacing="16"
|
||||
ItemTemplate="{StaticResource cardView}"/>
|
||||
<ActivityIndicator x:Name="activityBottomLoading" Margin="0, -10, 0, 16"
|
||||
IsRunning="{Binding IsBottomLoading}"
|
||||
IsVisible="{Binding IsBottomLoading}"/>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
|
||||
<u:BlurryPanel x:Name="panelFilter" VerticalOptions="Start" Opacity="0"
|
||||
Margin="{Binding PanelTopMargin}"
|
||||
BackgroundColor="{DynamicResource WindowColor}"
|
||||
HeightRequest="{Binding Height, Source={x:Reference gridFilter}}"/>
|
||||
<Grid x:Name="gridFilter" VerticalOptions="Start" Opacity="0"
|
||||
Margin="{Binding PageTopMargin}" Padding="10">
|
||||
<Grid RowSpacing="0" HorizontalOptions="Center">
|
||||
<u:SegmentedControl Margin="6, 6, 6, 3" VerticalOptions="Center"
|
||||
SelectedSegmentIndex="{Binding SegmentType, Mode=TwoWay}"
|
||||
SelectedTextColor="{DynamicResource TextColor}"
|
||||
TintColor="{DynamicResource CardBackgroundColor}">
|
||||
<u:SegmentedControl.Children>
|
||||
<u:SegmentedControlOption Text="{r:Text All}"/>
|
||||
<u:SegmentedControlOption Text="{r:Text General}"/>
|
||||
<u:SegmentedControlOption Text="{r:Text Animation}"/>
|
||||
<u:SegmentedControlOption Text="{r:Text Online}"/>
|
||||
</u:SegmentedControl.Children>
|
||||
</u:SegmentedControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</i:FavoriteIllustCollectionPage>
|
463
Gallery/Illust/FavoritesPage.xaml.cs
Normal file
463
Gallery/Illust/FavoritesPage.xaml.cs
Normal file
@ -0,0 +1,463 @@
|
||||
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<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)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
1002
Gallery/Illust/IllustCollectionPage.cs
Normal file
1002
Gallery/Illust/IllustCollectionPage.cs
Normal file
File diff suppressed because it is too large
Load Diff
43
Gallery/Illust/MainPage.xaml
Normal file
43
Gallery/Illust/MainPage.xaml
Normal file
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<i:IllustDataCollectionPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:i="clr-namespace:Gallery.Illust"
|
||||
xmlns:u="clr-namespace:Gallery.UI"
|
||||
xmlns:r="clr-namespace:Gallery.Resources"
|
||||
x:Class="Gallery.Illust.MainPage"
|
||||
BackgroundColor="{DynamicResource WindowColor}"
|
||||
Title="{r:Text Follow}">
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Order="Primary" Clicked="Refresh_Clicked"
|
||||
IconImageSource="{DynamicResource FontIconRefresh}"/>
|
||||
</ContentPage.ToolbarItems>
|
||||
<Grid>
|
||||
<ScrollView x:Name="scrollView" Scrolled="ScrollView_Scrolled"
|
||||
HorizontalOptions="Fill" HorizontalScrollBarVisibility="Never">
|
||||
<StackLayout>
|
||||
<ActivityIndicator x:Name="activityLoading" Margin="0, 10, 0, 0"
|
||||
HeightRequest="40"
|
||||
IsRunning="{Binding IsLoading}"
|
||||
IsVisible="{Binding IsLoading}"/>
|
||||
<u:FlowLayout ItemsSource="{Binding Illusts}"
|
||||
HorizontalOptions="Fill" Column="{Binding Columns}"
|
||||
RowSpacing="16" ColumnSpacing="16"
|
||||
ItemTemplate="{StaticResource cardView}">
|
||||
<u:FlowLayout.Margin>
|
||||
<OnPlatform x:TypeArguments="Thickness"
|
||||
iOS="16, 66, 16, 16"
|
||||
Android="16, 56, 16, 16"/>
|
||||
</u:FlowLayout.Margin>
|
||||
</u:FlowLayout>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
<u:BlurryPanel x:Name="panelBar" VerticalOptions="Start"
|
||||
HeightRequest="60"/>
|
||||
<SearchBar x:Name="searchBar" Placeholder="{r:Text Search}"
|
||||
HeightRequest="40"
|
||||
VerticalOptions="Start"
|
||||
CancelButtonColor="{DynamicResource TintColor}"
|
||||
Text="{Binding Keywords, Mode=TwoWay}"
|
||||
SearchButtonPressed="SearchBar_SearchButtonPressed"/>
|
||||
</Grid>
|
||||
</i:IllustDataCollectionPage>
|
138
Gallery/Illust/MainPage.xaml.cs
Normal file
138
Gallery/Illust/MainPage.xaml.cs
Normal file
@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Gallery.Utils;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Gallery.Illust
|
||||
{
|
||||
public partial class MainPage : IllustDataCollectionPage
|
||||
{
|
||||
public static readonly BindableProperty KeywordsProperty = BindableProperty.Create(
|
||||
nameof(Keywords), typeof(string), typeof(MainPage));
|
||||
|
||||
public string Keywords
|
||||
{
|
||||
get => (string)GetValue(KeywordsProperty);
|
||||
set => SetValue(KeywordsProperty, value);
|
||||
}
|
||||
|
||||
private double lastScrollY = double.MinValue;
|
||||
private ScrollDirection scrollDirection = ScrollDirection.Stop;
|
||||
|
||||
public MainPage()
|
||||
{
|
||||
Resources.Add("cardView", GetCardViewTemplate());
|
||||
InitializeComponent();
|
||||
|
||||
#if __IOS__
|
||||
searchBar.BackgroundColor = Color.Transparent;
|
||||
#else
|
||||
searchBar.SetDynamicResource(SearchBar.TextColorProperty, UI.Theme.ThemeBase.TextColor);
|
||||
searchBar.SetDynamicResource(SearchBar.PlaceholderColorProperty, UI.Theme.ThemeBase.SubTextColor);
|
||||
searchBar.SetDynamicResource(BackgroundColorProperty, UI.Theme.ThemeBase.WindowColor);
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override bool IsRedirectLogin => true;
|
||||
protected override bool NeedCookie => true;
|
||||
protected override ActivityIndicator LoadingIndicator => activityLoading;
|
||||
protected override double IndicatorMarginTop => 66;
|
||||
|
||||
protected override void OnSizeAllocated(double width, double height)
|
||||
{
|
||||
base.OnSizeAllocated(width, height);
|
||||
searchBar.Margin = new Thickness(0, PageTopMargin.Top + 8, 0, 0);
|
||||
panelBar.Margin = PanelTopMargin;
|
||||
}
|
||||
|
||||
#if __IOS__
|
||||
public override void OnOrientationChanged(bool landscape)
|
||||
{
|
||||
base.OnOrientationChanged(landscape);
|
||||
|
||||
AnimateToMargin(searchBar, new Thickness(0, PageTopMargin.Top + 8, 0, 0));
|
||||
AnimateToMargin(panelBar, PanelTopMargin);
|
||||
}
|
||||
#endif
|
||||
|
||||
protected override IEnumerable<IllustItem> DoGetIllustList(IllustData data, out int tag)
|
||||
{
|
||||
tag = 0;
|
||||
if (data.body == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var illusts = data.body.page.follow.Select(i =>
|
||||
data.body.thumbnails.illust.FirstOrDefault(l => (l.illustId ?? l.id) == i.ToString())?.ConvertToItem());
|
||||
return illusts;
|
||||
}
|
||||
|
||||
protected override IllustData DoLoadIllustData(bool force)
|
||||
{
|
||||
return Stores.LoadIllustData(force);
|
||||
}
|
||||
|
||||
private async void Refresh_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if (IsLoading)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await ScrollToTopAsync(scrollView);
|
||||
|
||||
StartLoad(true);
|
||||
}
|
||||
|
||||
private void SearchBar_SearchButtonPressed(object sender, EventArgs e)
|
||||
{
|
||||
var key = Keywords;
|
||||
if (key != null)
|
||||
{
|
||||
key = key.Trim().ToLower();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
Task.Run(() => App.OpenUrl(new Uri("gallery://example.com/artworks/" + key)));
|
||||
}
|
||||
}
|
||||
|
||||
private const int searchBarHeight = 60;
|
||||
|
||||
private void ScrollView_Scrolled(object sender, ScrolledEventArgs e)
|
||||
{
|
||||
var y = e.ScrollY;
|
||||
if (y > lastScrollY)
|
||||
{
|
||||
// down
|
||||
if (scrollDirection != ScrollDirection.Down && y > searchBarHeight - topOffset)
|
||||
{
|
||||
scrollDirection = ScrollDirection.Down;
|
||||
if (searchBar.IsFocused)
|
||||
{
|
||||
searchBar.Unfocus();
|
||||
}
|
||||
ViewExtensions.CancelAnimations(searchBar);
|
||||
ViewExtensions.CancelAnimations(panelBar);
|
||||
searchBar.TranslateTo(0, -searchBarHeight, easing: Easing.CubicIn);
|
||||
panelBar.TranslateTo(0, -searchBarHeight, easing: Easing.CubicIn);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// up
|
||||
if (scrollDirection != ScrollDirection.Up)
|
||||
{
|
||||
scrollDirection = ScrollDirection.Up;
|
||||
ViewExtensions.CancelAnimations(searchBar);
|
||||
ViewExtensions.CancelAnimations(panelBar);
|
||||
searchBar.TranslateTo(0, 0, easing: Easing.CubicOut);
|
||||
panelBar.TranslateTo(0, 0, easing: Easing.CubicOut);
|
||||
}
|
||||
}
|
||||
lastScrollY = y;
|
||||
}
|
||||
}
|
||||
}
|
116
Gallery/Illust/RankingPage.xaml
Executable file
116
Gallery/Illust/RankingPage.xaml
Executable file
@ -0,0 +1,116 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<i:IllustRankingDataCollectionPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:i="clr-namespace:Gallery.Illust"
|
||||
xmlns:u="clr-namespace:Gallery.UI"
|
||||
xmlns:r="clr-namespace:Gallery.Resources"
|
||||
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
|
||||
x:Class="Gallery.Illust.RankingPage"
|
||||
BackgroundColor="{DynamicResource WindowColor}"
|
||||
Title="{r:Text Ranking}">
|
||||
<Shell.TitleView>
|
||||
<Grid VerticalOptions="Fill" ColumnSpacing="6"
|
||||
HorizontalOptions="{x:OnPlatform Android=Start, iOS=Fill}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{OnPlatform Android=Auto}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackLayout Grid.Column="1" Orientation="Horizontal" Spacing="6">
|
||||
<StackLayout.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"/>
|
||||
</StackLayout.GestureRecognizers>
|
||||
<Label Text="{Binding InternalTitle}"
|
||||
TextColor="{DynamicResource TextColor}"
|
||||
FontSize="{OnPlatform Android=18}"
|
||||
LineBreakMode="HeadTruncation"
|
||||
VerticalTextAlignment="Center" FontAttributes="Bold"/>
|
||||
<Label x:Name="labelCaret"
|
||||
Text="{DynamicResource IconCaretDown}"
|
||||
TextColor="{DynamicResource TextColor}"
|
||||
FontFamily="{DynamicResource IconSolidFontFamily}"
|
||||
FontSize="Small"
|
||||
VerticalTextAlignment="Center"/>
|
||||
</StackLayout>
|
||||
</Grid>
|
||||
</Shell.TitleView>
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Order="Primary"
|
||||
Command="{Binding ToolbarCommand}" CommandParameter="prev"
|
||||
IconImageSource="{DynamicResource FontIconCaretCircleLeft}"/>
|
||||
<ToolbarItem Order="Primary"
|
||||
Command="{Binding ToolbarCommand}" CommandParameter="select"
|
||||
IconImageSource="{DynamicResource FontIconCalendarDay}"/>
|
||||
<ToolbarItem Order="Primary"
|
||||
Command="{Binding ToolbarCommand}" CommandParameter="next"
|
||||
IconImageSource="{DynamicResource FontIconCaretCircleRight}"/>
|
||||
</ContentPage.ToolbarItems>
|
||||
<Grid>
|
||||
<ScrollView x:Name="scrollView" Scrolled="ScrollView_Scrolled"
|
||||
HorizontalOptions="Fill" HorizontalScrollBarVisibility="Never">
|
||||
<StackLayout>
|
||||
<ActivityIndicator x:Name="activityLoading" Margin="0, -40, 0, 0"
|
||||
HeightRequest="40"
|
||||
IsRunning="{Binding IsLoading}"
|
||||
IsVisible="{Binding IsLoading}"/>
|
||||
<u:FlowLayout ItemsSource="{Binding Illusts}" MaxHeightChanged="FlowLayout_MaxHeightChanged"
|
||||
HorizontalOptions="Fill" Column="{Binding Columns}"
|
||||
Margin="16" RowSpacing="16" ColumnSpacing="16"
|
||||
ItemTemplate="{StaticResource cardView}"/>
|
||||
<ActivityIndicator x:Name="activityBottomLoading" Margin="0, -10, 0, 16"
|
||||
IsRunning="{Binding IsBottomLoading}"
|
||||
IsVisible="{Binding IsBottomLoading}"/>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
<u:BlurryPanel x:Name="panelFilter" VerticalOptions="Start" Opacity="0"
|
||||
Margin="{Binding PanelTopMargin}"
|
||||
BackgroundColor="{DynamicResource WindowColor}"
|
||||
HeightRequest="{Binding Height, Source={x:Reference gridFilter}}"/>
|
||||
<Grid x:Name="gridFilter" VerticalOptions="Start" Opacity="0"
|
||||
Margin="{Binding PageTopMargin}" Padding="10">
|
||||
<Grid RowSpacing="0" HorizontalOptions="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<u:SegmentedControl Margin="6, 6, 6, 3" VerticalOptions="Center"
|
||||
SelectedSegmentIndex="{Binding SegmentDate, Mode=TwoWay}"
|
||||
SelectedTextColor="{DynamicResource TextColor}"
|
||||
TintColor="{DynamicResource CardBackgroundColor}">
|
||||
<u:SegmentedControl.Children>
|
||||
<u:SegmentedControlOption Text="{r:Text Daily}"/>
|
||||
<u:SegmentedControlOption Text="{r:Text Weekly}"/>
|
||||
<u:SegmentedControlOption Text="{r:Text Monthly}"/>
|
||||
<u:SegmentedControlOption Text="{r:Text Male}"/>
|
||||
</u:SegmentedControl.Children>
|
||||
</u:SegmentedControl>
|
||||
<u:SegmentedControl Grid.Row="1" HorizontalOptions="Start"
|
||||
IsVisible="{Binding SegmentTypeVisible}"
|
||||
Margin="6, 3, 6, 6" VerticalOptions="Center"
|
||||
SelectedSegmentIndex="{Binding SegmentType, Mode=TwoWay}"
|
||||
SelectedTextColor="{DynamicResource TextColor}"
|
||||
TintColor="{DynamicResource CardBackgroundColor}">
|
||||
<u:SegmentedControl.Children>
|
||||
<u:SegmentedControlOption Text="{r:Text General}"/>
|
||||
<u:SegmentedControlOption Text="{r:Text R18}"/>
|
||||
</u:SegmentedControl.Children>
|
||||
</u:SegmentedControl>
|
||||
<Button Grid.Row="1" HorizontalOptions="End" VerticalOptions="Center"
|
||||
Text="{DynamicResource IconCircleCheck}"
|
||||
FontFamily="{DynamicResource IconSolidFontFamily}"
|
||||
BackgroundColor="Transparent"
|
||||
TextColor="{DynamicResource TintColor}"
|
||||
FontSize="20" Margin="0, 0, 6, 0"
|
||||
Clicked="Filter_Clicked"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<DatePicker x:Name="datePicker" IsVisible="False"
|
||||
ios:DatePicker.UpdateMode="WhenFinished"
|
||||
Date="{Binding SelectedDate}"
|
||||
MaximumDate="{Binding MaximumDate}"
|
||||
Focused="DatePicker_Focused"
|
||||
Unfocused="DatePicker_Focused"
|
||||
DateSelected="DatePicker_DateSelected"/>
|
||||
</Grid>
|
||||
</i:IllustRankingDataCollectionPage>
|
406
Gallery/Illust/RankingPage.xaml.cs
Executable file
406
Gallery/Illust/RankingPage.xaml.cs
Executable file
@ -0,0 +1,406 @@
|
||||
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 RankingPage : IllustRankingDataCollectionPage
|
||||
{
|
||||
private static readonly string[] segmentDates = { "daily", "weekly", "monthly", "male" };
|
||||
|
||||
public static readonly BindableProperty InternalTitleProperty = BindableProperty.Create(
|
||||
nameof(InternalTitle), typeof(string), typeof(RankingPage));
|
||||
|
||||
public string InternalTitle
|
||||
{
|
||||
get => (string)GetValue(InternalTitleProperty);
|
||||
set => SetValue(InternalTitleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly BindableProperty SegmentDateProperty = BindableProperty.Create(
|
||||
nameof(SegmentDate), typeof(int), typeof(RankingPage), propertyChanged: OnSegmentDatePropertyChanged);
|
||||
public static readonly BindableProperty SegmentTypeVisibleProperty = BindableProperty.Create(
|
||||
nameof(SegmentTypeVisible), typeof(bool), typeof(RankingPage));
|
||||
public static readonly BindableProperty SegmentTypeProperty = BindableProperty.Create(
|
||||
nameof(SegmentType), typeof(int), typeof(RankingPage), propertyChanged: OnSegmentTypePropertyChanged);
|
||||
public static readonly BindableProperty MaximumDateProperty = BindableProperty.Create(
|
||||
nameof(MaximumDate), typeof(DateTime), typeof(RankingPage), DateTime.Today);
|
||||
public static readonly BindableProperty SelectedDateProperty = BindableProperty.Create(
|
||||
nameof(SelectedDate), typeof(DateTime), typeof(RankingPage), DateTime.Today);
|
||||
|
||||
private static void OnSegmentDatePropertyChanged(BindableObject obj, object old, object @new)
|
||||
{
|
||||
var page = (RankingPage)obj;
|
||||
var index = (int)@new;
|
||||
if (index == 2)
|
||||
{
|
||||
// monthly
|
||||
var typeIndex = page.SegmentType;
|
||||
if (typeIndex == 1)
|
||||
{
|
||||
// r-18
|
||||
page.SegmentType = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
private static void OnSegmentTypePropertyChanged(BindableObject obj, object old, object @new)
|
||||
{
|
||||
var page = (RankingPage)obj;
|
||||
var index = (int)@new;
|
||||
if (index == 1)
|
||||
{
|
||||
// r-18
|
||||
var dateIndex = page.SegmentDate;
|
||||
if (dateIndex == 2)
|
||||
{
|
||||
// monthly
|
||||
page.SegmentDate = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int SegmentDate
|
||||
{
|
||||
get => (int)GetValue(SegmentDateProperty);
|
||||
set => SetValue(SegmentDateProperty, value);
|
||||
}
|
||||
public bool SegmentTypeVisible
|
||||
{
|
||||
get => (bool)GetValue(SegmentTypeVisibleProperty);
|
||||
set => SetValue(SegmentTypeVisibleProperty, value);
|
||||
}
|
||||
public int SegmentType
|
||||
{
|
||||
get => (int)GetValue(SegmentTypeProperty);
|
||||
set => SetValue(SegmentTypeProperty, value);
|
||||
}
|
||||
public DateTime MaximumDate
|
||||
{
|
||||
get => (DateTime)GetValue(MaximumDateProperty);
|
||||
set => SetValue(MaximumDateProperty, value);
|
||||
}
|
||||
public DateTime SelectedDate
|
||||
{
|
||||
get => (DateTime)GetValue(SelectedDateProperty);
|
||||
set => SetValue(SelectedDateProperty, value);
|
||||
}
|
||||
public Command<string> ToolbarCommand { get; private set; }
|
||||
|
||||
private bool previousEnabled;
|
||||
private bool dateEnabled;
|
||||
private bool nextEnabled;
|
||||
|
||||
private bool isFilterVisible;
|
||||
private bool isDatePickerVisible;
|
||||
private string lastQueryKey;
|
||||
private string queryDate;
|
||||
private string previousDate;
|
||||
private string nextDate;
|
||||
private int currentPage;
|
||||
private int nextPage;
|
||||
|
||||
private string QueryKey => segmentDates[SegmentDate] + (SegmentType == 1 ? "_r18" : string.Empty);
|
||||
|
||||
public RankingPage()
|
||||
{
|
||||
Resources.Add("cardView", GetCardViewTemplate(titleBinding: nameof(IllustItem.RankTitle)));
|
||||
ToolbarCommand = new Command<string>(OnDateTrigger, OnCanDateTrigger);
|
||||
InitializeComponent();
|
||||
gridFilter.TranslationY = -100;
|
||||
panelFilter.TranslationY = -100;
|
||||
|
||||
// preferences
|
||||
SegmentDate = Preferences.Get(Configs.QueryModeKey, 0);
|
||||
SegmentType = Preferences.Get(Configs.QueryTypeKey, 0);
|
||||
lastQueryKey = QueryKey;
|
||||
queryDate = Preferences.Get(Configs.QueryDateKey, null);
|
||||
currentPage = 1;
|
||||
datePicker.MinimumDate = new DateTime(2007, 9, 13);
|
||||
MaximumDate = DateTime.Today.AddDays(-1);
|
||||
}
|
||||
|
||||
protected override ActivityIndicator LoadingIndicator => activityLoading;
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
var r18 = Configs.IsOnR18;
|
||||
if (!r18)
|
||||
{
|
||||
SegmentType = 0;
|
||||
var query = QueryKey;
|
||||
if (lastQueryKey != query)
|
||||
{
|
||||
ReleaseCollection();
|
||||
lastQueryKey = query;
|
||||
}
|
||||
}
|
||||
SegmentTypeVisible = r18;
|
||||
|
||||
if (currentPage != 1 && Illusts == null)
|
||||
{
|
||||
currentPage = 1;
|
||||
}
|
||||
base.OnAppearing();
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
|
||||
// saving state
|
||||
Preferences.Set(Configs.QueryModeKey, SegmentDate);
|
||||
Preferences.Set(Configs.QueryTypeKey, SegmentType);
|
||||
if (queryDate != null)
|
||||
{
|
||||
Preferences.Set(Configs.QueryDateKey, queryDate);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<IllustItem> DoGetIllustList(IllustRankingData data, out int tag)
|
||||
{
|
||||
tag = currentPage;
|
||||
if (lastQueryKey != null && lastQueryKey.StartsWith(segmentDates[3]))
|
||||
{
|
||||
return data.contents.Where(i => i.illust_type == "0").Select(i => i.ConvertToItem());
|
||||
}
|
||||
return data.contents.Select(i => i.ConvertToItem());
|
||||
}
|
||||
|
||||
protected override IllustRankingData DoLoadIllustData(bool force)
|
||||
{
|
||||
var data = Stores.LoadIllustRankingData(lastQueryKey, queryDate, currentPage, out lastError, force);
|
||||
if (data != null)
|
||||
{
|
||||
if (int.TryParse(data.next, out int next))
|
||||
{
|
||||
nextPage = next;
|
||||
}
|
||||
var date = data.date;
|
||||
DateTime now;
|
||||
if (date.Length == 8 && int.TryParse(date, out _))
|
||||
{
|
||||
queryDate = date;
|
||||
now = new DateTime(
|
||||
int.Parse(date.Substring(0, 4)),
|
||||
int.Parse(date.Substring(4, 2)),
|
||||
int.Parse(date.Substring(6, 2)));
|
||||
SelectedDate = now;
|
||||
//date = now.ToShortDateString();
|
||||
date = now.ToString("yyyy-MM-dd");
|
||||
}
|
||||
else
|
||||
{
|
||||
now = default;
|
||||
}
|
||||
date = ResourceHelper.GetResource(data.mode, date);
|
||||
MainThread.BeginInvokeOnMainThread(() => InternalTitle = date);
|
||||
|
||||
var prev_date = data.prev_date;
|
||||
if (int.TryParse(prev_date, out _))
|
||||
{
|
||||
previousDate = prev_date;
|
||||
previousEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
previousDate = null;
|
||||
previousEnabled = false;
|
||||
}
|
||||
var next_date = data.next_date;
|
||||
if (int.TryParse(next_date, out _))
|
||||
{
|
||||
nextDate = next_date;
|
||||
nextEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextDate = null;
|
||||
nextEnabled = false;
|
||||
if (now != default && force)
|
||||
{
|
||||
MaximumDate = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
dateEnabled = true;
|
||||
MainThread.BeginInvokeOnMainThread(ToolbarCommand.ChangeCanExecute);
|
||||
return data;
|
||||
}
|
||||
|
||||
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, -100, easing: Easing.CubicIn),
|
||||
gridFilter.FadeTo(0, easing: Easing.CubicIn),
|
||||
panelFilter.TranslateTo(0, -100, easing: Easing.CubicIn),
|
||||
panelFilter.FadeTo(0, easing: Easing.CubicIn)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnDateTrigger(string action)
|
||||
{
|
||||
if (IsLoading)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (action == "select")
|
||||
{
|
||||
// stop the scrolling
|
||||
await scrollView.ScrollToAsync(scrollView.ScrollX, scrollView.ScrollY, false);
|
||||
datePicker.Focus();
|
||||
}
|
||||
else
|
||||
{
|
||||
var date = action == "prev" ? previousDate : nextDate;
|
||||
if (date == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (isFilterVisible)
|
||||
{
|
||||
ToggleFilterPanel(false);
|
||||
}
|
||||
queryDate = date;
|
||||
PrepareLoad();
|
||||
}
|
||||
}
|
||||
|
||||
private bool OnCanDateTrigger(string action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case "prev":
|
||||
return previousEnabled;
|
||||
case "next":
|
||||
return nextEnabled;
|
||||
default:
|
||||
return dateEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
private void DatePicker_Focused(object sender, FocusEventArgs e)
|
||||
{
|
||||
isDatePickerVisible = e.IsFocused;
|
||||
}
|
||||
|
||||
private void DatePicker_DateSelected(object sender, DateChangedEventArgs e)
|
||||
{
|
||||
if (e.OldDate == DateTime.Today || IsLoading)
|
||||
{
|
||||
// first load or loading
|
||||
return;
|
||||
}
|
||||
queryDate = e.NewDate.ToString("yyyyMMdd");
|
||||
PrepareLoad();
|
||||
}
|
||||
|
||||
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 (nextPage == currentPage + 1)
|
||||
{
|
||||
currentPage = nextPage;
|
||||
#if DEBUG
|
||||
App.DebugPrint($"loading page {nextPage}");
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ScrollView_Scrolled(object sender, ScrolledEventArgs e)
|
||||
{
|
||||
var y = e.ScrollY;
|
||||
if (IsScrollingDown(y))
|
||||
{
|
||||
// down
|
||||
if (isFilterVisible)
|
||||
{
|
||||
ToggleFilterPanel(false);
|
||||
}
|
||||
}
|
||||
if (isDatePickerVisible)
|
||||
{
|
||||
isDatePickerVisible = false;
|
||||
datePicker.Unfocus();
|
||||
}
|
||||
OnScrolled(y);
|
||||
}
|
||||
|
||||
private void Filter_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
var query = QueryKey;
|
||||
ToggleFilterPanel(false);
|
||||
|
||||
if (IsLoading)
|
||||
{
|
||||
return;
|
||||
}
|
||||
//if (lastQueryKey != query)
|
||||
{
|
||||
// query changed.
|
||||
lastQueryKey = query;
|
||||
#if DEBUG
|
||||
App.DebugPrint($"query changed: {query}");
|
||||
#endif
|
||||
PrepareLoad();
|
||||
}
|
||||
}
|
||||
|
||||
private async void PrepareLoad()
|
||||
{
|
||||
await ScrollToTopAsync(scrollView);
|
||||
// release
|
||||
currentPage = 1;
|
||||
previousEnabled = false;
|
||||
dateEnabled = false;
|
||||
nextEnabled = false;
|
||||
ToolbarCommand.ChangeCanExecute();
|
||||
StartLoad(true);
|
||||
}
|
||||
|
||||
private void ReleaseCollection()
|
||||
{
|
||||
currentPage = 1;
|
||||
InvalidateCollection();
|
||||
}
|
||||
}
|
||||
}
|
32
Gallery/Illust/RecommendsPage.xaml
Executable file
32
Gallery/Illust/RecommendsPage.xaml
Executable file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<i:IllustDataCollectionPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:i="clr-namespace:Gallery.Illust"
|
||||
xmlns:u="clr-namespace:Gallery.UI"
|
||||
xmlns:r="clr-namespace:Gallery.Resources"
|
||||
x:Class="Gallery.Illust.RecommendsPage"
|
||||
BackgroundColor="{DynamicResource WindowColor}"
|
||||
Title="{r:Text Recommends}">
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Order="Primary" Clicked="Refresh_Clicked"
|
||||
IconImageSource="{DynamicResource FontIconRefresh}"/>
|
||||
</ContentPage.ToolbarItems>
|
||||
<Grid>
|
||||
<ScrollView x:Name="scrollView" HorizontalOptions="Fill" HorizontalScrollBarVisibility="Never">
|
||||
<StackLayout>
|
||||
<ActivityIndicator x:Name="activityLoading" Margin="0, -40, 0, 0"
|
||||
HeightRequest="40"
|
||||
IsRunning="{Binding IsLoading}"
|
||||
IsVisible="{Binding IsLoading}"/>
|
||||
<u:FlowLayout ItemsSource="{Binding Users}" IsVisible="{Binding UserRecommendsVisible}"
|
||||
HorizontalOptions="Fill" Column="{Binding UserColumns}"
|
||||
Margin="16" RowSpacing="16"
|
||||
ItemTemplate="{StaticResource userCardView}"/>
|
||||
<u:FlowLayout ItemsSource="{Binding Illusts}"
|
||||
HorizontalOptions="Fill" Column="{Binding Columns}"
|
||||
Margin="16" RowSpacing="16" ColumnSpacing="16"
|
||||
ItemTemplate="{StaticResource cardView}"/>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</Grid>
|
||||
</i:IllustDataCollectionPage>
|
369
Gallery/Illust/RecommendsPage.xaml.cs
Executable file
369
Gallery/Illust/RecommendsPage.xaml.cs
Executable file
@ -0,0 +1,369 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Gallery.UI;
|
||||
using Gallery.UI.Theme;
|
||||
using Gallery.Utils;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Gallery.Illust
|
||||
{
|
||||
public partial class RecommendsPage : IllustDataCollectionPage
|
||||
{
|
||||
public static readonly BindableProperty UsersProperty = BindableProperty.Create(
|
||||
nameof(Users), typeof(List<IllustUserItem>), typeof(RecommendsPage));
|
||||
public static readonly BindableProperty UserColumnsProperty = BindableProperty.Create(
|
||||
nameof(UserColumns), typeof(int), typeof(RecommendsPage), 1);
|
||||
public static readonly BindableProperty UserRecommendsVisibleProperty = BindableProperty.Create(
|
||||
nameof(UserRecommendsVisible), typeof(bool), typeof(RecommendsPage));
|
||||
|
||||
public List<IllustUserItem> Users
|
||||
{
|
||||
get => (List<IllustUserItem>)GetValue(UsersProperty);
|
||||
set => SetValue(UsersProperty, value);
|
||||
}
|
||||
public int UserColumns
|
||||
{
|
||||
get => (int)GetValue(UserColumnsProperty);
|
||||
set => SetValue(UserColumnsProperty, value);
|
||||
}
|
||||
public bool UserRecommendsVisible
|
||||
{
|
||||
get => (bool)GetValue(UserRecommendsVisibleProperty);
|
||||
set => SetValue(UserRecommendsVisibleProperty, value);
|
||||
}
|
||||
|
||||
private IllustData illustData;
|
||||
|
||||
public RecommendsPage()
|
||||
{
|
||||
Resources.Add("cardView", GetCardViewTemplate());
|
||||
Resources.Add("userCardView", GetUserCardViewTemplate());
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override bool NeedCookie => true;
|
||||
protected override ActivityIndicator LoadingIndicator => activityLoading;
|
||||
|
||||
public override void OnUnload()
|
||||
{
|
||||
base.OnUnload();
|
||||
Users = null;
|
||||
}
|
||||
|
||||
private Image GetUserCardViewImage(int column, CornerMask masks, string source, string parameter)
|
||||
{
|
||||
Image image;
|
||||
if (masks == CornerMask.None)
|
||||
{
|
||||
image = new Image();
|
||||
}
|
||||
else
|
||||
{
|
||||
image = new RoundImage
|
||||
{
|
||||
CornerRadius = 10,
|
||||
CornerMasks = masks
|
||||
};
|
||||
}
|
||||
image.HorizontalOptions = LayoutOptions.Fill;
|
||||
image.Aspect = Aspect.AspectFill;
|
||||
image.GestureRecognizers.Add(new TapGestureRecognizer
|
||||
{
|
||||
Command = commandIllustImageTapped
|
||||
}
|
||||
.Binding(TapGestureRecognizer.CommandParameterProperty, parameter));
|
||||
image.SetBinding(Image.SourceProperty, source);
|
||||
if (column > 0)
|
||||
{
|
||||
Grid.SetColumn(image, column);
|
||||
}
|
||||
Grid.SetRow(image, 1);
|
||||
return image;
|
||||
}
|
||||
|
||||
private DataTemplate GetUserCardViewTemplate()
|
||||
{
|
||||
return new DataTemplate(() =>
|
||||
{
|
||||
return new Grid
|
||||
{
|
||||
RowSpacing = 0,
|
||||
ColumnSpacing = 0,
|
||||
RowDefinitions =
|
||||
{
|
||||
new RowDefinition { Height = 40 },
|
||||
new RowDefinition { Height = 120 }
|
||||
},
|
||||
ColumnDefinitions =
|
||||
{
|
||||
new ColumnDefinition(),
|
||||
new ColumnDefinition(),
|
||||
new ColumnDefinition()
|
||||
},
|
||||
Children =
|
||||
{
|
||||
// stacklayout: user
|
||||
new Grid
|
||||
{
|
||||
ColumnDefinitions =
|
||||
{
|
||||
new ColumnDefinition { Width = 30 },
|
||||
new ColumnDefinition()
|
||||
},
|
||||
Padding = new Thickness(8, 0, 8, 8),
|
||||
Children =
|
||||
{
|
||||
// user icon
|
||||
new CircleImage
|
||||
{
|
||||
WidthRequest = 30,
|
||||
HeightRequest = 30,
|
||||
Aspect = Aspect.AspectFill
|
||||
}
|
||||
.Binding(Image.SourceProperty, nameof(IllustUserItem.ProfileImage)),
|
||||
|
||||
// user name
|
||||
new Label
|
||||
{
|
||||
HorizontalOptions = LayoutOptions.FillAndExpand,
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
LineBreakMode = LineBreakMode.TailTruncation,
|
||||
FontSize = StyleDefinition.FontSizeSmall
|
||||
}
|
||||
.Binding(Label.TextProperty, nameof(IllustUserItem.UserName))
|
||||
.DynamicResource(Label.TextColorProperty, ThemeBase.SubTextColor)
|
||||
.GridColumn(1),
|
||||
},
|
||||
GestureRecognizers =
|
||||
{
|
||||
new TapGestureRecognizer
|
||||
{
|
||||
Command = commandUserTapped
|
||||
}
|
||||
.Binding(TapGestureRecognizer.CommandParameterProperty, ".")
|
||||
}
|
||||
}
|
||||
.GridColumnSpan(3),
|
||||
|
||||
GetUserCardViewImage(0, CornerMask.Left,
|
||||
$"{nameof(IllustUserItem.Image1Item)}.{nameof(IllustItem.Image)}",
|
||||
nameof(IllustUserItem.Image1Item)),
|
||||
|
||||
GetUserCardViewImage(1, CornerMask.None,
|
||||
$"{nameof(IllustUserItem.Image2Item)}.{nameof(IllustItem.Image)}",
|
||||
nameof(IllustUserItem.Image2Item)),
|
||||
|
||||
GetUserCardViewImage(2, CornerMask.Right,
|
||||
$"{nameof(IllustUserItem.Image3Item)}.{nameof(IllustItem.Image)}",
|
||||
nameof(IllustUserItem.Image3Item))
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
protected override void OnSizeAllocated(double width, double height)
|
||||
{
|
||||
base.OnSizeAllocated(width, height);
|
||||
int columns;
|
||||
if (width > height)
|
||||
{
|
||||
columns = isPhone ? 2 : 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
columns = isPhone ? 1 : 2;
|
||||
}
|
||||
if (UserColumns != columns)
|
||||
{
|
||||
UserColumns = columns;
|
||||
#if DEBUG
|
||||
App.DebugPrint($"change user columns to {columns}");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DoIllustsLoaded(IllustCollection collection, bool bottom)
|
||||
{
|
||||
//IsLoading = false;
|
||||
if (illustData != null)
|
||||
{
|
||||
IllustCollection = collection;
|
||||
Task.Run(() => DoLoadUserRecommendsData(illustData));
|
||||
}
|
||||
else
|
||||
{
|
||||
base.DoIllustsLoaded(collection, bottom);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<IllustItem> DoGetIllustList(IllustData data, out int tag)
|
||||
{
|
||||
tag = 0;
|
||||
if (data.body == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return data.body.page.recommend.ids.Select(id =>
|
||||
data.body.thumbnails.illust.FirstOrDefault(l => (l.illustId ?? l.id) == id)?.ConvertToItem());
|
||||
}
|
||||
|
||||
protected override IllustData DoLoadIllustData(bool force)
|
||||
{
|
||||
illustData = Stores.LoadIllustData(force);
|
||||
return illustData;
|
||||
}
|
||||
|
||||
private void DoLoadUserRecommendsData(IllustData data)
|
||||
{
|
||||
var r18 = Configs.IsOnR18;
|
||||
var defaultImage = StyleDefinition.DownloadBackground;
|
||||
var users = data.body.page.recommendUser.Select(u =>
|
||||
{
|
||||
var usrId = u.id.ToString();
|
||||
var usr = data.body.users.FirstOrDefault(r => r.userId == usrId);
|
||||
if (usr == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
IllustItem item1 = null, item2 = null, item3 = null;
|
||||
if (u.illustIds != null)
|
||||
{
|
||||
var length = u.illustIds.Length;
|
||||
if (length > 0)
|
||||
{
|
||||
var id = u.illustIds[0];
|
||||
item1 = data.body.thumbnails.illust.FirstOrDefault(l => (l.illustId ?? l.id) == id)?.ConvertToItem(defaultImage);
|
||||
}
|
||||
if (length > 1)
|
||||
{
|
||||
var id = u.illustIds[1];
|
||||
item2 = data.body.thumbnails.illust.FirstOrDefault(l => (l.illustId ?? l.id) == id)?.ConvertToItem(defaultImage);
|
||||
}
|
||||
if (length > 2)
|
||||
{
|
||||
var id = u.illustIds[2];
|
||||
item3 = data.body.thumbnails.illust.FirstOrDefault(l => (l.illustId ?? l.id) == id)?.ConvertToItem(defaultImage);
|
||||
}
|
||||
}
|
||||
if (!r18)
|
||||
{
|
||||
if (item1?.IsRestrict == true ||
|
||||
item2?.IsRestrict == true ||
|
||||
item3?.IsRestrict == true)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return new IllustUserItem
|
||||
{
|
||||
UserId = usrId,
|
||||
UserName = usr.name,
|
||||
ProfileUrl = usr.image,
|
||||
Image1Item = item1,
|
||||
Image2Item = item2,
|
||||
Image3Item = item3
|
||||
};
|
||||
}).Where(u => u != null);
|
||||
|
||||
var list = new List<IllustUserItem>(users);
|
||||
activityLoading.Animate("margin", top =>
|
||||
{
|
||||
activityLoading.Margin = new Thickness(0, top, 0, 0);
|
||||
},
|
||||
16, -40, easing: Easing.CubicIn, finished: (v, r) =>
|
||||
{
|
||||
IsLoading = false;
|
||||
UserRecommendsVisible = list.Count > 0;
|
||||
#if __IOS__
|
||||
Device.StartTimer(TimeSpan.FromMilliseconds(48), () =>
|
||||
#else
|
||||
Device.StartTimer(TimeSpan.FromMilliseconds(150), () =>
|
||||
#endif
|
||||
{
|
||||
Users = list;
|
||||
Illusts = IllustCollection;
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
DoLoadUserRecommendsImages(list);
|
||||
}
|
||||
|
||||
private void DoLoadUserRecommendsImages(List<IllustUserItem> users)
|
||||
{
|
||||
foreach (var user in users)
|
||||
{
|
||||
if (user.ProfileUrl != null)
|
||||
{
|
||||
var userImage = Stores.LoadUserProfileImage(user.ProfileUrl, true);
|
||||
if (userImage != null)
|
||||
{
|
||||
user.ProfileImage = userImage;
|
||||
}
|
||||
}
|
||||
Task.WaitAll(
|
||||
Task.Run(() => DoLoadUserRecommendsImage(user.Image1Item)),
|
||||
Task.Run(() => DoLoadUserRecommendsImage(user.Image2Item)),
|
||||
Task.Run(() => DoLoadUserRecommendsImage(user.Image3Item)));
|
||||
}
|
||||
}
|
||||
|
||||
private void DoLoadUserRecommendsImage(IllustItem illust)
|
||||
{
|
||||
if (illust.ImageUrl != null)
|
||||
{
|
||||
var url = Configs.GetThumbnailUrl(illust.ImageUrl);
|
||||
var image = Stores.LoadPreviewImage(url, false);
|
||||
if (image == null)
|
||||
{
|
||||
image = Stores.LoadThumbnailImage(url, true);
|
||||
}
|
||||
if (image != null)
|
||||
{
|
||||
illust.Image = image;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void Refresh_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if (IsLoading)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await ScrollToTopAsync(scrollView);
|
||||
StartLoad(true);
|
||||
}
|
||||
}
|
||||
|
||||
public class IllustUserItem : BindableObject, IIllustItem
|
||||
{
|
||||
//public static readonly BindableProperty Image1Property = BindableProperty.Create(
|
||||
// nameof(Image1), typeof(ImageSource), typeof(IllustUserItem));
|
||||
public static readonly BindableProperty ProfileImageProperty = BindableProperty.Create(
|
||||
nameof(ProfileImage), typeof(ImageSource), typeof(IllustUserItem));
|
||||
|
||||
//public ImageSource Image1
|
||||
//{
|
||||
// get => (ImageSource)GetValue(Image1Property);
|
||||
// set => SetValue(Image1Property, value);
|
||||
//}
|
||||
public ImageSource ProfileImage
|
||||
{
|
||||
get => (ImageSource)GetValue(ProfileImageProperty);
|
||||
set => SetValue(ProfileImageProperty, value);
|
||||
}
|
||||
|
||||
public string UserId { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public IllustItem Image1Item { get; set; }
|
||||
public IllustItem Image2Item { get; set; }
|
||||
public IllustItem Image3Item { get; set; }
|
||||
public string ProfileUrl { get; set; }
|
||||
|
||||
public bool IsFavorite { get; }
|
||||
public string BookmarkId { get; }
|
||||
}
|
||||
}
|
32
Gallery/Illust/RelatedIllustsPage.xaml
Executable file
32
Gallery/Illust/RelatedIllustsPage.xaml
Executable file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<i:IllustRecommendsCollectionPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:i="clr-namespace:Gallery.Illust"
|
||||
xmlns:u="clr-namespace:Gallery.UI"
|
||||
xmlns:r="clr-namespace:Gallery.Resources"
|
||||
x:Class="Gallery.Illust.RelatedIllustsPage"
|
||||
Title="{r:Text RelatedIllusts}"
|
||||
BackgroundColor="{DynamicResource WindowColor}">
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Order="Primary" Clicked="Refresh_Clicked"
|
||||
IconImageSource="{DynamicResource FontIconRefresh}"/>
|
||||
</ContentPage.ToolbarItems>
|
||||
<Grid>
|
||||
<ScrollView x:Name="scrollView" Scrolled="ScrollView_Scrolled"
|
||||
HorizontalOptions="Fill" HorizontalScrollBarVisibility="Never">
|
||||
<StackLayout>
|
||||
<ActivityIndicator x:Name="activityLoading" Margin="0, -40, 0, 0"
|
||||
HeightRequest="40"
|
||||
IsRunning="{Binding IsLoading}"
|
||||
IsVisible="{Binding IsLoading}"/>
|
||||
<u:FlowLayout ItemsSource="{Binding Illusts}" MaxHeightChanged="FlowLayout_MaxHeightChanged"
|
||||
HorizontalOptions="Fill" Column="{Binding Columns}"
|
||||
Margin="16" RowSpacing="16" ColumnSpacing="16"
|
||||
ItemTemplate="{StaticResource cardView}"/>
|
||||
<ActivityIndicator x:Name="activityBottomLoading" Margin="0, -10, 0, 16"
|
||||
IsRunning="{Binding IsBottomLoading}"
|
||||
IsVisible="{Binding IsBottomLoading}"/>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</Grid>
|
||||
</i:IllustRecommendsCollectionPage>
|
120
Gallery/Illust/RelatedIllustsPage.xaml.cs
Normal file
120
Gallery/Illust/RelatedIllustsPage.xaml.cs
Normal file
@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Gallery.UI;
|
||||
using Gallery.Utils;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Gallery.Illust
|
||||
{
|
||||
public partial class RelatedIllustsPage : IllustRecommendsCollectionPage
|
||||
{
|
||||
private const int STEP = 18;
|
||||
|
||||
private readonly IllustItem illustItem;
|
||||
private int startIndex;
|
||||
private int nextIndex;
|
||||
private string[] illustIds;
|
||||
|
||||
public RelatedIllustsPage(IllustItem item)
|
||||
{
|
||||
illustItem = item;
|
||||
|
||||
Resources.Add("cardView", GetCardViewTemplate());
|
||||
InitializeComponent();
|
||||
|
||||
startIndex = -1;
|
||||
nextIndex = 0;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
if (startIndex != -1 && Illusts == null)
|
||||
{
|
||||
startIndex = -1;
|
||||
}
|
||||
base.OnAppearing();
|
||||
}
|
||||
|
||||
protected override bool IsAutoReload => false;
|
||||
protected override ActivityIndicator LoadingIndicator => activityLoading;
|
||||
|
||||
protected override IEnumerable<IllustItem> DoGetIllustList(IllustRecommendsData data, out int tag)
|
||||
{
|
||||
tag = startIndex;
|
||||
if (data.body == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return data.body.illusts.Where(i => i.url != null).Select(i => i.ConvertToItem());
|
||||
}
|
||||
|
||||
protected override IllustRecommendsData DoLoadIllustData(bool force)
|
||||
{
|
||||
IllustRecommendsData data;
|
||||
if (startIndex < 0)
|
||||
{
|
||||
// init
|
||||
data = Stores.LoadIllustRecommendsInitData(illustItem.Id);
|
||||
if (data == null || data.body == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
illustIds = data.body.nextIds;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (illustIds == null || startIndex >= illustIds.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var ids = illustIds.Skip(startIndex).Take(STEP).ToArray();
|
||||
data = Stores.LoadIllustRecommendsListData(illustItem.Id, ids);
|
||||
nextIndex = startIndex + STEP;
|
||||
if (ids.Length == 0 || nextIndex >= illustIds.Length)
|
||||
{
|
||||
// done
|
||||
#if DEBUG
|
||||
App.DebugPrint($"download completed: {startIndex}");
|
||||
#endif
|
||||
startIndex = nextIndex;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
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;
|
||||
OnScrolled(y);
|
||||
}
|
||||
|
||||
private async void Refresh_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if (IsLoading)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await ScrollToTopAsync(scrollView);
|
||||
startIndex = -1;
|
||||
nextIndex = 0;
|
||||
illustIds = null;
|
||||
StartLoad(true);
|
||||
}
|
||||
}
|
||||
}
|
44
Gallery/Illust/UserIllustPage.xaml
Executable file
44
Gallery/Illust/UserIllustPage.xaml
Executable file
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<i:IllustUserDataCollectionPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:i="clr-namespace:Gallery.Illust"
|
||||
xmlns:u="clr-namespace:Gallery.UI"
|
||||
x:Class="Gallery.Illust.UserIllustPage"
|
||||
BackgroundColor="{DynamicResource WindowColor}">
|
||||
<Shell.TitleView>
|
||||
<StackLayout Orientation="Horizontal">
|
||||
<u:CircleImage Aspect="AspectFill" Source="{Binding UserIcon}"
|
||||
VerticalOptions="Center"
|
||||
WidthRequest="34" HeightRequest="34" >
|
||||
<u:CircleImage.Margin>
|
||||
<OnPlatform x:TypeArguments="Thickness" Android="0, 5, 0, 5"/>
|
||||
</u:CircleImage.Margin>
|
||||
</u:CircleImage>
|
||||
<Label Text="{Binding Title}" Margin="10, 0, 0, 0"
|
||||
VerticalOptions="Center" LineBreakMode="TailTruncation"
|
||||
TextColor="{DynamicResource TextColor}"/>
|
||||
</StackLayout>
|
||||
</Shell.TitleView>
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Order="Primary" Clicked="Refresh_Clicked"
|
||||
IconImageSource="{DynamicResource FontIconRefresh}"/>
|
||||
</ContentPage.ToolbarItems>
|
||||
<Grid>
|
||||
<ScrollView x:Name="scrollView" Scrolled="ScrollView_Scrolled"
|
||||
HorizontalOptions="Fill" HorizontalScrollBarVisibility="Never">
|
||||
<StackLayout>
|
||||
<ActivityIndicator x:Name="activityLoading" Margin="0, -40, 0, 0"
|
||||
HeightRequest="40"
|
||||
IsRunning="{Binding IsLoading}"
|
||||
IsVisible="{Binding IsLoading}"/>
|
||||
<u:FlowLayout ItemsSource="{Binding Illusts}" MaxHeightChanged="FlowLayout_MaxHeightChanged"
|
||||
HorizontalOptions="Fill" Column="{Binding Columns}"
|
||||
Margin="16" RowSpacing="16" ColumnSpacing="16"
|
||||
ItemTemplate="{StaticResource cardView}"/>
|
||||
<ActivityIndicator x:Name="activityBottomLoading" Margin="0, -10, 0, 16"
|
||||
IsRunning="{Binding IsBottomLoading}"
|
||||
IsVisible="{Binding IsBottomLoading}"/>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</Grid>
|
||||
</i:IllustUserDataCollectionPage>
|
133
Gallery/Illust/UserIllustPage.xaml.cs
Executable file
133
Gallery/Illust/UserIllustPage.xaml.cs
Executable file
@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Gallery.UI;
|
||||
using Gallery.Utils;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Gallery.Illust
|
||||
{
|
||||
public partial class UserIllustPage : IllustUserDataCollectionPage
|
||||
{
|
||||
private const int STEP = 48;
|
||||
|
||||
public static readonly BindableProperty UserIconProperty = BindableProperty.Create(
|
||||
nameof(UserIcon), typeof(ImageSource), typeof(UserIllustPage));
|
||||
|
||||
public ImageSource UserIcon
|
||||
{
|
||||
get => (ImageSource)GetValue(UserIconProperty);
|
||||
set => SetValue(UserIconProperty, value);
|
||||
}
|
||||
|
||||
public IIllustItem UserItem { get; }
|
||||
|
||||
private int startIndex;
|
||||
private int nextIndex;
|
||||
private string[] illustIds;
|
||||
|
||||
public UserIllustPage(IIllustItem item)
|
||||
{
|
||||
UserItem = item;
|
||||
UserIcon = item.ProfileImage;
|
||||
Title = item.UserName;
|
||||
|
||||
Resources.Add("cardView", GetCardViewTemplate(true));
|
||||
InitializeComponent();
|
||||
|
||||
startIndex = -1;
|
||||
nextIndex = 0;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
if (startIndex != -1 && Illusts == null)
|
||||
{
|
||||
startIndex = -1;
|
||||
}
|
||||
base.OnAppearing();
|
||||
}
|
||||
|
||||
protected override bool IsAutoReload => false;
|
||||
protected override ActivityIndicator LoadingIndicator => activityLoading;
|
||||
|
||||
protected override IEnumerable<IllustItem> DoGetIllustList(IllustUserData data, out int tag)
|
||||
{
|
||||
tag = startIndex;
|
||||
if (data.body == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return data.body.works.Select(i => i.Value?.ConvertToItem());
|
||||
}
|
||||
|
||||
protected override IllustUserData DoLoadIllustData(bool force)
|
||||
{
|
||||
var userId = UserItem.UserId;
|
||||
if (startIndex < 0)
|
||||
{
|
||||
var init = Stores.LoadIllustUserInitData(userId);
|
||||
if (init == null || init.body == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
illustIds = init.body.illusts.Keys.OrderByDescending(i => int.TryParse(i, out int id) ? id : -1).ToArray();
|
||||
#if DEBUG
|
||||
App.DebugPrint($"user has ({illustIds.Length}) illusts.");
|
||||
#endif
|
||||
startIndex = 0;
|
||||
}
|
||||
|
||||
if (illustIds == null || startIndex >= illustIds.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var ids = illustIds.Skip(startIndex).Take(STEP).ToArray();
|
||||
var data = Stores.LoadIllustUserData(userId, ids, startIndex == 0);
|
||||
nextIndex = startIndex + STEP;
|
||||
if (ids.Length == 0 || nextIndex >= illustIds.Length)
|
||||
{
|
||||
// done
|
||||
#if DEBUG
|
||||
App.DebugPrint($"download completed: {startIndex}");
|
||||
#endif
|
||||
startIndex = nextIndex;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
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;
|
||||
OnScrolled(y);
|
||||
}
|
||||
|
||||
private async void Refresh_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if (IsLoading)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await ScrollToTopAsync(scrollView);
|
||||
startIndex = -1;
|
||||
nextIndex = 0;
|
||||
illustIds = null;
|
||||
StartLoad(true);
|
||||
}
|
||||
}
|
||||
}
|
66
Gallery/Illust/ViewIllustPage.xaml
Executable file
66
Gallery/Illust/ViewIllustPage.xaml
Executable file
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<u:AdaptedPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:u="clr-namespace:Gallery.UI"
|
||||
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
|
||||
x:Class="Gallery.Illust.ViewIllustPage"
|
||||
ios:Page.UseSafeArea="False"
|
||||
Shell.TabBarIsVisible="False"
|
||||
BackgroundColor="{DynamicResource WindowColor}">
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Order="Primary" Clicked="Favorite_Clicked"
|
||||
IconImageSource="{Binding FavoriteIcon}"/>
|
||||
<ToolbarItem Order="Primary" Clicked="Refresh_Clicked"
|
||||
IconImageSource="{DynamicResource FontIconRefresh}"/>
|
||||
<ToolbarItem Order="Primary" Clicked="More_Clicked"
|
||||
IconImageSource="{DynamicResource FontIconMore}"/>
|
||||
</ContentPage.ToolbarItems>
|
||||
<Grid RowSpacing="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<CarouselView ItemsSource="{Binding Illusts}" HorizontalScrollBarVisibility="Never"
|
||||
Position="{Binding CurrentPage}"
|
||||
ItemTemplate="{StaticResource carouselView}"
|
||||
IsScrollAnimated="{Binding IsScrollAnimated}">
|
||||
<CarouselView.ItemsLayout>
|
||||
<LinearItemsLayout Orientation="Vertical" ItemSpacing="20"/>
|
||||
</CarouselView.ItemsLayout>
|
||||
</CarouselView>
|
||||
|
||||
<Grid Margin="{Binding PageTopMargin}" VerticalOptions="Start">
|
||||
<ProgressBar x:Name="progress" IsVisible="{Binding ProgressVisible}"
|
||||
Progress="0.05">
|
||||
<ProgressBar.Margin>
|
||||
<OnPlatform x:TypeArguments="Thickness" Android="0, -6, 0, 0"/>
|
||||
</ProgressBar.Margin>
|
||||
</ProgressBar>
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="{Binding PageTopMargin}" IsVisible="{Binding IsPageVisible}"
|
||||
HorizontalOptions="End" VerticalOptions="Start">
|
||||
<u:RoundLabel Text="{Binding PagePositionText}"
|
||||
BackgroundColor="{DynamicResource MaskColor}" Margin="0, 6, 6, 0"
|
||||
Padding="6, 4" CornerRadius="6"
|
||||
FontSize="Micro" TextColor="White"/>
|
||||
</Grid>
|
||||
|
||||
<u:RoundLabel Text="{Binding AnimeStatus}"
|
||||
BackgroundColor="{DynamicResource MaskColor}" Margin="0, 0, 6, 6"
|
||||
Padding="13, 12, 0, 0" CornerRadius="22"
|
||||
WidthRequest="44" HeightRequest="44"
|
||||
HorizontalOptions="End" VerticalOptions="End"
|
||||
FontFamily="{DynamicResource IconSolidFontFamily}"
|
||||
FontSize="20" TextColor="White"
|
||||
IsVisible="{Binding IsAnimateSliderVisible}"/>
|
||||
|
||||
<Slider Grid.Row="1" VerticalOptions="End"
|
||||
Margin="{DynamicResource ScreenBottomPadding}"
|
||||
MinimumTrackColor="{DynamicResource TintColor}"
|
||||
IsEnabled="{Binding IsAnimateSliderEnabled}"
|
||||
IsVisible="{Binding IsAnimateSliderVisible}"
|
||||
Value="{Binding CurrentAnimeFrame, Mode=TwoWay}"
|
||||
Maximum="{Binding MaximumFrame}"/>
|
||||
</Grid>
|
||||
</u:AdaptedPage>
|
897
Gallery/Illust/ViewIllustPage.xaml.cs
Executable file
897
Gallery/Illust/ViewIllustPage.xaml.cs
Executable file
@ -0,0 +1,897 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Gallery.Resources;
|
||||
using Gallery.UI;
|
||||
using Gallery.UI.Theme;
|
||||
using Gallery.Utils;
|
||||
using Xamarin.Essentials;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Gallery.Illust
|
||||
{
|
||||
[QueryProperty("IllustId", "id")]
|
||||
public partial class ViewIllustPage : AdaptedPage
|
||||
{
|
||||
#region - Properties -
|
||||
public static readonly BindableProperty FavoriteIconProperty = BindableProperty.Create(
|
||||
nameof(FavoriteIcon), typeof(ImageSource), typeof(ViewIllustPage));
|
||||
|
||||
public ImageSource FavoriteIcon
|
||||
{
|
||||
get => (ImageSource)GetValue(FavoriteIconProperty);
|
||||
set => SetValue(FavoriteIconProperty, value);
|
||||
}
|
||||
|
||||
public static readonly BindableProperty IllustsProperty = BindableProperty.Create(
|
||||
nameof(Illusts), typeof(IllustDetailItem[]), typeof(ViewIllustPage));
|
||||
public static readonly BindableProperty IsPageVisibleProperty = BindableProperty.Create(
|
||||
nameof(IsPageVisible), typeof(bool), typeof(ViewIllustPage));
|
||||
public static readonly BindableProperty PagePositionTextProperty = BindableProperty.Create(
|
||||
nameof(PagePositionText), typeof(string), typeof(ViewIllustPage));
|
||||
public static readonly BindableProperty CurrentPageProperty = BindableProperty.Create(
|
||||
nameof(CurrentPage), typeof(int), typeof(ViewIllustPage), propertyChanged: OnCurrentPagePropertyChanged);
|
||||
public static readonly BindableProperty IsScrollAnimatedProperty = BindableProperty.Create(
|
||||
nameof(IsScrollAnimated), typeof(bool), typeof(ViewIllustPage), true);
|
||||
|
||||
public IllustDetailItem[] Illusts
|
||||
{
|
||||
get => (IllustDetailItem[])GetValue(IllustsProperty);
|
||||
set => SetValue(IllustsProperty, value);
|
||||
}
|
||||
public bool IsPageVisible
|
||||
{
|
||||
get => (bool)GetValue(IsPageVisibleProperty);
|
||||
set => SetValue(IsPageVisibleProperty, value);
|
||||
}
|
||||
public string PagePositionText
|
||||
{
|
||||
get => (string)GetValue(PagePositionTextProperty);
|
||||
set => SetValue(PagePositionTextProperty, value);
|
||||
}
|
||||
public int CurrentPage
|
||||
{
|
||||
get => (int)GetValue(CurrentPageProperty);
|
||||
set => SetValue(CurrentPageProperty, value);
|
||||
}
|
||||
public bool IsScrollAnimated
|
||||
{
|
||||
get => (bool)GetValue(IsScrollAnimatedProperty);
|
||||
set => SetValue(IsScrollAnimatedProperty, value);
|
||||
}
|
||||
|
||||
private static void OnCurrentPagePropertyChanged(BindableObject obj, object old, object @new)
|
||||
{
|
||||
var page = (ViewIllustPage)obj;
|
||||
var index = (int)@new;
|
||||
var items = page.Illusts;
|
||||
var length = items.Length;
|
||||
page.PagePositionText = $"{index + 1}/{length}";
|
||||
}
|
||||
|
||||
public static readonly BindableProperty AnimeStatusProperty = BindableProperty.Create(
|
||||
nameof(AnimeStatus), typeof(string), typeof(ViewIllustPage), StyleDefinition.IconPlay);
|
||||
public static readonly BindableProperty IsAnimateSliderVisibleProperty = BindableProperty.Create(
|
||||
nameof(IsAnimateSliderVisible), typeof(bool), typeof(ViewIllustPage));
|
||||
public static readonly BindableProperty IsAnimateSliderEnabledProperty = BindableProperty.Create(
|
||||
nameof(IsAnimateSliderEnabled), typeof(bool), typeof(ViewIllustPage));
|
||||
public static readonly BindableProperty CurrentAnimeFrameProperty = BindableProperty.Create(
|
||||
nameof(CurrentAnimeFrame), typeof(double), typeof(ViewIllustPage), propertyChanged: OnCurrentAnimeFramePropertyChanged);
|
||||
public static readonly BindableProperty MaximumFrameProperty = BindableProperty.Create(
|
||||
nameof(MaximumFrame), typeof(double), typeof(ViewIllustPage), 1.0);
|
||||
|
||||
private static void OnCurrentAnimeFramePropertyChanged(BindableObject obj, object old, object @new)
|
||||
{
|
||||
var page = (ViewIllustPage)obj;
|
||||
if (page.ugoira != null && page.IsAnimateSliderEnabled)
|
||||
{
|
||||
var frame = (double)@new;
|
||||
page.ugoira.ToggleFrame((int)frame);
|
||||
}
|
||||
}
|
||||
|
||||
public string AnimeStatus
|
||||
{
|
||||
get => (string)GetValue(AnimeStatusProperty);
|
||||
set => SetValue(AnimeStatusProperty, value);
|
||||
}
|
||||
public bool IsAnimateSliderVisible
|
||||
{
|
||||
get => (bool)GetValue(IsAnimateSliderVisibleProperty);
|
||||
set => SetValue(IsAnimateSliderVisibleProperty, value);
|
||||
}
|
||||
public bool IsAnimateSliderEnabled
|
||||
{
|
||||
get => (bool)GetValue(IsAnimateSliderEnabledProperty);
|
||||
set => SetValue(IsAnimateSliderEnabledProperty, value);
|
||||
}
|
||||
public double CurrentAnimeFrame
|
||||
{
|
||||
get => (double)GetValue(CurrentAnimeFrameProperty);
|
||||
set => SetValue(CurrentAnimeFrameProperty, value);
|
||||
}
|
||||
public double MaximumFrame
|
||||
{
|
||||
get => (double)GetValue(MaximumFrameProperty);
|
||||
set => SetValue(MaximumFrameProperty, value);
|
||||
}
|
||||
|
||||
public static readonly BindableProperty ProgressVisibleProperty = BindableProperty.Create(
|
||||
nameof(ProgressVisible), typeof(bool), typeof(ViewIllustPage));
|
||||
|
||||
public bool ProgressVisible
|
||||
{
|
||||
get => (bool)GetValue(ProgressVisibleProperty);
|
||||
set => SetValue(ProgressVisibleProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public IllustItem IllustItem { get; private set; }
|
||||
|
||||
private readonly ImageSource fontIconLove;
|
||||
private readonly ImageSource fontIconNotLove;
|
||||
private readonly ImageSource fontIconCircleLove;
|
||||
private bool favoriteChanged;
|
||||
private IllustUgoiraData ugoiraData;
|
||||
private Ugoira ugoira;
|
||||
private ParallelTask task;
|
||||
|
||||
private readonly object sync = new object();
|
||||
private int downloaded = 0;
|
||||
private int pageCount;
|
||||
private bool isPreloading;
|
||||
|
||||
public ViewIllustPage(IllustItem illust)
|
||||
{
|
||||
IllustItem = illust;
|
||||
|
||||
fontIconLove = (ImageSource)Application.Current.Resources[ThemeBase.FontIconLove];
|
||||
fontIconNotLove = (ImageSource)Application.Current.Resources[ThemeBase.FontIconNotLove];
|
||||
fontIconCircleLove = (ImageSource)Application.Current.Resources[ThemeBase.FontIconCircleLove];
|
||||
|
||||
if (illust != null)
|
||||
{
|
||||
pageCount = illust.PageCount;
|
||||
RefreshInformation(illust, pageCount);
|
||||
}
|
||||
Resources.Add("carouselView", GetCarouseTemplate());
|
||||
|
||||
BindingContext = this;
|
||||
InitializeComponent();
|
||||
|
||||
if (illust != null)
|
||||
{
|
||||
LoadIllust(illust);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
Screen.SetHomeIndicatorAutoHidden(Shell.Current, true);
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
Screen.SetHomeIndicatorAutoHidden(Shell.Current, false);
|
||||
base.OnDisappearing();
|
||||
|
||||
if (ugoira != null)
|
||||
{
|
||||
IllustItem.IsPlaying = false;
|
||||
ugoira.FrameChanged -= OnUgoiraFrameChanged;
|
||||
ugoira.TogglePlay(false);
|
||||
ugoira.Dispose();
|
||||
ugoira = null;
|
||||
}
|
||||
|
||||
if (favoriteChanged)
|
||||
{
|
||||
Stores.SaveFavoritesIllusts();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnUnload()
|
||||
{
|
||||
if (task != null)
|
||||
{
|
||||
task.Dispose();
|
||||
task = null;
|
||||
}
|
||||
}
|
||||
|
||||
#if __IOS__
|
||||
protected override void OnPageTopMarginChanged(Thickness old, Thickness @new)
|
||||
{
|
||||
var illusts = Illusts;
|
||||
if (illusts != null)
|
||||
{
|
||||
for (var i = 0; i < illusts.Length; i++)
|
||||
{
|
||||
illusts[i].TopPadding = @new;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private DataTemplate GetCarouseTemplate()
|
||||
{
|
||||
return new DataTemplate(() =>
|
||||
{
|
||||
// image
|
||||
var image = new Image
|
||||
{
|
||||
HorizontalOptions = LayoutOptions.Fill,
|
||||
VerticalOptions = LayoutOptions.Fill,
|
||||
Aspect = Aspect.AspectFit
|
||||
}
|
||||
.Binding(Image.SourceProperty, nameof(IllustDetailItem.Image));
|
||||
|
||||
// downloading
|
||||
var downloading = new Frame
|
||||
{
|
||||
HasShadow = false,
|
||||
Margin = default,
|
||||
Padding = new Thickness(20),
|
||||
CornerRadius = 8,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
Content = new ActivityIndicator()
|
||||
.Binding(IsVisibleProperty, nameof(IllustDetailItem.Loading))
|
||||
.Binding(ActivityIndicator.IsRunningProperty, nameof(IllustDetailItem.Loading))
|
||||
.DynamicResource(ActivityIndicator.ColorProperty, ThemeBase.WindowColor)
|
||||
}
|
||||
.Binding(IsVisibleProperty, nameof(IllustDetailItem.Loading))
|
||||
.DynamicResource(BackgroundColorProperty, ThemeBase.MaskColor);
|
||||
|
||||
// loading original
|
||||
var original = new ActivityIndicator
|
||||
{
|
||||
Margin = new Thickness(10),
|
||||
HorizontalOptions = LayoutOptions.Start,
|
||||
VerticalOptions = LayoutOptions.Start
|
||||
}
|
||||
.Binding(IsVisibleProperty, nameof(IllustDetailItem.Downloading))
|
||||
.Binding(ActivityIndicator.IsRunningProperty, nameof(IllustDetailItem.Downloading))
|
||||
.DynamicResource(ActivityIndicator.ColorProperty, ThemeBase.TextColor);
|
||||
|
||||
var tap = new TapGestureRecognizer();
|
||||
tap.Tapped += Image_Tapped;
|
||||
var tapPrevious = new TapGestureRecognizer();
|
||||
tapPrevious.Tapped += TapPrevious_Tapped;
|
||||
var tapNext = new TapGestureRecognizer();
|
||||
tapNext.Tapped += TapNext_Tapped;
|
||||
|
||||
var grid = new Grid
|
||||
{
|
||||
Children =
|
||||
{
|
||||
// image
|
||||
image,
|
||||
|
||||
// tap holder
|
||||
new Grid
|
||||
{
|
||||
RowDefinitions =
|
||||
{
|
||||
new RowDefinition { Height = new GridLength(.3, GridUnitType.Star) },
|
||||
new RowDefinition { Height = new GridLength(.4, GridUnitType.Star) },
|
||||
new RowDefinition { Height = new GridLength(.3, GridUnitType.Star) }
|
||||
},
|
||||
Children =
|
||||
{
|
||||
new Label { GestureRecognizers = { tapPrevious } },
|
||||
new Label { GestureRecognizers = { tap } }.GridRow(1),
|
||||
new Label { GestureRecognizers = { tapNext } }.GridRow(2)
|
||||
}
|
||||
},
|
||||
|
||||
// downloading
|
||||
downloading,
|
||||
|
||||
// loading original
|
||||
original
|
||||
}
|
||||
};
|
||||
#if __IOS__
|
||||
grid.SetBinding(Xamarin.Forms.Layout.PaddingProperty, nameof(IllustDetailItem.TopPadding));
|
||||
#endif
|
||||
return grid;
|
||||
});
|
||||
}
|
||||
|
||||
private void LoadIllust(IllustItem illust)
|
||||
{
|
||||
if (illust == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var items = new IllustDetailItem[illust.PageCount];
|
||||
|
||||
#if __IOS__
|
||||
var topMargin = PageTopMargin;
|
||||
#endif
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
items[i] = new IllustDetailItem
|
||||
{
|
||||
#if __IOS__
|
||||
TopPadding = topMargin,
|
||||
#endif
|
||||
Id = illust.Id
|
||||
};
|
||||
if (i == 0)
|
||||
{
|
||||
items[i].Loading = true;
|
||||
items[i].Image = illust.Image;
|
||||
}
|
||||
}
|
||||
|
||||
Illusts = items;
|
||||
Task.Run(() => DoLoadImages());
|
||||
}
|
||||
|
||||
private void DoLoadImages(bool force = false)
|
||||
{
|
||||
var illustItem = IllustItem;
|
||||
var pages = Stores.LoadIllustPageData(illustItem.Id, out string error, force);
|
||||
if (pages == null)
|
||||
{
|
||||
App.DebugError("illustPage.load", $"failed to load illust page data, id: {illustItem.Id}");
|
||||
if (error != null)
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
DisplayAlert(ResourceHelper.Title, error, ResourceHelper.Ok);
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
var items = Illusts;
|
||||
var reload = false;
|
||||
if (pages.body.Length > items.Length)
|
||||
{
|
||||
#if DEBUG
|
||||
App.DebugPrint($"local page count ({items.Length}) is not equals the remote one ({pages.body.Length})");
|
||||
#endif
|
||||
var tmp = new IllustDetailItem[pages.body.Length];
|
||||
items.CopyTo(items, 0);
|
||||
#if __IOS__
|
||||
var topMargin = PageTopMargin;
|
||||
#endif
|
||||
for (var i = items.Length; i < tmp.Length; i++)
|
||||
{
|
||||
tmp[i] = new IllustDetailItem
|
||||
{
|
||||
Id = illustItem.Id,
|
||||
#if __IOS__
|
||||
TopPadding = topMargin,
|
||||
#endif
|
||||
Loading = i == 0
|
||||
};
|
||||
}
|
||||
Illusts = items = tmp;
|
||||
reload = true;
|
||||
}
|
||||
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
var item = items[i];
|
||||
var p = pages.body[i];
|
||||
item.PreviewUrl = p.urls.regular;
|
||||
item.OriginalUrl = p.urls.original;
|
||||
|
||||
if (i == 0 && illustItem.ImageUrl == null)
|
||||
{
|
||||
// maybe open from a link
|
||||
reload = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (reload)
|
||||
{
|
||||
DoForcePreload(false);
|
||||
}
|
||||
|
||||
if (task != null)
|
||||
{
|
||||
task.Dispose();
|
||||
task = null;
|
||||
}
|
||||
task = ParallelTask.Start("view.illusts", 0, items.Length, 2, i =>
|
||||
{
|
||||
DoLoadImage(i);
|
||||
return true;
|
||||
});
|
||||
|
||||
if (illustItem.IsAnimeVisible)
|
||||
{
|
||||
// anime
|
||||
ugoiraData = Stores.LoadIllustUgoiraData(illustItem.Id);
|
||||
if (ugoiraData != null && ugoiraData.body != null)
|
||||
{
|
||||
var length = ugoiraData.body.frames.Length;
|
||||
MaximumFrame = length > 0 ? length : 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DoLoadImage(int index, bool force = false)
|
||||
{
|
||||
var items = Illusts;
|
||||
if (index < 0 || (!force && index >= items.Length))
|
||||
{
|
||||
App.DebugPrint($"invalid index: {index}");
|
||||
return;
|
||||
}
|
||||
|
||||
var item = items[index];
|
||||
if (index > 0 && !force)
|
||||
{
|
||||
if (item.Loading || item.Image != null)
|
||||
{
|
||||
#if DEBUG
|
||||
App.DebugPrint($"skipped, loading or already loaded, index: {index}, loading: {item.Loading}");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
item.Loading = true;
|
||||
var image = Stores.LoadPreviewImage(item.PreviewUrl, true, IllustItem.Id, force: force);
|
||||
if (image != null)
|
||||
{
|
||||
item.Image = image;
|
||||
if(index == 0)
|
||||
{
|
||||
IllustItem.Image = image;
|
||||
}
|
||||
}
|
||||
item.Loading = false;
|
||||
RefreshProgress();
|
||||
}
|
||||
|
||||
private void RefreshProgress()
|
||||
{
|
||||
if (pageCount <= 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (sync)
|
||||
{
|
||||
downloaded++;
|
||||
}
|
||||
if (downloaded >= pageCount)
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
ViewExtensions.CancelAnimations(progress);
|
||||
await progress.ProgressTo(1, 250, Easing.CubicIn);
|
||||
await progress.FadeTo(0, easing: Easing.CubicIn);
|
||||
ProgressVisible = false;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var val = downloaded / (float)pageCount;
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
ViewExtensions.CancelAnimations(progress);
|
||||
progress.ProgressTo(val, 250, Easing.CubicIn);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void TapPrevious_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
var index = CurrentPage;
|
||||
if (index > 0)
|
||||
{
|
||||
IsScrollAnimated = false;
|
||||
CurrentPage = index - 1;
|
||||
IsScrollAnimated = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void TapNext_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
var index = CurrentPage;
|
||||
var illusts = Illusts;
|
||||
if (illusts != null && index < illusts.Length - 1)
|
||||
{
|
||||
IsScrollAnimated = false;
|
||||
CurrentPage = index + 1;
|
||||
IsScrollAnimated = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async void Favorite_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
var favorites = Stores.Favorites;
|
||||
var illust = IllustItem;
|
||||
var index = favorites.FindIndex(i => i.Id == illust.Id);
|
||||
var bookmarkId = illust.BookmarkId;
|
||||
bool add = index < 0 && bookmarkId == null;
|
||||
if (add)
|
||||
{
|
||||
illust.IsFavorite = true;
|
||||
illust.FavoriteDateUtc = DateTime.UtcNow;
|
||||
favorites.Insert(0, illust);
|
||||
FavoriteIcon = fontIconLove;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (index >= 0)
|
||||
{
|
||||
var item = favorites[index];
|
||||
if (bookmarkId == null && item.BookmarkId != null)
|
||||
{
|
||||
bookmarkId = item.BookmarkId;
|
||||
illust.BookmarkId = bookmarkId;
|
||||
}
|
||||
favorites.RemoveAt(index);
|
||||
}
|
||||
illust.IsFavorite = false;
|
||||
FavoriteIcon = bookmarkId == null ?
|
||||
fontIconNotLove :
|
||||
fontIconCircleLove;
|
||||
}
|
||||
favoriteChanged = true;
|
||||
|
||||
if (Configs.SyncFavType == SyncType.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (Configs.Cookie == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!add && string.IsNullOrEmpty(bookmarkId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (Configs.SyncFavType == SyncType.Prompt)
|
||||
{
|
||||
var ok = await DisplayAlert(
|
||||
ResourceHelper.Title,
|
||||
ResourceHelper.ConfirmSyncFavorite,
|
||||
ResourceHelper.Yes,
|
||||
ResourceHelper.No);
|
||||
if (!ok)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (add)
|
||||
{
|
||||
var id = await Task.Run(() => Stores.AddBookmark(illust.Id));
|
||||
if (id != null)
|
||||
{
|
||||
bookmarkId = id;
|
||||
illust.BookmarkId = bookmarkId;
|
||||
FavoriteIcon = fontIconCircleLove;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = await Task.Run(() => Stores.DeleteBookmark(bookmarkId));
|
||||
if (result)
|
||||
{
|
||||
FavoriteIcon = fontIconNotLove;
|
||||
}
|
||||
}
|
||||
|
||||
// immediately save after changing remote
|
||||
Stores.SaveFavoritesIllusts();
|
||||
favoriteChanged = false;
|
||||
}
|
||||
|
||||
private void Image_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
if (ugoiraData == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var illustItem = IllustItem;
|
||||
|
||||
if (ugoira != null)
|
||||
{
|
||||
var playing = !ugoira.IsPlaying;
|
||||
AnimeStatus = playing ? StyleDefinition.IconPause : StyleDefinition.IconPlay;
|
||||
IsAnimateSliderEnabled = !playing;
|
||||
ugoira.TogglePlay(playing);
|
||||
illustItem.IsPlaying = playing;
|
||||
}
|
||||
else if (((VisualElement)sender).BindingContext is IllustDetailItem item)
|
||||
{
|
||||
if (illustItem.IsPlaying || !illustItem.IsAnimeVisible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ugoira = new Ugoira(ugoiraData, item);
|
||||
ugoira.FrameChanged += OnUgoiraFrameChanged;
|
||||
AnimeStatus = StyleDefinition.IconPause;
|
||||
illustItem.IsPlaying = true;
|
||||
ugoira.TogglePlay(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUgoiraFrameChanged(object sender, UgoiraEventArgs e)
|
||||
{
|
||||
e.DetailItem.Image = e.Image;
|
||||
CurrentAnimeFrame = e.FrameIndex;
|
||||
}
|
||||
|
||||
private void RefreshInformation(IllustItem item, int count)
|
||||
{
|
||||
Title = item.Title;
|
||||
FavoriteIcon = item.BookmarkId != null ?
|
||||
fontIconCircleLove :
|
||||
Stores.Favorites.Any(i => i.Id == item.Id) ?
|
||||
fontIconLove :
|
||||
fontIconNotLove;
|
||||
IsAnimateSliderVisible = item.IsAnimeVisible;
|
||||
if (count > 1)
|
||||
{
|
||||
IsPageVisible = true;
|
||||
ProgressVisible = true;
|
||||
PagePositionText = $"{CurrentPage + 1}/{count}";
|
||||
}
|
||||
}
|
||||
|
||||
private void DoForcePreload(bool force)
|
||||
{
|
||||
isPreloading = true;
|
||||
var illustItem = IllustItem;
|
||||
if (force)
|
||||
{
|
||||
var illusts = Illusts;
|
||||
var currentPage = CurrentPage;
|
||||
if (currentPage >= 0 && illusts != null && currentPage < illusts.Length)
|
||||
{
|
||||
illusts[currentPage].Loading = true;
|
||||
}
|
||||
}
|
||||
// force to reload
|
||||
var preload = Stores.LoadIllustPreloadData(illustItem.Id, true, force: force);
|
||||
if (preload != null && preload.illust.TryGetValue(illustItem.Id, out var illust))
|
||||
{
|
||||
illust.CopyToItem(illustItem);
|
||||
pageCount = illustItem.PageCount;
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
RefreshInformation(illustItem, pageCount);
|
||||
});
|
||||
if (preload.user.TryGetValue(illust.userId, out var user))
|
||||
{
|
||||
illustItem.ProfileUrl = user.image;
|
||||
}
|
||||
}
|
||||
isPreloading = false;
|
||||
}
|
||||
|
||||
private void Refresh_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if (isPreloading)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Task.Run(() =>
|
||||
{
|
||||
DoForcePreload(true);
|
||||
DoLoadImage(CurrentPage, true);
|
||||
});
|
||||
}
|
||||
|
||||
private async void More_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
int p = CurrentPage;
|
||||
var illusts = Illusts;
|
||||
if (illusts == null || p < 0 || p >= illusts.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = illusts[p];
|
||||
List<string> extras = new List<string>();
|
||||
|
||||
var share = ResourceHelper.Share;
|
||||
var preview = Stores.GetPreviewImagePath(item.PreviewUrl);
|
||||
if (preview != null)
|
||||
{
|
||||
extras.Add(share);
|
||||
}
|
||||
|
||||
var userDetail = ResourceHelper.UserDetail;
|
||||
extras.Add(userDetail);
|
||||
|
||||
var related = ResourceHelper.RelatedIllusts;
|
||||
extras.Add(related);
|
||||
|
||||
#if __IOS__
|
||||
var exportVideo = ResourceHelper.ExportVideo;
|
||||
if (IsAnimateSliderVisible)
|
||||
{
|
||||
extras.Add(exportVideo);
|
||||
}
|
||||
#endif
|
||||
|
||||
var saveOriginal = ResourceHelper.SaveOriginal;
|
||||
var illustItem = IllustItem;
|
||||
var result = await DisplayActionSheet(
|
||||
$"{illustItem.Title} (id: {illustItem.Id})",
|
||||
ResourceHelper.Cancel,
|
||||
saveOriginal,
|
||||
extras.ToArray());
|
||||
|
||||
if (result == saveOriginal)
|
||||
{
|
||||
SaveOriginalImage(item);
|
||||
}
|
||||
else if (result == share)
|
||||
{
|
||||
await Share.RequestAsync(new ShareFileRequest
|
||||
{
|
||||
Title = illustItem.Title,
|
||||
File = new ShareFile(preview)
|
||||
});
|
||||
}
|
||||
else if (result == userDetail)
|
||||
{
|
||||
var page = new UserIllustPage(illustItem);
|
||||
await Navigation.PushAsync(page);
|
||||
}
|
||||
else if (result == related)
|
||||
{
|
||||
var page = new RelatedIllustsPage(illustItem);
|
||||
await Navigation.PushAsync(page);
|
||||
}
|
||||
#if __IOS__
|
||||
else if (result == exportVideo)
|
||||
{
|
||||
ExportVideo();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if __IOS__
|
||||
private async void ExportVideo()
|
||||
{
|
||||
string msg = ResourceHelper.CantExportVideo;
|
||||
|
||||
if (ugoira != null && ugoiraData != null && ugoiraData.body != null)
|
||||
{
|
||||
if (Stores.CheckUgoiraVideo(ugoiraData.body.originalSrc))
|
||||
{
|
||||
var flag = await DisplayAlert(ResourceHelper.Operation,
|
||||
ResourceHelper.AlreadySavedVideo,
|
||||
ResourceHelper.Yes,
|
||||
ResourceHelper.No);
|
||||
if (!flag)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var status = await Permissions.CheckStatusAsync<Permissions.Photos>();
|
||||
if (status != PermissionStatus.Granted)
|
||||
{
|
||||
status = await Permissions.RequestAsync<Permissions.Photos>();
|
||||
if (status != PermissionStatus.Granted)
|
||||
{
|
||||
App.DebugPrint("access denied to gallery.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var success = await Task.Run(ugoira.ExportVideo); // ugoira.ExportGif
|
||||
if (success != null)
|
||||
{
|
||||
#if DEBUG
|
||||
msg = ResourceHelper.ExportSuccess;
|
||||
#else
|
||||
var result = await FileStore.SaveVideoToGalleryAsync(success);
|
||||
|
||||
msg = result ?? ResourceHelper.ExportSuccess;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
await DisplayAlert(ResourceHelper.Title, msg, ResourceHelper.Ok);
|
||||
}
|
||||
#endif
|
||||
|
||||
private async void SaveOriginalImage(IllustDetailItem item)
|
||||
{
|
||||
if (Stores.CheckIllustImage(item.OriginalUrl))
|
||||
{
|
||||
var flag = await DisplayAlert(ResourceHelper.Operation,
|
||||
ResourceHelper.AlreadySavedQuestion,
|
||||
ResourceHelper.Yes,
|
||||
ResourceHelper.No);
|
||||
if (!flag)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var status = await Permissions.CheckStatusAsync<Permissions.Photos>();
|
||||
if (status != PermissionStatus.Granted)
|
||||
{
|
||||
status = await Permissions.RequestAsync<Permissions.Photos>();
|
||||
if (status != PermissionStatus.Granted)
|
||||
{
|
||||
App.DebugPrint("access denied to gallery.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (item == null || item.Downloading)
|
||||
{
|
||||
return;
|
||||
}
|
||||
item.Downloading = true;
|
||||
_ = Task.Run(() => DoLoadOriginalImage(item));
|
||||
}
|
||||
|
||||
private void DoLoadOriginalImage(IllustDetailItem item)
|
||||
{
|
||||
var image = Stores.LoadIllustImage(item.OriginalUrl);
|
||||
if (image != null)
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
var result = await FileStore.SaveImageToGalleryAsync(image);
|
||||
|
||||
string message = result ?? ResourceHelper.SaveSuccess;
|
||||
await DisplayAlert(ResourceHelper.Title, message, ResourceHelper.Ok);
|
||||
});
|
||||
}
|
||||
item.Downloading = false;
|
||||
}
|
||||
}
|
||||
|
||||
public class IllustDetailItem : BindableObject
|
||||
{
|
||||
public static readonly BindableProperty ImageProperty = BindableProperty.Create(
|
||||
nameof(Image), typeof(ImageSource), typeof(IllustDetailItem));
|
||||
public static readonly BindableProperty LoadingProperty = BindableProperty.Create(
|
||||
nameof(Loading), typeof(bool), typeof(IllustDetailItem));
|
||||
public static readonly BindableProperty DownloadingProperty = BindableProperty.Create(
|
||||
nameof(Downloading), typeof(bool), typeof(IllustDetailItem));
|
||||
#if __IOS__
|
||||
public static readonly BindableProperty TopPaddingProperty = BindableProperty.Create(
|
||||
nameof(TopPadding), typeof(Thickness), typeof(IllustDetailItem));
|
||||
|
||||
public Thickness TopPadding
|
||||
{
|
||||
get => (Thickness)GetValue(TopPaddingProperty);
|
||||
set => SetValue(TopPaddingProperty, value);
|
||||
}
|
||||
#endif
|
||||
|
||||
public ImageSource Image
|
||||
{
|
||||
get => (ImageSource)GetValue(ImageProperty);
|
||||
set => SetValue(ImageProperty, value);
|
||||
}
|
||||
public bool Loading
|
||||
{
|
||||
get => (bool)GetValue(LoadingProperty);
|
||||
set => SetValue(LoadingProperty, value);
|
||||
}
|
||||
public bool Downloading
|
||||
{
|
||||
get => (bool)GetValue(DownloadingProperty);
|
||||
set => SetValue(DownloadingProperty, value);
|
||||
}
|
||||
public string Id { get; set; }
|
||||
public string PreviewUrl { get; set; }
|
||||
public string OriginalUrl { get; set; }
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user