feature: support iOS login

This commit is contained in:
Tsanie Lily 2020-05-18 01:31:47 +08:00
parent 2ae415f00f
commit 5380a41b00
16 changed files with 307 additions and 34 deletions

View File

@ -83,6 +83,7 @@
<Compile Include="Renderers\OptionEntryRenderer.cs" /> <Compile Include="Renderers\OptionEntryRenderer.cs" />
<Compile Include="Renderers\AppShellSection\AppAppearanceTracker.cs" /> <Compile Include="Renderers\AppShellSection\AppAppearanceTracker.cs" />
<Compile Include="Renderers\BlurryPanelRenderer.cs" /> <Compile Include="Renderers\BlurryPanelRenderer.cs" />
<Compile Include="Renderers\HybridWebViewRenderer.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<InterfaceDefinition Include="Resources\LaunchScreen.storyboard" /> <InterfaceDefinition Include="Resources\LaunchScreen.storyboard" />

View File

@ -0,0 +1,80 @@
using System;
using Foundation;
using Pixiview.iOS.Renderers;
using Pixiview.Login;
using Pixiview.Utils;
using WebKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace Pixiview.iOS.Renderers
{
public class HybridWebViewRenderer : WkWebViewRenderer
{
public HybridWebViewRenderer() : this(new WKWebViewConfiguration())
{
}
public HybridWebViewRenderer(WKWebViewConfiguration config) : base(config)
{
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.NewElement is HybridWebView webView)
{
string url = webView.Uri;
//Configuration.SetUrlSchemeHandler
CustomUserAgent = webView.UserAgent;
NavigationDelegate = new PixivNavigationDelegate(webView);
LoadRequest(new NSUrlRequest(NSUrl.FromString(url)));
}
}
private class PixivNavigationDelegate : WKNavigationDelegate
{
private readonly HybridWebView hybridWebView;
public PixivNavigationDelegate(HybridWebView hybrid)
{
hybridWebView = hybrid;
}
public override void DecidePolicy(WKWebView webView, WKNavigationResponse navigationResponse, Action<WKNavigationResponsePolicy> decisionHandler)
{
var url = webView.Url;
if (url.Host == "www.pixiv.net" && url.Path == "/")
{
if (navigationResponse.Response is NSHttpUrlResponse response)
{
if (response.AllHeaderFields.TryGetValue(new NSString("x-userid"), out var val))
{
Configs.SetUserId(val.ToString(), true);
}
}
}
decisionHandler(WKNavigationResponsePolicy.Allow);
}
public override async void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
var url = webView.Url;
if (url.Host == "www.pixiv.net" && url.Path == "/")
{
var store = webView.Configuration.WebsiteDataStore.HttpCookieStore;
var result = await Configs.RequestCookieContainer(store);
if (result && hybridWebView != null)
{
hybridWebView.OnLoginHandle();
}
}
}
}
}
}

View File

@ -31,6 +31,9 @@ namespace Pixiview
private void InitPreferences() private void InitPreferences()
{ {
Configs.SetCookie(Preferences.Get(Configs.CookieKey, null));
Configs.SetUserId(Preferences.Get(Configs.UserIdKey, null));
Configs.IsOnR18 = Preferences.Get(Configs.IsOnR18Key, false); Configs.IsOnR18 = Preferences.Get(Configs.IsOnR18Key, false);
var isProxied = Preferences.Get(Configs.IsProxiedKey, false); var isProxied = Preferences.Get(Configs.IsProxiedKey, false);
if (isProxied) if (isProxied)

View File

@ -15,6 +15,9 @@
<Shell.FlyoutHeaderTemplate> <Shell.FlyoutHeaderTemplate>
<DataTemplate> <DataTemplate>
<Grid RowSpacing="0" BackgroundColor="{DynamicResource WindowColor}" Padding="20, 0, 0, 20"> <Grid RowSpacing="0" BackgroundColor="{DynamicResource WindowColor}" Padding="20, 0, 0, 20">
<Grid.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"/>
</Grid.GestureRecognizers>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="80"/> <RowDefinition Height="80"/>
<RowDefinition Height="25"/> <RowDefinition Height="25"/>

View File

@ -1,5 +1,7 @@
using System; using System;
using Pixiview.Login;
using Pixiview.Utils; using Pixiview.Utils;
using Xamarin.Essentials;
using Xamarin.Forms; using Xamarin.Forms;
namespace Pixiview namespace Pixiview
@ -42,6 +44,35 @@ namespace Pixiview
StatusBarHeight = height StatusBarHeight = height
}); });
} }
private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
PushToLogin(null);
}
private bool isLoginOpened;
public void PushToLogin(Action after)
{
if (isLoginOpened)
{
return;
}
isLoginOpened = true;
var loginPage = new LoginPage(()=>
{
isLoginOpened = false;
after?.Invoke();
});
loginPage.Disappearing += (sender, e) =>
{
isLoginOpened = false;
};
MainThread.BeginInvokeOnMainThread(async () =>
{
await Navigation.PushModalAsync(loginPage);
});
}
} }
public class BarHeightEventArgs : EventArgs public class BarHeightEventArgs : EventArgs

