* Pixiview/Utils/Stores.cs:
* Pixiview/Pixiview.projitems: * Pixiview/Utils/IllustData.cs: * Pixiview/Utils/HttpUtility.cs: * Pixiview/Utils/IllustLegacy.cs: * Pixiview/Illust/ViewIllustPage.xaml: * Pixiview/Illust/ViewIllustPage.xaml.cs: feature: view animate * Pixiview/Illust/FavoritesPage.xaml.cs: lazy load favorites page * Pixiview/UI/CardView.cs: * Pixiview/UI/AdaptedPage.cs: * Pixiview/Utils/Converters.cs: * Pixiview/UI/StyleDefinition.cs: * Pixiview/UI/Theme/ThemeBase.cs: * Pixiview/Illust/RankingPage.xaml: * Pixiview/Illust/RankingPage.xaml.cs: * Pixiview/Resources/Languages/zh-CN.xml: * Pixiview/Illust/IllustCollectionPage.cs: * Pixiview.iOS/Renderers/SegmentedControlRenderer.cs: feature: filter ranking * Pixiview.iOS/Info.plist: * Pixiview.iOS/Pixiview.iOS.csproj: * Pixiview.iOS.OpenExtension/Info.plist: * Pixiview.Android/Pixiview.Android.csproj: * Pixiview.Android/Properties/AndroidManifest.xml: version update
This commit is contained in:
parent
4561bc2674
commit
ba46ba02c4
@ -55,7 +55,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Xamarin.Forms" Version="4.6.0.726" />
|
||||
<PackageReference Include="Xamarin.Essentials" Version="1.5.3.1" />
|
||||
<PackageReference Include="Xamarin.Essentials" Version="1.5.3.2" />
|
||||
<PackageReference Include="Newtonsoft.Json">
|
||||
<Version>12.0.3</Version>
|
||||
</PackageReference>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0.511" package="org.tsanie.pixiview" android:versionCode="2">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0.513" package="org.tsanie.pixiview" android:versionCode="3">
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
|
||||
<application android:label="Pixiview" android:icon="@mipmap/icon" android:roundIcon="@mipmap/icon_round"></application>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
@ -14,8 +14,6 @@
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>13.4</string>
|
||||
<key>NSExtension</key>
|
||||
@ -31,6 +29,8 @@
|
||||
<string>com.apple.share-services</string>
|
||||
</dict>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.511</string>
|
||||
<string>1.0.513</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>3</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -79,8 +79,8 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.511</string>
|
||||
<string>1.0.513</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2</string>
|
||||
<string>3</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -143,7 +143,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Xamarin.Forms" Version="4.6.0.726" />
|
||||
<PackageReference Include="Xamarin.Essentials" Version="1.5.3.1" />
|
||||
<PackageReference Include="Xamarin.Essentials" Version="1.5.3.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\Pixiview\Pixiview.projitems" Label="Shared" Condition="Exists('..\Pixiview\Pixiview.projitems')" />
|
||||
|
@ -114,7 +114,7 @@ namespace Pixiview.iOS.Renderers
|
||||
var tintColor = element.TintColor;
|
||||
if (tintColor == default)
|
||||
{
|
||||
return UIColor.QuaternaryLabelColor;
|
||||
return UIColor.SystemGray6Color;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -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<IllustItem> DoGetIllustList(IllustItem[] data, ICommand command)
|
||||
|
@ -15,6 +15,7 @@ namespace Pixiview.Illust
|
||||
{
|
||||
public abstract class FavoriteIllustCollectionPage : IllustCollectionPage<IllustItem[]> { }
|
||||
public abstract class IllustDataCollectionPage : IllustCollectionPage<IllustData> { }
|
||||
public abstract class IllustRankingDataCollectionPage : IllustCollectionPage<IllustRankingData> { }
|
||||
public abstract class IllustUserDataCollectionPage : IllustCollectionPage<IllustUserData> { }
|
||||
|
||||
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<IllustItem> 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;
|
||||
}
|
||||
}
|
||||
|
@ -1,31 +1,86 @@
|
||||
<?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:Pixiview.Illust"
|
||||
xmlns:u="clr-namespace:Pixiview.UI"
|
||||
xmlns:r="clr-namespace:Pixiview.Resources"
|
||||
x:Class="Pixiview.Illust.RankingPage"
|
||||
BackgroundColor="{DynamicResource WindowColor}"
|
||||
Shell.NavBarHasShadow="False">
|
||||
<i:IllustRankingDataCollectionPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:i="clr-namespace:Pixiview.Illust"
|
||||
xmlns:u="clr-namespace:Pixiview.UI"
|
||||
xmlns:r="clr-namespace:Pixiview.Resources"
|
||||
x:Class="Pixiview.Illust.RankingPage"
|
||||
BackgroundColor="{DynamicResource WindowColor}">
|
||||
<Shell.TitleView>
|
||||
<Grid VerticalOptions="Fill" HorizontalOptions="Fill" ColumnSpacing="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Grid.Column="1" Text="{Binding Title}"
|
||||
VerticalTextAlignment="Center" FontAttributes="Bold">
|
||||
<Label.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"/>
|
||||
</Label.GestureRecognizers>
|
||||
</Label>
|
||||
<Label Grid.Column="2" Text="{Binding PanelState}"
|
||||
FontFamily="{DynamicResource IconSolidFontFamily}"
|
||||
FontSize="Small"
|
||||
VerticalTextAlignment="Center"/>
|
||||
</Grid>
|
||||
</Shell.TitleView>
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Order="Primary" Clicked="Refresh_Clicked"
|
||||
IconImageSource="{DynamicResource FontIconRefresh}"/>
|
||||
</ContentPage.ToolbarItems>
|
||||
<Grid>
|
||||
<ScrollView HorizontalOptions="Fill" HorizontalScrollBarVisibility="Never">
|
||||
<ScrollView x:Name="scrollView" Scrolled="ScrollView_Scrolled"
|
||||
HorizontalOptions="Fill" HorizontalScrollBarVisibility="Never">
|
||||
<u:FlowLayout ItemsSource="{Binding Illusts}"
|
||||
HorizontalOptions="Fill" Column="{Binding Columns}"
|
||||
Margin="16, 62, 16, 16" RowSpacing="16" ColumnSpacing="16"
|
||||
Margin="16, 16, 16, 16" RowSpacing="16" ColumnSpacing="16"
|
||||
ItemTemplate="{StaticResource cardView}"/>
|
||||
</ScrollView>
|
||||
<u:BlurryPanel VerticalOptions="Start" HeightRequest="50" Margin="{Binding PanelTopMargin}"/>
|
||||
<!--<u:BlurryPanel VerticalOptions="Start" HeightRequest="50" Margin="{Binding PanelTopMargin}"/>
|
||||
<SearchBar x:Name="searchBar" Placeholder="{r:Text Search}" HeightRequest="50"
|
||||
VerticalOptions="Start"
|
||||
Margin="{Binding PageTopMargin}"
|
||||
CancelButtonColor="{DynamicResource TintColor}"
|
||||
Text="{Binding Keywords, Mode=TwoWay}"
|
||||
SearchButtonPressed="SearchBar_SearchButtonPressed"
|
||||
Unfocused="SearchBar_Unfocused"/>
|
||||
Unfocused="SearchBar_Unfocused"/>-->
|
||||
<u:BlurryPanel x:Name="panelFilter" VerticalOptions="Start" Opacity="0"
|
||||
Margin="{Binding PanelTopMargin}"
|
||||
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}">
|
||||
<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"
|
||||
Margin="6, 3, 6, 6" VerticalOptions="Center"
|
||||
SelectedSegmentIndex="{Binding SegmentType, Mode=TwoWay}">
|
||||
<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}"
|
||||
TextColor="{DynamicResource TintColor}"
|
||||
FontSize="20" Margin="0, 0, 6, 0"
|
||||
Clicked="Filter_Clicked"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Frame HasShadow="False" Margin="0" Padding="20" CornerRadius="8"
|
||||
IsVisible="{Binding IsLoading}"
|
||||
HorizontalOptions="Center" VerticalOptions="Center"
|
||||
@ -34,4 +89,4 @@
|
||||
Color="{DynamicResource WindowColor}"/>
|
||||
</Frame>
|
||||
</Grid>
|
||||
</i:IllustDataCollectionPage>
|
||||
</i:IllustRankingDataCollectionPage>
|
||||
|
@ -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<IllustItem> 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<IllustItem> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,9 @@
|
||||
<Image.Effects>
|
||||
<util:LongPressEffect/>
|
||||
</Image.Effects>
|
||||
<Image.GestureRecognizers>
|
||||
<TapGestureRecognizer Tapped="Image_Tapped"/>
|
||||
</Image.GestureRecognizers>
|
||||
</Image>
|
||||
<Frame HasShadow="False" Margin="0" Padding="20" CornerRadius="8"
|
||||
IsVisible="{Binding Loading}"
|
||||
|
@ -60,6 +60,8 @@ namespace Pixiview.Illust
|
||||
private readonly ICommand longPressed;
|
||||
private readonly ImageSource fontIconLove;
|
||||
private readonly ImageSource fontIconNotLove;
|
||||
private IllustUgoiraData ugoiraData;
|
||||
private Ugoira ugoira;
|
||||
|
||||
public ViewIllustPage(IllustItem illust, bool save)
|
||||
{
|
||||
@ -98,6 +100,12 @@ namespace Pixiview.Illust
|
||||
{
|
||||
base.OnDisappearing();
|
||||
|
||||
if (ugoira != null)
|
||||
{
|
||||
ugoira.TogglePlay(false);
|
||||
IllustItem.IsPlaying = false;
|
||||
}
|
||||
|
||||
if (saveFavorites)
|
||||
{
|
||||
Stores.SaveFavoritesIllusts();
|
||||
@ -127,6 +135,7 @@ namespace Pixiview.Illust
|
||||
{
|
||||
items[i] = new IllustDetailItem
|
||||
{
|
||||
Id = illust.Id,
|
||||
LongPressed = longPressed
|
||||
};
|
||||
if (i == 0)
|
||||
@ -159,6 +168,7 @@ namespace Pixiview.Illust
|
||||
{
|
||||
tmp[i] = new IllustDetailItem
|
||||
{
|
||||
Id = illustItem.Id,
|
||||
LongPressed = longPressed
|
||||
};
|
||||
}
|
||||
@ -197,6 +207,11 @@ namespace Pixiview.Illust
|
||||
{
|
||||
DoLoadImage(1);
|
||||
}
|
||||
else if (illustItem.IllustType == IllustType.Anime)
|
||||
{
|
||||
// anime
|
||||
ugoiraData = Stores.LoadIllustUgoiraData(illustItem.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private void DoLoadImage(int index, bool force = false)
|
||||
@ -268,6 +283,40 @@ namespace Pixiview.Illust
|
||||
}
|
||||
}
|
||||
|
||||
private void Image_Tapped(object sender, EventArgs e)
|
||||
{
|
||||
if (ugoiraData == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var illustItem = IllustItem;
|
||||
|
||||
if (ugoira != null)
|
||||
{
|
||||
var playing = !ugoira.IsPlaying;
|
||||
ugoira.TogglePlay(playing);
|
||||
illustItem.IsPlaying = playing;
|
||||
}
|
||||
else if (((Image)sender).BindingContext is IllustDetailItem item)
|
||||
{
|
||||
if (illustItem.IsPlaying ||
|
||||
illustItem.IllustType != IllustType.Anime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ugoira = new Ugoira(ugoiraData, item);
|
||||
ugoira.FrameChanged += OnUgoiraFrameChanged;
|
||||
illustItem.IsPlaying = true;
|
||||
ugoira.TogglePlay(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUgoiraFrameChanged(object sender, UgoiraEventArgs e)
|
||||
{
|
||||
e.DetailItem.Image = e.Image;
|
||||
}
|
||||
|
||||
private async void Illust_LongPressed(IllustDetailItem item)
|
||||
{
|
||||
List<string> extras = new List<string>();
|
||||
@ -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; }
|
||||
|
@ -91,6 +91,8 @@
|
||||
<Compile Include="$(MSBuildThisFileDirectory)UI\Theme\LightTheme.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)UI\Theme\ThemeBase.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)UI\BlurryPanel.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Utils\IllustLegacy.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Utils\HttpUtility.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="$(MSBuildThisFileDirectory)Illust\" />
|
||||
|
@ -10,6 +10,19 @@
|
||||
<Enabled>启用</Enabled>
|
||||
<Host>主机</Host>
|
||||
<Port>端口</Port>
|
||||
<Daily>今日</Daily>
|
||||
<Weekly>本周</Weekly>
|
||||
<Monthly>本月</Monthly>
|
||||
<Male>受男性欢迎</Male>
|
||||
<monthly>当月截至 {0}</monthly>
|
||||
<weekly>当周截至 {0}</weekly>
|
||||
<daily>当日 {0}</daily>
|
||||
<male>受欢迎 {0}</male>
|
||||
<monthly_r18>当月截至 {0}</monthly_r18>
|
||||
<weekly_r18>当周截至 {0}</weekly_r18>
|
||||
<daily_r18>当日 {0}</daily_r18>
|
||||
<male_r18>受欢迎 {0}</male_r18>
|
||||
<General>一般</General>
|
||||
<R18>R-18</R18>
|
||||
<Follow>已关注</Follow>
|
||||
<Recommends>推荐</Recommends>
|
||||
|
@ -27,6 +27,7 @@ namespace Pixiview.UI
|
||||
|
||||
public event EventHandler Load;
|
||||
public event EventHandler Unload;
|
||||
public event EventHandler<ThicknessEventArgs> PageTopMarginChanged;
|
||||
public event EventHandler<OrientationEventArgs> 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; }
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
502
Pixiview/Utils/HttpUtility.cs
Normal file
502
Pixiview/Utils/HttpUtility.cs
Normal file
@ -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<T>(string file, string url, string referer,
|
||||
bool force = false,
|
||||
Action<HttpRequestHeaders> header = null,
|
||||
Func<T, string> namehandler = null,
|
||||
Func<string, string> 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<T>(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<T>(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<HttpRequestHeaders> 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<T>(Func<T> 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<UgoiraEventArgs> 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; }
|
||||
}
|
||||
}
|
@ -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<string, Illust> works;
|
||||
}
|
||||
|
||||
public class IllustUgoiraData : IllustResponse<IllustUgoiraBody> { }
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
107
Pixiview/Utils/IllustLegacy.cs
Normal file
107
Pixiview/Utils/IllustLegacy.cs
Normal file
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<T>(string file, string url, string referer, bool force = false, Func<string, string> 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<T>(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<IllustData>(
|
||||
var result = HttpUtility.LoadObject<IllustData>(
|
||||
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<IllustRankingData>(
|
||||
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<IllustPreloadBody>(
|
||||
var result = HttpUtility.LoadObject<IllustPreloadBody>(
|
||||
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<IllustPageData>(
|
||||
var result = HttpUtility.LoadObject<IllustPageData>(
|
||||
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<IllustUgoiraData>(
|
||||
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<IllustUserListData>(
|
||||
var list = HttpUtility.LoadObject<IllustUserListData>(
|
||||
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<IllustUserData>(
|
||||
var result = HttpUtility.LoadObject<IllustUserData>(
|
||||
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<T>(Func<T> 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<HttpRequestHeaders> 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)
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user