diff --git a/Pixiview.Android/Pixiview.Android.csproj b/Pixiview.Android/Pixiview.Android.csproj
index 4269866..20a4453 100644
--- a/Pixiview.Android/Pixiview.Android.csproj
+++ b/Pixiview.Android/Pixiview.Android.csproj
@@ -55,7 +55,7 @@
-
+
12.0.3
diff --git a/Pixiview.Android/Properties/AndroidManifest.xml b/Pixiview.Android/Properties/AndroidManifest.xml
index 6df914d..2b2f321 100644
--- a/Pixiview.Android/Properties/AndroidManifest.xml
+++ b/Pixiview.Android/Properties/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/Pixiview.iOS.OpenExtension/Info.plist b/Pixiview.iOS.OpenExtension/Info.plist
index 466947f..3f49fb8 100644
--- a/Pixiview.iOS.OpenExtension/Info.plist
+++ b/Pixiview.iOS.OpenExtension/Info.plist
@@ -14,8 +14,6 @@
6.0
CFBundlePackageType
XPC!
- CFBundleVersion
- 2
MinimumOSVersion
13.4
NSExtension
@@ -31,6 +29,8 @@
com.apple.share-services
CFBundleShortVersionString
- 1.0.511
+ 1.0.513
+ CFBundleVersion
+ 3
diff --git a/Pixiview.iOS/Info.plist b/Pixiview.iOS/Info.plist
index d962109..54c2bf4 100644
--- a/Pixiview.iOS/Info.plist
+++ b/Pixiview.iOS/Info.plist
@@ -79,8 +79,8 @@
CFBundleShortVersionString
- 1.0.511
+ 1.0.513
CFBundleVersion
- 2
+ 3
diff --git a/Pixiview.iOS/Pixiview.iOS.csproj b/Pixiview.iOS/Pixiview.iOS.csproj
index ba7184c..d5e188a 100644
--- a/Pixiview.iOS/Pixiview.iOS.csproj
+++ b/Pixiview.iOS/Pixiview.iOS.csproj
@@ -143,7 +143,7 @@
-
+
diff --git a/Pixiview.iOS/Renderers/SegmentedControlRenderer.cs b/Pixiview.iOS/Renderers/SegmentedControlRenderer.cs
index c56a1ca..d4aacfe 100644
--- a/Pixiview.iOS/Renderers/SegmentedControlRenderer.cs
+++ b/Pixiview.iOS/Renderers/SegmentedControlRenderer.cs
@@ -114,7 +114,7 @@ namespace Pixiview.iOS.Renderers
var tintColor = element.TintColor;
if (tintColor == default)
{
- return UIColor.QuaternaryLabelColor;
+ return UIColor.SystemGray6Color;
}
else
{
diff --git a/Pixiview/Illust/FavoritesPage.xaml.cs b/Pixiview/Illust/FavoritesPage.xaml.cs
index 2dd45f4..75a4c19 100644
--- a/Pixiview/Illust/FavoritesPage.xaml.cs
+++ b/Pixiview/Illust/FavoritesPage.xaml.cs
@@ -5,6 +5,7 @@ using System.Windows.Input;
using Pixiview.Resources;
using Pixiview.Utils;
using Xamarin.Essentials;
+using Xamarin.Forms;
namespace Pixiview.Illust
{
@@ -25,7 +26,11 @@ namespace Pixiview.Illust
protected override void OnAppearing()
{
//base.OnAppearing();
- Reload();
+ Device.StartTimer(TimeSpan.FromMilliseconds(200), () =>
+ {
+ Reload();
+ return false;
+ });
}
protected override IEnumerable DoGetIllustList(IllustItem[] data, ICommand command)
diff --git a/Pixiview/Illust/IllustCollectionPage.cs b/Pixiview/Illust/IllustCollectionPage.cs
index 23569fb..f968fbb 100644
--- a/Pixiview/Illust/IllustCollectionPage.cs
+++ b/Pixiview/Illust/IllustCollectionPage.cs
@@ -15,6 +15,7 @@ namespace Pixiview.Illust
{
public abstract class FavoriteIllustCollectionPage : IllustCollectionPage { }
public abstract class IllustDataCollectionPage : IllustCollectionPage { }
+ public abstract class IllustRankingDataCollectionPage : IllustCollectionPage { }
public abstract class IllustUserDataCollectionPage : IllustCollectionPage { }
public interface IIllustCollectionPage
@@ -132,20 +133,38 @@ namespace Pixiview.Illust
{
base.OnSizeAllocated(width, height);
int columns;
+ var oldMargin = PanelTopMargin;
+ Thickness newMargin;
+ if (StyleDefinition.IsFullscreenDevice)
+ {
+ newMargin = width > height ?
+ AppShell.HalfNavigationBarOffset :
+ AppShell.NavigationBarOffset;
+ }
+ else if (isPhone)
+ {
+ newMargin = width > height ?
+ StyleDefinition.TopOffset16 :
+ StyleDefinition.TopOffset32;
+ }
+ else
+ {
+ // TODO ipad
+ newMargin = StyleDefinition.TopOffset32;
+ }
if (width > height)
{
- PanelTopMargin = StyleDefinition.IsFullscreenDevice ?
- AppShell.HalfNavigationBarOffset :
- StyleDefinition.TopOffset16;
columns = isPhone ? 4 : 6;
}
else
{
- PanelTopMargin = StyleDefinition.IsFullscreenDevice ?
- AppShell.NavigationBarOffset :
- StyleDefinition.TopOffset32;
columns = isPhone ? 2 : 4;
}
+ if (oldMargin != newMargin)
+ {
+ PanelTopMargin = newMargin;
+ OnPanelTopMarginChanged(oldMargin, newMargin);
+ }
if (Columns != columns)
{
Columns = columns;
@@ -162,6 +181,11 @@ namespace Pixiview.Illust
var page = new ViewIllustPage(illust, IsFavoriteVisible);
Navigation.PushAsync(page);
}
+ protected virtual void DoIllustsLoaded(IllustCollection collection)
+ {
+ IllustCollection = collection;
+ Illusts = collection;
+ }
protected void StartLoad(bool force = false)
{
@@ -227,16 +251,33 @@ namespace Pixiview.Illust
.Binding(IsVisibleProperty, nameof(IllustItem.IsPageVisible))
.DynamicResource(Label.FontFamilyProperty, ThemeBase.IconSolidFontFamily);
+ // label: is anime
+ var anime = new RoundLabel
+ {
+ Text = StyleDefinition.IconPlay,
+ BackgroundColor = StyleDefinition.ColorDeepShadow,
+ Margin = new Thickness(0, 0, 6, 6),
+ Padding = new Thickness(12, 9, 0, 0),
+ WidthRequest = 36,
+ HeightRequest = 36,
+ CornerRadius = 18,
+ HorizontalOptions = LayoutOptions.End,
+ VerticalOptions = LayoutOptions.End,
+ FontSize = StyleDefinition.FontSizeTitle,
+ TextColor = Color.White
+ }
+ .Binding(IsVisibleProperty, nameof(IllustItem.IsAnimeVisible))
+ .DynamicResource(Label.FontFamilyProperty, ThemeBase.IconSolidFontFamily);
+
// label: title
var title = new Label
{
Padding = new Thickness(8, 2),
HorizontalOptions = LayoutOptions.FillAndExpand,
+ VerticalOptions = LayoutOptions.Center,
LineBreakMode = LineBreakMode.TailTruncation,
FontSize = StyleDefinition.FontSizeSmall
- }
- .Binding(Label.TextProperty, nameof(IllustItem.Title))
- .DynamicResource(Label.TextColorProperty, ThemeBase.TextColor);
+ }.DynamicResource(Label.TextColorProperty, ThemeBase.TextColor);
// label: favorite
var favorite = new Label
@@ -266,24 +307,34 @@ namespace Pixiview.Illust
Content = new Grid
{
HorizontalOptions = LayoutOptions.Fill,
+ RowSpacing = 0,
RowDefinitions =
{
new RowDefinition().Binding(RowDefinition.HeightProperty, nameof(IllustItem.ImageHeight)),
- new RowDefinition { Height = GridLength.Auto }
+ new RowDefinition { Height = 30 }
},
Children =
{
image,
r18,
pages,
- title,
+ anime,
- // stacklayout: user
- new StackLayout
+ // stacklayout: title
+ new Grid
{
- Orientation = StackOrientation.Horizontal,
+ ColumnDefinitions =
+ {
+ new ColumnDefinition(),
+ new ColumnDefinition { Width = 20 }
+ },
+ VerticalOptions = LayoutOptions.Center,
Padding = new Thickness(0, 0, 8, 0),
- Children = { title, favorite }
+ Children =
+ {
+ title.Binding(Label.TextProperty, nameof(IllustItem.Title)),
+ favorite.GridColumn(1)
+ }
}
.GridRow(1)
}
@@ -302,23 +353,31 @@ namespace Pixiview.Illust
Content = new Grid
{
HorizontalOptions = LayoutOptions.Fill,
+ RowSpacing = 0,
RowDefinitions =
{
new RowDefinition().Binding(RowDefinition.HeightProperty, nameof(IllustItem.ImageHeight)),
- new RowDefinition { Height = GridLength.Auto },
- new RowDefinition { Height = GridLength.Auto }
+ new RowDefinition { Height = 30 },
+ new RowDefinition { Height = 40 }
},
Children =
{
image,
r18,
pages,
- title.GridRow(1),
+ anime,
+
+ title.Binding(Label.TextProperty, nameof(IllustItem.RankTitle)).GridRow(1),
// stacklayout: user
- new StackLayout
+ new Grid
{
- Orientation = StackOrientation.Horizontal,
+ ColumnDefinitions =
+ {
+ new ColumnDefinition { Width = 30 },
+ new ColumnDefinition(),
+ new ColumnDefinition { Width = 20 }
+ },
Padding = new Thickness(8, 0, 8, 8),
Children =
{
@@ -340,10 +399,11 @@ namespace Pixiview.Illust
FontSize = StyleDefinition.FontSizeMicro
}
.Binding(Label.TextProperty, nameof(IllustItem.UserName))
- .DynamicResource(Label.TextColorProperty, ThemeBase.SubTextColor),
+ .DynamicResource(Label.TextColorProperty, ThemeBase.SubTextColor)
+ .GridColumn(1),
// label: favorite
- favorite
+ favorite.GridColumn(2)
}
}
.GridRow(2)
@@ -367,7 +427,9 @@ namespace Pixiview.Illust
}
if (force && IsFavoriteVisible)
{
- LastUpdated = DateTime.Now;
+ var now = DateTime.Now;
+ LastUpdated = now;
+ lastUpdated = now;
}
var data = DoGetIllustList(illustData, commandIllustImageTapped).Where(i => i != null);
@@ -385,8 +447,7 @@ namespace Pixiview.Illust
item.IsFavorite = favorites.Any(i => i.Id == item.Id);
}
}
- IllustCollection = collection;
- Illusts = collection;
+ DoIllustsLoaded(collection);
IsLoading = false;
DoLoadImages(collection);
@@ -474,11 +535,20 @@ namespace Pixiview.Illust
public List Illusts { get; set; }
}
+ public enum IllustType
+ {
+ Illust = 0,
+ Manga = 1,
+ Anime = 2
+ }
+
[JsonObject(MemberSerialization.OptIn)]
public class IllustItem : BindableObject
{
public static readonly BindableProperty TitleProperty = BindableProperty.Create(
nameof(Title), typeof(string), typeof(IllustItem));
+ public static readonly BindableProperty RankTitleProperty = BindableProperty.Create(
+ nameof(RankTitle), typeof(string), typeof(IllustItem));
public static readonly BindableProperty ImageProperty = BindableProperty.Create(
nameof(Image), typeof(ImageSource), typeof(IllustItem));
public static readonly BindableProperty ProfileImageProperty = BindableProperty.Create(
@@ -487,6 +557,8 @@ namespace Pixiview.Illust
nameof(ImageHeight), typeof(GridLength), typeof(IllustItem), GridLength.Auto);
public static readonly BindableProperty IsFavoriteProperty = BindableProperty.Create(
nameof(IsFavorite), typeof(bool), typeof(IllustItem));
+ public static readonly BindableProperty IsPlayingProperty = BindableProperty.Create(
+ nameof(IsPlaying), typeof(bool), typeof(IllustItem));
[JsonProperty]
public string Title
@@ -494,6 +566,11 @@ namespace Pixiview.Illust
get => (string)GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
+ public string RankTitle
+ {
+ get => (string)GetValue(RankTitleProperty);
+ set => SetValue(RankTitleProperty, value);
+ }
public ImageSource Image
{
get => (ImageSource)GetValue(ImageProperty);
@@ -514,6 +591,11 @@ namespace Pixiview.Illust
get => (bool)GetValue(IsFavoriteProperty);
set => SetValue(IsFavoriteProperty, value);
}
+ public bool IsPlaying
+ {
+ get => (bool)GetValue(IsPlayingProperty);
+ set => SetValue(IsPlayingProperty, value);
+ }
public ICommand IllustTapped { get; set; }
[JsonProperty]
@@ -540,8 +622,19 @@ namespace Pixiview.Illust
get => ImageHeight.IsAuto ? -1 : ImageHeight.Value;
set => ImageHeight = value > 0 ? value : GridLength.Auto;
}
+ [JsonProperty]
+ public int YesRank { get; set; }
+ [JsonProperty]
+ public int RatingCount { get; set; }
+ [JsonProperty]
+ public int ViewCount { get; set; }
+ [JsonProperty]
+ public long UploadTimestamp { get; set; }
+ [JsonProperty]
+ public IllustType IllustType { get; set; }
public string PageCountText => $"{StyleDefinition.IconLayer} {PageCount}";
public bool IsPageVisible => PageCount > 1;
+ public bool IsAnimeVisible => IllustType == IllustType.Anime;
}
}
diff --git a/Pixiview/Illust/RankingPage.xaml b/Pixiview/Illust/RankingPage.xaml
index 6b3cb15..f6abfb8 100644
--- a/Pixiview/Illust/RankingPage.xaml
+++ b/Pixiview/Illust/RankingPage.xaml
@@ -1,31 +1,86 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/Pixiview/Illust/RankingPage.xaml.cs b/Pixiview/Illust/RankingPage.xaml.cs
index 2eddbb3..3214766 100644
--- a/Pixiview/Illust/RankingPage.xaml.cs
+++ b/Pixiview/Illust/RankingPage.xaml.cs
@@ -1,45 +1,156 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
using System.Windows.Input;
+using Pixiview.Resources;
+using Pixiview.UI;
using Pixiview.Utils;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Pixiview.Illust
{
- public partial class RankingPage : IllustDataCollectionPage
+ public partial class RankingPage : IllustRankingDataCollectionPage
{
+ private static readonly string[] segmentDates = { "daily", "weekly", "monthly", "male" };
+ private static readonly object sync = new object();
+
public static readonly BindableProperty KeywordsProperty = BindableProperty.Create(
nameof(Keywords), typeof(string), typeof(RankingPage));
+ public static readonly BindableProperty PanelStateProperty = BindableProperty.Create(
+ nameof(PanelState), typeof(string), typeof(RankingPage), StyleDefinition.IconCaretDown);
+ public static readonly BindableProperty SegmentDateProperty = BindableProperty.Create(
+ nameof(SegmentDate), typeof(int), typeof(RankingPage), propertyChanged: OnSegmentDatePropertyChanged);
+ public static readonly BindableProperty SegmentTypeProperty = BindableProperty.Create(
+ nameof(SegmentType), typeof(int), typeof(RankingPage), propertyChanged: OnSegmentTypePropertyChanged);
+
+ 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 string Keywords
{
get => (string)GetValue(KeywordsProperty);
set => SetValue(KeywordsProperty, value);
}
+ public string PanelState
+ {
+ get => (string)GetValue(PanelStateProperty);
+ set => SetValue(PanelStateProperty, value);
+ }
+ public int SegmentDate
+ {
+ get => (int)GetValue(SegmentDateProperty);
+ set => SetValue(SegmentDateProperty, value);
+ }
+ public int SegmentType
+ {
+ get => (int)GetValue(SegmentTypeProperty);
+ set => SetValue(SegmentTypeProperty, value);
+ }
+
+ private bool isFilterVisible;
+ private double topOffset;
+ private string lastQueryKey;
+ private string queryDate;
+ private int currentPage;
+ private int nextPage;
+
+ private string QueryKey => segmentDates[SegmentDate] + (SegmentType == 1 ? "_r18" : string.Empty);
public RankingPage()
{
Resources.Add("cardView", GetCardViewTemplate());
InitializeComponent();
+ gridFilter.TranslationY = -100;
+ panelFilter.TranslationY = -100;
-#if __IOS__
- searchBar.BackgroundColor = Color.Transparent;
-#elif __ANDROID__
- searchBar.SetDynamicResource(SearchBar.TextColorProperty, UI.Theme.ThemeBase.TextColor);
- searchBar.SetDynamicResource(SearchBar.PlaceholderColorProperty, UI.Theme.ThemeBase.SubTextColor);
- searchBar.SetDynamicResource(BackgroundColorProperty, UI.Theme.ThemeBase.WindowColor);
-#endif
+//#if __IOS__
+// searchBar.BackgroundColor = Color.Transparent;
+//#elif __ANDROID__
+// searchBar.SetDynamicResource(SearchBar.TextColorProperty, UI.Theme.ThemeBase.TextColor);
+// searchBar.SetDynamicResource(SearchBar.PlaceholderColorProperty, UI.Theme.ThemeBase.SubTextColor);
+// searchBar.SetDynamicResource(BackgroundColorProperty, UI.Theme.ThemeBase.WindowColor);
+//#endif
+
+ lastQueryKey = QueryKey;
+ queryDate = null; // $"{now.Year}{now.Month:00}{now.Day:00}";
+ currentPage = 1;
+ }
+
+ protected override void OnSizeAllocated(double width, double height)
+ {
+ base.OnSizeAllocated(width, height);
+ if (StyleDefinition.IsFullscreenDevice)
+ {
+ topOffset = width > height ?
+ AppShell.NavigationBarOffset.Top :
+ AppShell.TotalBarOffset.Top;
+ }
+ else if (isPhone)
+ {
+ topOffset = width > height ?
+ StyleDefinition.TopOffset32.Top :
+ AppShell.TotalBarOffset.Top;
+ }
+ else
+ {
+ // TODO: ipad
+ topOffset = AppShell.TotalBarOffset.Top;
+ }
}
protected override bool IsLazyload => true;
- protected override IEnumerable DoGetIllustList(IllustData data, ICommand command)
+ protected override void DoIllustsLoaded(IllustCollection collection)
{
- return data.body.page.ranking.items.Select(i =>
+ var now = IllustCollection;
+ if (now == null)
{
- var item = data.body.thumbnails.illust.FirstOrDefault(l => l.illustId == i.id)?.ConvertToItem();
+ IllustCollection = collection;
+ Illusts = collection;
+ }
+ else
+ {
+ now = new IllustCollection(now.Concat(collection));
+ IllustCollection = now;
+ Illusts = now;
+ }
+ }
+
+ protected override IEnumerable DoGetIllustList(IllustRankingData data, ICommand command)
+ {
+ return data.contents.Select(i =>
+ {
+ var item = i.ConvertToItem();
if (item != null)
{
item.IllustTapped = command;
@@ -48,27 +159,67 @@ namespace Pixiview.Illust
});
}
- protected override IllustData DoLoadIllustData(bool force)
+ protected override IllustRankingData DoLoadIllustData(bool force)
{
- var data = Stores.LoadIllustData(force);
+ var data = Stores.LoadIllustRankingData(lastQueryKey, queryDate, currentPage, force);
if (data != null)
{
- var date = data.body.page.ranking.date;
+ //if (data.contents.Length * data.page < data.rank_total)
+ //{
+ // nextPage = currentPage + 1;
+ //}
+ if (int.TryParse(data.next, out int next))
+ {
+ nextPage = next;
+ }
+ var date = data.date;
if (date.Length == 8)
{
+ queryDate = date;
date = date.Substring(0, 4) + "-" + date.Substring(4, 2) + "-" + date.Substring(6, 2);
}
+ date = ResourceHelper.GetResource(data.mode, date);
MainThread.BeginInvokeOnMainThread(() => Title = date);
}
return data;
}
+ private async void ToggleFilterPanel(bool flag)
+ {
+ ViewExtensions.CancelAnimations(gridFilter);
+ ViewExtensions.CancelAnimations(panelFilter);
+ if (flag)
+ {
+ isFilterVisible = true;
+ PanelState = StyleDefinition.IconCaretUp;
+ await Task.WhenAll(
+ 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;
+ PanelState = StyleDefinition.IconCaretDown;
+ await Task.WhenAll(
+ gridFilter.TranslateTo(0, -100, easing: Easing.CubicOut),
+ gridFilter.FadeTo(0, easing: Easing.CubicOut),
+ panelFilter.TranslateTo(0, -100, easing: Easing.CubicOut),
+ panelFilter.FadeTo(0, easing: Easing.CubicOut)
+ );
+ }
+ }
+
private void Refresh_Clicked(object sender, EventArgs e)
{
if (IsLoading)
{
return;
}
+ currentPage = 1;
+ IllustCollection = null;
StartLoad(true);
}
@@ -100,5 +251,65 @@ namespace Pixiview.Illust
SearchBar_SearchButtonPressed(sender, e);
}
}
+
+ private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
+ {
+ ToggleFilterPanel(!isFilterVisible);
+ }
+
+ private void ScrollView_Scrolled(object sender, ScrolledEventArgs e)
+ {
+ var bottomOffset = scrollView.ContentSize.Height - (scrollView.LayoutAreaOverride.Height * 2);
+ if (e.ScrollY > 0 && e.ScrollY + topOffset > bottomOffset)
+ {
+ bool refresh = false;
+ lock (sync)
+ {
+ if (IsLoading)
+ {
+ return;
+ }
+ App.DebugPrint("bottom arrived.");
+ if (nextPage == currentPage + 1)
+ {
+ currentPage = nextPage;
+ refresh = true;
+ App.DebugPrint($"loading page {nextPage}");
+ }
+ }
+ if (refresh)
+ {
+ StartLoad(true);
+ }
+ }
+ }
+
+ private void Filter_Clicked(object sender, EventArgs e)
+ {
+ var query = QueryKey;
+ ToggleFilterPanel(false);
+
+ bool refresh = false;
+ lock (sync)
+ {
+ if (IsLoading)
+ {
+ return;
+ }
+ if (lastQueryKey != query)
+ {
+ // query changed.
+ currentPage = 1;
+ lastQueryKey = query;
+ IllustCollection = null;
+ refresh = true;
+ App.DebugPrint($"query changed: {query}");
+ }
+ }
+ if (refresh)
+ {
+ StartLoad(true);
+ }
+ }
}
}
diff --git a/Pixiview/Illust/ViewIllustPage.xaml b/Pixiview/Illust/ViewIllustPage.xaml
index d91ed7f..a2ae931 100644
--- a/Pixiview/Illust/ViewIllustPage.xaml
+++ b/Pixiview/Illust/ViewIllustPage.xaml
@@ -31,6 +31,9 @@
+
+
+
extras = new List();
@@ -380,6 +429,7 @@ namespace Pixiview.Illust
get => (bool)GetValue(DownloadingProperty);
set => SetValue(DownloadingProperty, value);
}
+ public string Id { get; set; }
public ICommand LongPressed { get; set; }
public string PreviewUrl { get; set; }
public string OriginalUrl { get; set; }
diff --git a/Pixiview/Pixiview.projitems b/Pixiview/Pixiview.projitems
index 737e50f..e5f73a2 100644
--- a/Pixiview/Pixiview.projitems
+++ b/Pixiview/Pixiview.projitems
@@ -91,6 +91,8 @@
+
+
diff --git a/Pixiview/Resources/Languages/zh-CN.xml b/Pixiview/Resources/Languages/zh-CN.xml
index db96830..b0ee4d2 100644
--- a/Pixiview/Resources/Languages/zh-CN.xml
+++ b/Pixiview/Resources/Languages/zh-CN.xml
@@ -10,6 +10,19 @@
启用
主机
端口
+ 今日
+ 本周
+ 本月
+ 受男性欢迎
+ 当月截至 {0}
+ 当周截至 {0}
+ 当日 {0}
+ 受欢迎 {0}
+ 当月截至 {0}
+ 当周截至 {0}
+ 当日 {0}
+ 受欢迎 {0}
+ 一般
R-18
已关注
推荐
diff --git a/Pixiview/UI/AdaptedPage.cs b/Pixiview/UI/AdaptedPage.cs
index d40b411..9b1b19a 100644
--- a/Pixiview/UI/AdaptedPage.cs
+++ b/Pixiview/UI/AdaptedPage.cs
@@ -27,6 +27,7 @@ namespace Pixiview.UI
public event EventHandler Load;
public event EventHandler Unload;
+ public event EventHandler PageTopMarginChanged;
public event EventHandler OrientationChanged;
protected static readonly bool isPhone = DeviceInfo.Idiom == DeviceIdiom.Phone;
@@ -49,10 +50,12 @@ namespace Pixiview.UI
public virtual void OnOrientationChanged(Orientation orientation)
{
+ var oldMargin = PageTopMargin;
+ Thickness newMargin;
switch (orientation)
{
case Orientation.Portrait:
- PageTopMargin = AppShell.TotalBarOffset;
+ newMargin = AppShell.TotalBarOffset;
break;
case Orientation.PortraitUpsideDown:
case Orientation.Unknown:
@@ -61,17 +64,35 @@ namespace Pixiview.UI
default:
if (StyleDefinition.IsFullscreenDevice)
{
- PageTopMargin = AppShell.NavigationBarOffset;
+ newMargin = AppShell.NavigationBarOffset;
}
else
{
- PageTopMargin = isPhone ? StyleDefinition.TopOffset32 : AppShell.TotalBarOffset;
+ newMargin = isPhone ? StyleDefinition.TopOffset32 : AppShell.TotalBarOffset;
}
break;
}
+ if (oldMargin != newMargin)
+ {
+ PageTopMargin = newMargin;
+ OnPageTopMarginChanged(oldMargin, newMargin);
+ }
OrientationChanged?.Invoke(this, new OrientationEventArgs { CurrentOrientation = orientation });
}
+ public virtual void OnPageTopMarginChanged(Thickness old, Thickness @new)
+ {
+ PageTopMarginChanged?.Invoke(this, new ThicknessEventArgs
+ {
+ OldMargin = old,
+ NewMargin = @new
+ });
+ }
+
+ public virtual void OnPanelTopMarginChanged(Thickness old, Thickness @new)
+ {
+ }
+
public void InitOrientation(Orientation orientation)
{
CurrentOrientation = orientation;
@@ -120,6 +141,12 @@ namespace Pixiview.UI
}
}
+ public class ThicknessEventArgs : EventArgs
+ {
+ public Thickness OldMargin { get; set; }
+ public Thickness NewMargin { get; set; }
+ }
+
public class OrientationEventArgs : EventArgs
{
public Orientation CurrentOrientation { get; set; }
diff --git a/Pixiview/UI/CardView.cs b/Pixiview/UI/CardView.cs
index 224cfc5..6802a5f 100644
--- a/Pixiview/UI/CardView.cs
+++ b/Pixiview/UI/CardView.cs
@@ -13,6 +13,8 @@ namespace Pixiview.UI
nameof(ShadowRadius), typeof(float), typeof(CardView), 5f);
public static readonly BindableProperty ShadowOffsetProperty = BindableProperty.Create(
nameof(ShadowOffset), typeof(Size), typeof(CardView));
+ public static readonly BindableProperty RankProperty = BindableProperty.Create(
+ nameof(Rank), typeof(int), typeof(CardView));
public float CornerRadius
{
@@ -34,6 +36,11 @@ namespace Pixiview.UI
get => (Size)GetValue(ShadowOffsetProperty);
set => SetValue(ShadowOffsetProperty, value);
}
+ public int Rank
+ {
+ get => (int)GetValue(RankProperty);
+ set => SetValue(RankProperty, value);
+ }
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
diff --git a/Pixiview/UI/StyleDefinition.cs b/Pixiview/UI/StyleDefinition.cs
index edbe6e8..2be3425 100644
--- a/Pixiview/UI/StyleDefinition.cs
+++ b/Pixiview/UI/StyleDefinition.cs
@@ -41,6 +41,10 @@ namespace Pixiview.UI
public const string IconOption = "\uf013";
public const string IconFavorite = "\uf02e";
public const string IconShare = "\uf35d";
+ public const string IconCaretDown = "\uf0d7";
+ public const string IconCaretUp = "\uf0d8";
+ public const string IconCircleCheck = "\uf058";
+ public const string IconPlay = "\uf04b";
static StyleDefinition()
{
diff --git a/Pixiview/UI/Theme/ThemeBase.cs b/Pixiview/UI/Theme/ThemeBase.cs
index 4f01f1e..6ca2aa7 100644
--- a/Pixiview/UI/Theme/ThemeBase.cs
+++ b/Pixiview/UI/Theme/ThemeBase.cs
@@ -13,6 +13,7 @@ namespace Pixiview.UI.Theme
public const string FontIconOption = nameof(FontIconOption);
public const string FontIconFavorite = nameof(FontIconFavorite);
public const string FontIconShare = nameof(FontIconShare);
+ public const string IconCircleCheck = nameof(IconCircleCheck);
public const string StatusBarStyle = nameof(StatusBarStyle);
public const string WindowColor = nameof(WindowColor);
@@ -52,6 +53,8 @@ namespace Pixiview.UI.Theme
Add(FontIconOption, GetSolidIcon(StyleDefinition.IconOption, solidFontFamily));
Add(FontIconFavorite, GetSolidIcon(StyleDefinition.IconFavorite, solidFontFamily));
Add(FontIconShare, GetSolidIcon(StyleDefinition.IconShare, solidFontFamily));
+
+ Add(IconCircleCheck, StyleDefinition.IconCircleCheck);
}
private FontImageSource GetSolidIcon(string icon, string family, Color color = default)
diff --git a/Pixiview/Utils/Converters.cs b/Pixiview/Utils/Converters.cs
index 1dbaeda..079df06 100644
--- a/Pixiview/Utils/Converters.cs
+++ b/Pixiview/Utils/Converters.cs
@@ -17,4 +17,24 @@ namespace Pixiview.Utils
throw new NotImplementedException();
}
}
+
+ public class OffsetConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is Thickness thickness && parameter != null)
+ {
+ if (double.TryParse(parameter.ToString(), out var offset))
+ {
+ return new Thickness(thickness.Left, thickness.Top + offset, thickness.Right, thickness.Bottom);
+ }
+ }
+ return value;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
}
diff --git a/Pixiview/Utils/HttpUtility.cs b/Pixiview/Utils/HttpUtility.cs
new file mode 100644
index 0000000..c2c02b5
--- /dev/null
+++ b/Pixiview/Utils/HttpUtility.cs
@@ -0,0 +1,502 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+using Pixiview.Illust;
+using Xamarin.Forms;
+
+namespace Pixiview.Utils
+{
+ public class HttpUtility
+ {
+ public static T LoadObject(string file, string url, string referer,
+ bool force = false,
+ Action header = null,
+ Func namehandler = null,
+ Func action = null)
+ {
+ string content = null;
+ if (!force && file != null && File.Exists(file))
+ {
+ try
+ {
+ content = File.ReadAllText(file);
+ }
+ catch (Exception ex)
+ {
+ App.DebugError("load", $"failed to read file: {file}, error: {ex.Message}");
+ }
+ }
+ if (content == null)
+ {
+ var response = Download(url, headers =>
+ {
+ if (referer != null)
+ {
+ headers.Referrer = new Uri(referer);
+ }
+ headers.Add("User-Agent", Configs.UserAgent);
+ headers.Add("Accept", Configs.AcceptJson);
+ headers.Add("Cookie", Configs.Cookie);
+ if (header == null)
+ {
+ headers.Add("X-User-Id", Configs.UserId);
+ }
+ else
+ {
+ header(headers);
+ }
+ });
+ if (response == null)
+ {
+ return default;
+ }
+ using (response)
+ {
+ try
+ {
+ content = response.Content.ReadAsStringAsync().Result;
+ if (action != null)
+ {
+ content = action(content);
+ }
+ }
+ catch (Exception ex)
+ {
+ App.DebugError("load.strea", $"failed to read stream, error: {ex.Message}");
+ return default;
+ }
+
+ bool rtn = false;
+ T result = default;
+ if (namehandler != null)
+ {
+ try
+ {
+ result = JsonConvert.DeserializeObject(content);
+ file = namehandler(result);
+ rtn = true;
+ }
+ catch (Exception ex)
+ {
+ App.DebugError("load", $"failed to parse illust JSON object, error: {ex.Message}");
+ }
+ }
+
+ if (file != null)
+ {
+ try
+ {
+ var folder = Path.GetDirectoryName(file);
+ if (!Directory.Exists(folder))
+ {
+ Directory.CreateDirectory(folder);
+ }
+ File.WriteAllText(file, content, Encoding.UTF8);
+ }
+ catch (Exception ex)
+ {
+ App.DebugError("save", $"failed to save illust JSON object, error: {ex.Message}");
+ }
+ }
+
+ if (rtn)
+ {
+ return result;
+ }
+ }
+ }
+ try
+ {
+ return JsonConvert.DeserializeObject(content);
+ }
+ catch (Exception ex)
+ {
+ App.DebugError("load", $"failed to parse illust JSON object, error: {ex.Message}");
+ return default;
+ }
+ }
+
+ public static string DownloadImage(string url, string working, string folder)
+ {
+ try
+ {
+ var directory = Path.Combine(working, folder);
+ if (!Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+ var file = Path.Combine(directory, Path.GetFileName(url));
+ App.DebugPrint($"download, url: {url}");
+ var response = Download(url, headers =>
+ {
+ headers.Referrer = new Uri(Configs.Referer);
+ headers.Add("User-Agent", Configs.UserAgent);
+ headers.Add("Accept", Configs.AcceptImage);
+ });
+ if (response == null)
+ {
+ return null;
+ }
+ using (response)
+ using (var fs = File.OpenWrite(file))
+ {
+ response.Content.CopyToAsync(fs).Wait();
+ //if (response.Headers.Date != null)
+ //{
+ // File.SetLastWriteTimeUtc(file, response.Headers.Date.Value.UtcDateTime);
+ //}
+ }
+ return file;
+ }
+ catch (Exception ex)
+ {
+ App.DebugError("image.download", ex.Message);
+ return null;
+ }
+ }
+
+ private static HttpResponseMessage Download(string url, Action headerAction)
+ {
+ App.DebugPrint($"GET: {url}");
+ var uri = new Uri(url);
+ var proxy = Configs.Proxy;
+ var handler = new HttpClientHandler
+ {
+ AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
+ UseCookies = false
+ };
+ if (proxy != null)
+ {
+ handler.Proxy = proxy;
+ handler.UseProxy = true;
+ }
+ var client = new HttpClient(handler)
+ {
+ BaseAddress = new Uri($"{uri.Scheme}://{uri.Host}"),
+ Timeout = TimeSpan.FromSeconds(30)
+ };
+ return TryCount(() =>
+ {
+ using (var request = new HttpRequestMessage(HttpMethod.Get, uri.PathAndQuery)
+ {
+ Version = new Version(2, 0)
+ })
+ {
+ var headers = request.Headers;
+ headerAction(headers);
+ if (proxy == null)
+ {
+ headers.Add("x-reverse", "yes");
+ }
+ headers.Add("Accept-Language", Configs.AcceptLanguage);
+ //headers.Add("Accept-Encoding", Configs.AcceptEncoding);
+ return client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result;
+ }
+ });
+ }
+
+ private static T TryCount(Func func, int tryCount = 2)
+ {
+ int tries = 0;
+ while (tries < tryCount)
+ {
+ try
+ {
+ return func();
+ }
+ catch (Exception ex)
+ {
+ tries++;
+ Thread.Sleep(1000);
+ App.DebugError("try.do", $"tries: {tries}, error: {ex.Message}");
+ }
+ }
+ return default;
+ }
+
+ public static (long Size, DateTimeOffset LastModified, HttpClient Client) GetUgoiraHeader(string url, string id)
+ {
+ var uri = new Uri(url);
+ var proxy = Configs.Proxy;
+ var handler = new HttpClientHandler
+ {
+ UseCookies = false
+ };
+ if (proxy != null)
+ {
+ handler.Proxy = proxy;
+ handler.UseProxy = true;
+ }
+ var client = new HttpClient(handler)
+ {
+ BaseAddress = new Uri($"{uri.Scheme}://{uri.Host}"),
+ Timeout = TimeSpan.FromSeconds(30)
+ };
+ var response = TryCount(() =>
+ {
+ using (var request = new HttpRequestMessage(HttpMethod.Head, uri.PathAndQuery)
+ {
+ Version = new Version(2, 0)
+ })
+ {
+ var headers = request.Headers;
+ UgoiraHeaderAction(headers, id);
+ headers.Add("Accept-Encoding", "gzip, deflate");
+ headers.Add("Accept-Language", Configs.AcceptLanguage);
+ return client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result;
+ }
+ });
+
+ var size = response.Content.Headers.ContentLength.Value;
+ var lastModified = response.Content.Headers.LastModified.Value;
+
+ return (size, lastModified, client);
+ }
+
+ public static long DownloadUgoiraImage(HttpClient client, string url, string id, DateTimeOffset lastModified, long from, long to, Stream stream)
+ {
+ var uri = new Uri(url);
+ var response = TryCount(() =>
+ {
+ using (var request = new HttpRequestMessage(HttpMethod.Get, uri.PathAndQuery)
+ {
+ Version = new Version(2, 0)
+ })
+ {
+ var headers = request.Headers;
+ UgoiraHeaderAction(headers, id);
+ headers.Add("Accept-Encoding", "identity");
+ headers.IfRange = new RangeConditionHeaderValue(lastModified);
+ headers.Range = new RangeHeaderValue(from, to);
+ headers.Add("Accept-Language", Configs.AcceptLanguage);
+ return client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result;
+ }
+ });
+
+ var length = response.Content.Headers.ContentLength.Value;
+ response.Content.CopyToAsync(stream).Wait();
+ return length;
+ }
+
+ private static void UgoiraHeaderAction(HttpRequestHeaders headers, string id)
+ {
+ headers.Add("Accept", "*/*");
+ headers.Add("Origin", Configs.Referer);
+ headers.Referrer = new Uri(string.Format(Configs.RefererIllust, id));
+ headers.Add("User-Agent", Configs.UserAgent);
+ }
+ }
+
+ public class Ugoira
+ {
+ private readonly IllustUgoiraBody ugoira;
+ private readonly IllustDetailItem detailItem;
+ private readonly ImageSource[] frames;
+ private readonly Timer timer;
+ private int index = 0;
+
+ public bool IsPlaying { get; private set; }
+ public event EventHandler FrameChanged;
+
+ public Ugoira(IllustUgoiraData illust, IllustDetailItem item)
+ {
+ ugoira = illust.body;
+ detailItem = item;
+ frames = new ImageSource[ugoira.frames.Length];
+ timer = new Timer(OnTimerCallback, null, Timeout.Infinite, Timeout.Infinite);
+
+ Task.Run(LoadFrames);
+ }
+
+ public void TogglePlay(bool flag)
+ {
+ if (IsPlaying == flag)
+ {
+ return;
+ }
+ if (flag)
+ {
+ IsPlaying = true;
+ timer.Change(0, Timeout.Infinite);
+ }
+ else
+ {
+ IsPlaying = false;
+ timer.Change(Timeout.Infinite, Timeout.Infinite);
+ }
+ }
+
+ private void OnTimerCallback(object state)
+ {
+ if (!IsPlaying)
+ {
+ return;
+ }
+
+ ImageSource frame;
+ var i = index;
+ var info = ugoira.frames[i];
+ while ((frame = frames[i]) == null)
+ {
+ // not downloaded yet, waiting...
+ Thread.Sleep(100);
+ }
+ FrameChanged?.Invoke(this, new UgoiraEventArgs
+ {
+ DetailItem = detailItem,
+ Image = frame
+ });
+ i++;
+ if (i >= frames.Length)
+ {
+ i = 0;
+ }
+ index = i;
+ timer.Change(info.delay, Timeout.Infinite);
+ }
+
+ private const int BUFFER_SIZE = 300000;
+
+ private void LoadFrames()
+ {
+ var zip = Path.GetFileName(ugoira.src);
+ bool download = false;
+ for (var i = 0; i < ugoira.frames.Length; i++)
+ {
+ var frame = ugoira.frames[i];
+ var image = Stores.LoadUgoiraImage(zip, frame.file);
+ if (image != null)
+ {
+ frames[i] = image;
+ }
+ else
+ {
+ download = true;
+ break;
+ }
+ }
+
+ if (download)
+ {
+ // need download
+ var url = ugoira.src;
+ var id = detailItem.Id;
+ var (size, lastModified, client) = HttpUtility.GetUgoiraHeader(url, id);
+ App.DebugPrint($"starting download ugoira: {size} bytes, last modified: {lastModified}");
+
+ var data = new byte[size];
+ using (var ms = new MemoryStream(data))
+ {
+ var index = 0;
+ for (var i = 0; ; i += BUFFER_SIZE)
+ {
+ long to;
+ if (i + BUFFER_SIZE > size)
+ {
+ to = size - 1;
+ }
+ else
+ {
+ to = i + BUFFER_SIZE - 1;
+ }
+ HttpUtility.DownloadUgoiraImage(client, url, id, lastModified, i, to, ms);
+ var last = ms.Position;
+ while (true)
+ {
+ var pos = ExtractImage(zip, data, index, last);
+ if (pos > index)
+ {
+ index = pos;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if (i + BUFFER_SIZE > size)
+ {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private int ExtractImage(string zip, byte[] data, int index, long last)
+ {
+ var i = index;
+ if (i + 30 > last)
+ {
+ return index;
+ }
+ if (data[i] != 0x50 || data[i + 1] != 0x4b ||
+ data[i + 2] != 0x03 || data[i + 3] != 0x04)
+ {
+ App.DebugPrint($"extract complete, header: {BitConverter.ToInt32(data, i):x8}");
+ return index;
+ }
+ i += 8; // signature(4) & version(2) & flags(2)
+ if (data[i] != 0 || data[i + 1] != 0)
+ {
+ App.DebugError("extract.image", $"doesn't support compressed data: {BitConverter.ToInt16(data, i):x4}");
+ return index;
+ }
+ i += 10; // compression(2) & mod-time(2) & mod-date(2) & crc-32(4)
+ int size = BitConverter.ToInt32(data, i);
+ i += 4; // size(4)
+ int rawSize = BitConverter.ToInt32(data, i);
+ if (size != rawSize)
+ {
+ App.DebugError("extract.image", $"data seems to be compressed: {size} ({rawSize}) bytes");
+ return index;
+ }
+ i += 4; // rawSize(4)
+ int filenameLength = BitConverter.ToInt16(data, i);
+ i += 2; // filename length(2)
+ int extraLength = BitConverter.ToInt16(data, i);
+ i += 2; // extra length(2)
+
+ if (i + filenameLength + extraLength + size > last)
+ {
+ App.DebugPrint($"download is not completed, index: {index}, size: {size}, last: {last}");
+ return index;
+ }
+
+ var filename = Encoding.UTF8.GetString(data, i, filenameLength);
+ i += filenameLength + extraLength; // filename & extra
+
+ // content
+ var content = new byte[size];
+ Array.Copy(data, i, content, 0, size);
+ i += size;
+
+ var file = Stores.SaveUgoiraImage(zip, filename, content);
+ if (file != null)
+ {
+ for (var n = 0; n < ugoira.frames.Length; n++)
+ {
+ if (ugoira.frames[n].file == filename)
+ {
+ App.DebugPrint($"load frame: {filename}");
+ frames[n] = ImageSource.FromFile(file);
+ break;
+ }
+ }
+ }
+ return i;
+ }
+ }
+
+ public class UgoiraEventArgs : EventArgs
+ {
+ public IllustDetailItem DetailItem { get; set; }
+ public ImageSource Image { get; set; }
+ }
+}
diff --git a/Pixiview/Utils/IllustData.cs b/Pixiview/Utils/IllustData.cs
index e3537d1..fcbc3d8 100644
--- a/Pixiview/Utils/IllustData.cs
+++ b/Pixiview/Utils/IllustData.cs
@@ -18,6 +18,8 @@ namespace Pixiview.Utils
public string illustTitle;
public string id;
public string title;
+ public int illustType;
+ public int xRestrict;
public string url;
public string description;
public string[] tags;
@@ -31,7 +33,6 @@ namespace Pixiview.Utils
public string seriesId;
public string seriesTitle;
public string profileImageUrl;
- public int xRestrict;
public class IllustUrls
{
@@ -49,6 +50,7 @@ namespace Pixiview.Utils
{
Id = illustId,
Title = illustTitle,
+ IllustType = (IllustType)illustType,
ImageUrl = urls?.x360 ?? url,
IsRestrict = xRestrict == 1,
ProfileUrl = profileImageUrl,
@@ -164,6 +166,7 @@ namespace Pixiview.Utils
public IllustItem CopyToItem(IllustItem item)
{
item.Title = illustTitle;
+ item.IllustType = (IllustType)illustType;
item.ImageUrl = urls?.regular;
item.IsRestrict = xRestrict == 1;
item.UserId = userId;
@@ -238,4 +241,19 @@ namespace Pixiview.Utils
{
public Dictionary works;
}
+
+ public class IllustUgoiraData : IllustResponse { }
+ public class IllustUgoiraBody
+ {
+ public string src;
+ public string originalSrc;
+ public string mime_type;
+ public Frame[] frames;
+
+ public class Frame
+ {
+ public string file;
+ public int delay;
+ }
+ }
}
diff --git a/Pixiview/Utils/IllustLegacy.cs b/Pixiview/Utils/IllustLegacy.cs
new file mode 100644
index 0000000..6f1e403
--- /dev/null
+++ b/Pixiview/Utils/IllustLegacy.cs
@@ -0,0 +1,107 @@
+using Pixiview.Illust;
+
+namespace Pixiview.Utils
+{
+ public class IllustRankingData
+ {
+ public Content[] contents;
+ public string mode;
+ public string content;
+ public int page;
+ public string prev;
+ public string next;
+ public string date;
+ public string prev_date;
+ public string next_date;
+ public int rank_total;
+
+ public class Content
+ {
+ public string title;
+ public string date;
+ public string[] tags;
+ public string url;
+ public string illust_type;
+ public string illust_book_style;
+ public string illust_page_count;
+ public string user_name;
+ public string profile_img;
+ public ContentType illust_content_type;
+ public object illust_series; // bool, Series
+ public long illust_id;
+ public int width;
+ public int height;
+ public long user_id;
+ public int rank;
+ public int yes_rank;
+ public int rating_count;
+ public int view_count;
+ public long illust_upload_timestamp;
+ public string attr;
+ public bool is_bookmarked;
+ public bool bookmarkable;
+
+ public class ContentType
+ {
+ public int sexual;
+ public bool lo;
+ public bool grotesque;
+ public bool violent;
+ public bool homosexual;
+ public bool drug;
+ public bool thoughts;
+ public bool antisocial;
+ public bool religion;
+ public bool original;
+ public bool furry;
+ public bool bl;
+ public bool yuri;
+ }
+
+ public class Series
+ {
+ public string illust_series_caption;
+ public string illust_series_content_count;
+ public string illust_series_content_illust_id;
+ public string illust_series_content_order;
+ public string illust_series_create_datetime;
+ public string illust_series_id;
+ public string illust_series_title;
+ public string illust_series_user_id;
+ public string page_url;
+ }
+
+ public IllustItem ConvertToItem()
+ {
+ if (!int.TryParse(illust_page_count, out int count))
+ {
+ count = 1;
+ }
+ if (!int.TryParse(illust_type, out int type))
+ {
+ type = 0;
+ }
+ return new IllustItem
+ {
+ Id = illust_id.ToString(),
+ Title = title,
+ RankTitle = $"#{rank} {title}",
+ IllustType = (IllustType)type,
+ ImageUrl = url,
+ //IsRestrict = xRestrict == 1,
+ ProfileUrl = profile_img,
+ UserId = user_id.ToString(),
+ UserName = user_name,
+ Width = width,
+ Height = height,
+ PageCount = count,
+
+ YesRank = yes_rank,
+ RatingCount = rating_count,
+ ViewCount = view_count,
+ UploadTimestamp = illust_upload_timestamp
+ };
+ }
+ }
+ }
+}
diff --git a/Pixiview/Utils/Stores.cs b/Pixiview/Utils/Stores.cs
index 371c6bd..d8c616f 100644
--- a/Pixiview/Utils/Stores.cs
+++ b/Pixiview/Utils/Stores.cs
@@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
-using System.Net.Http;
-using System.Net.Http.Headers;
using System.Text;
using Newtonsoft.Json;
using Pixiview.Illust;
@@ -25,6 +23,7 @@ namespace Pixiview.Utils
private const string favoriteFile = "favorites.json";
private const string imageFolder = "img-original";
private const string previewFolder = "img-master";
+ private const string ugoiraFolder = "img-zip-ugoira";
private const string illustFile = "illust.json";
private const string pagesFolder = "pages";
@@ -100,79 +99,41 @@ namespace Pixiview.Utils
}
}
- private static T LoadObject(string file, string url, string referer, bool force = false, Func action = null)
+ public static ImageSource LoadUgoiraImage(string zip, string frame)
{
- string content = null;
- if (!force && file != null && File.Exists(file))
+ var file = Path.Combine(PersonalFolder, ugoiraFolder, zip, frame);
+ if (File.Exists(file))
{
try
{
- content = File.ReadAllText(file);
+ return ImageSource.FromFile(file);
}
catch (Exception ex)
{
- App.DebugError("load", $"failed to read file: {file}, error: {ex.Message}");
+ App.DebugError("load.ugoira", $"failed to load ugoira frame: {zip}/{frame}, error: {ex.Message}");
}
}
- if (content == null)
- {
- var response = Download(url, headers =>
- {
- if (referer != null)
- {
- headers.Referrer = new Uri(referer);
- }
- headers.Add("User-Agent", Configs.UserAgent);
- headers.Add("Accept", Configs.AcceptJson);
- headers.Add("Cookie", Configs.Cookie);
- headers.Add("X-User-Id", Configs.UserId);
- });
- if (response == null)
- {
- return default;
- }
- using (response)
- {
- try
- {
- content = response.Content.ReadAsStringAsync().Result;
- if (action != null)
- {
- content = action(content);
- }
- }
- catch (Exception ex)
- {
- App.DebugError("load.strea", $"failed to read stream, error: {ex.Message}");
- return default;
- }
+ return null;
+ }
- if (file != null)
- {
- try
- {
- var folder = Path.GetDirectoryName(file);
- if (!Directory.Exists(folder))
- {
- Directory.CreateDirectory(folder);
- }
- File.WriteAllText(file, content, Encoding.UTF8);
- }
- catch (Exception ex)
- {
- App.DebugError("save", $"failed to save illust JSON object, error: {ex.Message}");
- }
- }
- }
- }
+ public static string SaveUgoiraImage(string zip, string frame, byte[] data)
+ {
try
{
- return JsonConvert.DeserializeObject(content);
+ var directory = Path.Combine(PersonalFolder, ugoiraFolder, zip);
+ if (!Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ var file = Path.Combine(directory, frame);
+ File.WriteAllBytes(file, data);
+ return file;
}
catch (Exception ex)
{
- App.DebugError("load", $"failed to parse illust JSON object, error: {ex.Message}");
- return default;
+ App.DebugError("save.ugoira", $"failed to save ugoira frame: {zip}/{frame}, error: {ex.Message}");
+ return null;
}
}
@@ -237,14 +198,52 @@ namespace Pixiview.Utils
public static IllustData LoadIllustData(bool force = false)
{
var file = Path.Combine(PersonalFolder, illustFile);
- var result = LoadObject(
+ var result = HttpUtility.LoadObject(
file,
Configs.UrlIllustList,
Configs.Referer,
force: force);
if (result == null || result.error)
{
- App.DebugPrint($"error when load illust data: {result?.message} ({force})");
+ App.DebugPrint($"error when load illust data: {result?.message}, force({force})");
+ }
+ return result;
+ }
+
+ public static IllustRankingData LoadIllustRankingData(string mode, string date, int page, bool force = false)
+ {
+ var file = Path.Combine(CacheFolder, $"{mode}_{date}_{page}.json");
+ string query = $"mode={mode}";
+ if (mode != "male" && mode != "male_r18")
+ {
+ query += "&content=illust";
+ }
+ if (date != null)
+ {
+ query += $"&date={date}";
+ }
+ var referer = string.Format(Configs.RefererIllustRanking, query);
+ if (page > 1)
+ {
+ query += $"&p={page}";
+ }
+ query += "&format=json";
+ var result = HttpUtility.LoadObject(
+ file,
+ string.Format(Configs.UrlIllustRanking, query),
+ referer,
+ namehandler: rst =>
+ {
+ return Path.Combine(CacheFolder, $"{mode}_{rst.date}_{page}.json");
+ },
+ header: headers =>
+ {
+ headers.Add("X-Requested-With", "XMLHttpRequest");
+ },
+ force: force);
+ if (result == null)
+ {
+ App.DebugPrint($"error when load ranking data: mode({mode}), date({date}), page({page}), force({force})");
}
return result;
}
@@ -252,7 +251,7 @@ namespace Pixiview.Utils
public static IllustPreloadBody LoadIllustPreloadData(string id, bool force = false)
{
var file = Path.Combine(CacheFolder, preloadsFolder, $"{id}.json");
- var result = LoadObject(
+ var result = HttpUtility.LoadObject(
file,
string.Format(Configs.UrlIllust, id),
null,
@@ -271,47 +270,66 @@ namespace Pixiview.Utils
}
return content;
});
+ if (result == null)
+ {
+ App.DebugPrint($"error when load preload data: force({force})");
+ }
return result;
}
public static IllustPageData LoadIllustPageData(string id, bool force = false)
{
var file = Path.Combine(CacheFolder, pagesFolder, $"{id}.json");
- var result = LoadObject(
+ var result = HttpUtility.LoadObject(
file,
string.Format(Configs.UrlIllustPage, id),
- string.Format(Configs.UrlIllust, id),
+ string.Format(Configs.RefererIllust, id),
force: force);
if (result == null || result.error)
{
- App.DebugPrint($"error when load page data: {result?.message} ({force})");
+ App.DebugPrint($"error when load page data: {result?.message}, force({force})");
+ }
+ return result;
+ }
+
+ public static IllustUgoiraData LoadIllustUgoiraData(string id, bool force = false)
+ {
+ var file = Path.Combine(PersonalFolder, ugoiraFolder, $"{id}.json");
+ var result = HttpUtility.LoadObject(
+ file,
+ string.Format(Configs.UrlIllustUgoira, id),
+ string.Format(Configs.RefererIllust, id),
+ force: force);
+ if (result == null || result.error)
+ {
+ App.DebugPrint($"error when load ugoira data: {result?.message}, force({force})");
}
return result;
}
public static IllustUserData LoadIllustUserData(string userId, bool force = false)
{
- var list = LoadObject(
+ var list = HttpUtility.LoadObject(
null,
string.Format(Configs.UrlIllustUserAll, userId),
- string.Format(Configs.UrlIllustUser, userId),
+ string.Format(Configs.RefererIllustUser, userId),
force: force);
if (list == null || list.error)
{
- App.DebugPrint($"error when load user data: {list?.message} ({force})");
+ App.DebugPrint($"error when load user data: {list?.message}, force({force})");
}
// TODO
var ids = string.Join("", list.body.illusts.Keys.Take(20).Select(id => $"ids%5B%5D={id}&"));
- var result = LoadObject(
+ var result = HttpUtility.LoadObject(
null,
string.Format(Configs.UrlIllustUserArtworks, userId, ids, 1),
- string.Format(Configs.UrlIllustUser, userId),
+ string.Format(Configs.RefererIllustUser, userId),
force: force);
if (result == null || result.error)
{
- App.DebugPrint($"error when load user illust data: {result?.message} ({force})");
+ App.DebugPrint($"error when load user illust data: {result?.message}, force({force})");
}
return result;
}
@@ -374,7 +392,7 @@ namespace Pixiview.Utils
}
if (downloading && image == null)
{
- file = DownloadImage(url, working, folder);
+ file = HttpUtility.DownloadImage(url, working, folder);
if (file != null)
{
return ImageSource.FromFile(file);
@@ -382,101 +400,6 @@ namespace Pixiview.Utils
}
return image;
}
-
- private static string DownloadImage(string url, string working, string folder)
- {
- try
- {
- var directory = Path.Combine(working, folder);
- if (!Directory.Exists(directory))
- {
- Directory.CreateDirectory(directory);
- }
- var file = Path.Combine(directory, Path.GetFileName(url));
- App.DebugPrint($"download, url: {url}");
- var response = Download(url, headers =>
- {
- headers.Referrer = new Uri(Configs.Referer);
- headers.Add("User-Agent", Configs.UserAgent);
- headers.Add("Accept", Configs.AcceptImage);
- });
- if (response == null)
- {
- return null;
- }
- using (response)
- using (var fs = File.OpenWrite(file))
- {
- response.Content.CopyToAsync(fs).Wait();
- //if (response.Headers.Date != null)
- //{
- // File.SetLastWriteTimeUtc(file, response.Headers.Date.Value.UtcDateTime);
- //}
- }
- return file;
- }
- catch (Exception ex)
- {
- App.DebugError("image.download", ex.Message);
- return null;
- }
- }
-
- private static T TryCount(Func func, int tryCount = 3)
- {
- int tries = 0;
- while (tries < tryCount)
- {
- try
- {
- return func();
- }
- catch (Exception ex)
- {
- tries++;
- System.Threading.Thread.Sleep(400);
- App.DebugError("try.do", $"tries: {tries}, error: {ex.Message}");
- }
- }
- return default;
- }
-
- private static HttpResponseMessage Download(string url, Action headerAction)
- {
- App.DebugPrint($"GET: {url}");
- var uri = new Uri(url);
- var proxy = Configs.Proxy;
- var handler = new HttpClientHandler
- {
- AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
- UseCookies = false
- };
- if (proxy != null)
- {
- handler.Proxy = proxy;
- handler.UseProxy = true;
- }
- var client = new HttpClient(handler)
- {
- BaseAddress = new Uri($"{uri.Scheme}://{uri.Host}"),
- Timeout = TimeSpan.FromSeconds(30)
- };
- return TryCount(() =>
- {
- using (var request = new HttpRequestMessage(HttpMethod.Get, uri.PathAndQuery)
- {
- Version = new Version(2, 0)
- })
- {
- var headers = request.Headers;
- headerAction(headers);
- headers.Add("x-reverse", "yes");
- headers.Add("Accept-Language", Configs.AcceptLanguage);
- //headers.Add("Accept-Encoding", Configs.AcceptEncoding);
- return client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result;
- }
- });
- }
}
public static class Configs
@@ -487,6 +410,9 @@ namespace Pixiview.Utils
public const int MaxThreads = 3;
public const string Referer = "https://www.pixiv.net/";
+ public const string RefererIllust = "https://www.pixiv.net/artworks/{0}";
+ public const string RefererIllustRanking = "https://www.pixiv.net/ranking.php?{0}";
+ public const string RefererIllustUser = "https://www.pixiv.net/users/{0}/artworks";
public static WebProxy Proxy;
private static string Prefix => Proxy == null ?
@@ -497,26 +423,37 @@ namespace Pixiview.Utils
public const int SuffixPreloadLength = 33; // SuffixPreload.Length
public static string UrlIllustList => Prefix + "ajax/top/illust?mode=all&lang=zh";
public static string UrlIllust => Prefix + "artworks/{0}";
+ public static string UrlIllustRanking => Prefix + "ranking.php?{0}";
public static string UrlIllustUserAll => Prefix + "ajax/user/{0}/profile/all?lang=zh";
public static string UrlIllustUserArtworks => Prefix + "ajax/user/{0}/profile/illusts?{1}work_category=illustManga&is_first_page={2}&lang=zh";
- public static string UrlIllustUser => Prefix + "users/{0}/artworks";
public static string UrlIllustPage => Prefix + "ajax/illust/{0}/pages?lang=zh";
+ public static string UrlIllustUgoira => Prefix + "ajax/illust/{0}/ugoira_meta?lang=zh";
public const string UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36";
public const string AcceptImage = "image/png,image/svg+xml,image/*;q=0.8,video/*;q=0.8,*/*;q=0.5";
public const string AcceptJson = "application/json";
//public const string AcceptEncoding = "gzip, deflate";
public const string AcceptLanguage = "zh-cn";
- public const string UserId = "53887721";
+ //public const string UserId = "53887721";
+ //public const string Cookie =
+ // "PHPSESSID=5sn8n049j5c18l0tlj91qrjhesgddhjv; " +
+ // "a_type=0; b_type=1; c_type=29; d_type=2; " +
+ // "p_ab_d_id=1021624041; p_ab_id=2; p_ab_id_2=0; " +
+ // "privacy_policy_agreement=2; " +
+ // "login_ever=yes; " +
+ // "__cfduid=d84153bf70ae67315a8bc297299d39eb61588856027; " +
+ // "first_visit_datetime_pc=2020-05-07+21%3A53%3A47; " +
+ // "yuid_b=MYkIJXc";
+ public const string UserId = "2603358";
public const string Cookie =
- "PHPSESSID=5sn8n049j5c18l0tlj91qrjhesgddhjv; " +
- "a_type=0; b_type=1; c_type=29; d_type=2; " +
- "p_ab_d_id=1021624041; p_ab_id=2; p_ab_id_2=0; " +
- "privacy_policy_agreement=2; " +
- "login_ever=yes; " +
- "__cfduid=d84153bf70ae67315a8bc297299d39eb61588856027; " +
- "first_visit_datetime_pc=2020-05-07+21%3A53%3A47; " +
- "yuid_b=MYkIJXc";
+ "PHPSESSID=2603358_VHyGPeRaz7LpeoFkRsHvjXIpApCMb56a; " +
+ "a_type=0; b_type=1; c_type=31; d_type=2; " +
+ "p_ab_id=2; p_ab_id_2=6; p_ab_d_id=1155161977; " +
+ "privacy_policy_agreement=2; " +
+ "login_ever=yes; " +
+ "__cfduid=d9fa2d4d1ddd30db85ebb519f9855d2561587806747; " +
+ "first_visit_datetime_pc=2019-10-29+22%3A05%3A30; " +
+ "yuid_b=NgcXQWQ";
public static string GetThumbnailUrl(string url)
{