View File

@ -530,6 +530,10 @@ namespace Pixiview.Illust
illustData = DoLoadIllustData(force); illustData = DoLoadIllustData(force);
if (illustData == null) if (illustData == null)
{ {
AppShell.Current.PushToLogin(()=>
{
StartLoad(true);
});
//App.DebugError("illusts.load", "failed to load illusts data."); //App.DebugError("illusts.load", "failed to load illusts data.");
IsLoading = false; IsLoading = false;
IsBottomLoading = false; IsBottomLoading = false;

View File

@ -0,0 +1,35 @@
using System;
using Xamarin.Forms;
namespace Pixiview.Login
{
public class HybridWebView : WebView
{
public static readonly BindableProperty UriProperty = BindableProperty.Create(
nameof(Uri),
typeof(string),
typeof(HybridWebView));
public static readonly BindableProperty UserAgentProperty = BindableProperty.Create(
nameof(UserAgent),
typeof(string),
typeof(HybridWebView));
public event EventHandler LoginHandle;
public string Uri
{
get => (string)GetValue(UriProperty);
set => SetValue(UriProperty, value);
}
public string UserAgent
{
get => (string)GetValue(UserAgentProperty);
set => SetValue(UserAgentProperty, value);
}
public void OnLoginHandle()
{
LoginHandle?.Invoke(this, EventArgs.Empty);
}
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<u:AdaptedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:u="clr-namespace:Pixiview.UI"
xmlns:r="clr-namespace:Pixiview.Resources"
xmlns:l="clr-namespace:Pixiview.Login"
x:Class="Pixiview.Login.LoginPage"
Title="{r:Text Login}">
<Grid>
<l:HybridWebView x:Name="webView" VerticalOptions="Fill"
Uri="https://accounts.pixiv.net/login?lang=zh"
LoginHandle="WebView_LoginHandle"/>
<StackLayout HorizontalOptions="End" VerticalOptions="Start"
Margin="{Binding PanelTopMargin}">
<Button BackgroundColor="#64000000" CornerRadius="20"
TextColor="White" Clicked="Button_Clicked"
FontSize="22" Padding="10" Margin="8"
FontFamily="{DynamicResource IconSolidFontFamily}"
Text="{DynamicResource IconClose}"/>
</StackLayout>
</Grid>
</u:AdaptedPage>

View File

@ -0,0 +1,31 @@
using System;
using Pixiview.UI;
using Pixiview.Utils;
namespace Pixiview.Login
{
public partial class LoginPage : AdaptedPage
{
private readonly Action action;
public LoginPage(Action after)
{
InitializeComponent();
webView.UserAgent = Configs.UserAgent;
BindingContext = this;
action = after;
}
private async void Button_Clicked(object sender, EventArgs e)
{
await Navigation.PopModalAsync();
}
private async void WebView_LoginHandle(object sender, EventArgs e)
{
await Navigation.PopModalAsync();
action?.Invoke();
}
}
}

View File

@ -46,6 +46,10 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:UpdateDesignTimeXaml</Generator> <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Login\LoginPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)App.cs" /> <Compile Include="$(MSBuildThisFileDirectory)App.cs" />
@ -101,6 +105,10 @@
<DependentUpon>RelatedIllustsPage.xaml</DependentUpon> <DependentUpon>RelatedIllustsPage.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="$(MSBuildThisFileDirectory)Utils\Ugoira.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Utils\Ugoira.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Login\LoginPage.xaml.cs">
<DependentUpon>LoginPage.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Login\HybridWebView.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="$(MSBuildThisFileDirectory)Illust\" /> <Folder Include="$(MSBuildThisFileDirectory)Illust\" />
@ -109,5 +117,6 @@
<Folder Include="$(MSBuildThisFileDirectory)Resources\Languages\" /> <Folder Include="$(MSBuildThisFileDirectory)Resources\Languages\" />
<Folder Include="$(MSBuildThisFileDirectory)UI\" /> <Folder Include="$(MSBuildThisFileDirectory)UI\" />
<Folder Include="$(MSBuildThisFileDirectory)UI\Theme\" /> <Folder Include="$(MSBuildThisFileDirectory)UI\Theme\" />
<Folder Include="$(MSBuildThisFileDirectory)Login\" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -5,6 +5,7 @@
<Cancel>取消</Cancel> <Cancel>取消</Cancel>
<Yes></Yes> <Yes></Yes>
<No></No> <No></No>
<Login>登录</Login>
<Illusts>插画</Illusts> <Illusts>插画</Illusts>
<Proxy>代理</Proxy> <Proxy>代理</Proxy>
<Detail>详细</Detail> <Detail>详细</Detail>

View File

@ -51,6 +51,7 @@ namespace Pixiview.UI
public const string IconCaretCircleLeft = "\uf32e"; public const string IconCaretCircleLeft = "\uf32e";
public const string IconCaretCircleRight = "\uf330"; public const string IconCaretCircleRight = "\uf330";
public const string IconCalendarDay = "\uf783"; public const string IconCalendarDay = "\uf783";
public const string IconClose = "\uf057";
static StyleDefinition() static StyleDefinition()
{ {

View File

@ -19,6 +19,7 @@ namespace Pixiview.UI.Theme
public const string FontIconCalendarDay = nameof(FontIconCalendarDay); public const string FontIconCalendarDay = nameof(FontIconCalendarDay);
public const string IconCircleCheck = nameof(IconCircleCheck); public const string IconCircleCheck = nameof(IconCircleCheck);
public const string IconCaretDown = nameof(IconCaretDown); public const string IconCaretDown = nameof(IconCaretDown);
public const string IconClose = nameof(IconClose);
public const string StatusBarStyle = nameof(StatusBarStyle); public const string StatusBarStyle = nameof(StatusBarStyle);
public const string WindowColor = nameof(WindowColor); public const string WindowColor = nameof(WindowColor);
@ -66,6 +67,7 @@ namespace Pixiview.UI.Theme
Add(IconCircleCheck, StyleDefinition.IconCircleCheck); Add(IconCircleCheck, StyleDefinition.IconCircleCheck);
Add(IconCaretDown, StyleDefinition.IconCaretDown); Add(IconCaretDown, StyleDefinition.IconCaretDown);
Add(IconClose, StyleDefinition.IconClose);
} }
private FontImageSource GetSolidIcon(string icon, string family, Color color = default) private FontImageSource GetSolidIcon(string icon, string family, Color color = default)

View File

@ -132,18 +132,20 @@ namespace Pixiview.Utils
for (int i = from; i < to; i++) for (int i = from; i < to; i++)
{ {
var index = i; var index = i;
bool flag; while (true)
{
lock (sync) lock (sync)
{ {
flag = count >= max; if (count < max)
}
while (flag)
{ {
break;
}
if (disposed) if (disposed)
{ {
App.DebugPrint($"parallel task determinate, disposed"); App.DebugPrint($"parallel task determinate, disposed");
return; return;
} }
}
Thread.Sleep(100); Thread.Sleep(100);
} }
lock (sync) lock (sync)
@ -175,6 +177,7 @@ namespace Pixiview.Utils
} }
}); });
} }
App.DebugPrint($"parallel task done");
} }
public void Dispose() public void Dispose()

View File

@ -39,10 +39,18 @@ namespace Pixiview.Utils
} }
headers.Add("User-Agent", Configs.UserAgent); headers.Add("User-Agent", Configs.UserAgent);
headers.Add("Accept", Configs.AcceptJson); headers.Add("Accept", Configs.AcceptJson);
headers.Add("Cookie", Configs.Cookie); var cookie = Configs.Cookie;
if (cookie != null)
{
headers.Add("Cookie", cookie);
}
if (header == null) if (header == null)
{ {
headers.Add("X-User-Id", Configs.UserId); var userId = Configs.UserId;
if (userId != null)
{
headers.Add("X-User-Id", userId);
}
} }
else else
{ {

View File

@ -4,6 +4,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using Pixiview.Illust; using Pixiview.Illust;
using Xamarin.Essentials; using Xamarin.Essentials;
@ -470,6 +471,8 @@ namespace Pixiview.Utils
public static class Configs public static class Configs
{ {
public const string CookieKey = "cookies";
public const string UserIdKey = "user_id";
public const string IsOnR18Key = "is_on_r18"; public const string IsOnR18Key = "is_on_r18";
public const string IsProxiedKey = "is_proxied"; public const string IsProxiedKey = "is_proxied";
public const string HostKey = "host"; public const string HostKey = "host";
@ -490,6 +493,65 @@ namespace Pixiview.Utils
private static string Prefix => Proxy == null ? private static string Prefix => Proxy == null ?
"https://hk.tsanie.us/reverse/" : "https://hk.tsanie.us/reverse/" :
"https://www.pixiv.net/"; "https://www.pixiv.net/";
public static string UserId { get; private set; }
public static string Cookie { get; private set; }
public static void SetUserId(string userId, bool save = false)
{
UserId = userId;
if (!save)
{
return;
}
if (userId == null)
{
Preferences.Remove(UserIdKey);
}
else
{
Preferences.Set(UserIdKey, userId);
}
}
public static void SetCookie(string cookie)
{
Cookie = cookie;
}
#if __IOS__
public static Task<bool> RequestCookieContainer(WebKit.WKHttpCookieStore cookieStore)
{
var task = new TaskCompletionSource<bool>();
cookieStore.GetAllCookies(cookies =>
{
var list = new List<string>();
foreach (var c in cookies)
{
#if DEBUG
App.DebugPrint($"domain: {c.Domain}, path: {c.Path}, {c.Name}={c.Value}, http only: {c.IsHttpOnly}, session only: {c.IsSessionOnly}");
#endif
var domain = c.Domain;
if (domain == null)
{
continue;
}
if (domain != "www.pixiv.net" && domain != ".pixiv.net")
{
continue;
}
list.Add($"{c.Name}={c.Value}");
}
var cookie = string.Join("; ", list);
Cookie = cookie;
Preferences.Set(CookieKey, cookie);
task.SetResult(true);
});
return task.Task;
}
#elif __ANDROID__
task.SetResult(false);
#endif
public const string SuffixPreload = " id=\"meta-preload-data\" content='"; public const string SuffixPreload = " id=\"meta-preload-data\" content='";
public const int SuffixPreloadLength = 33; // SuffixPreload.Length public const int SuffixPreloadLength = 33; // SuffixPreload.Length
@ -508,29 +570,6 @@ namespace Pixiview.Utils
//public const string AcceptEncoding = "gzip, deflate"; //public const string AcceptEncoding = "gzip, deflate";
public const string AcceptLanguage = "zh-cn"; public const string AcceptLanguage = "zh-cn";
#if __ANDROID__
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";
#else
public const string UserId = "2603358";
public const string Cookie =
"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";
#endif
private const string URL_PREVIEW = "https://i.pximg.net/c/360x360_70"; private const string URL_PREVIEW = "https://i.pximg.net/c/360x360_70";
public static string GetThumbnailUrl(string url) public static string GetThumbnailUrl(string url)