diff --git a/Pixiview.iOS/Info.plist b/Pixiview.iOS/Info.plist
index 8e84970..c6a9565 100644
--- a/Pixiview.iOS/Info.plist
+++ b/Pixiview.iOS/Info.plist
@@ -44,5 +44,7 @@
UIStatusBarStyle
UIStatusBarStyleDefault
+ NSPhotoLibraryUsageDescription
+ 需要访问您的图片库
diff --git a/Pixiview.iOS/Pixiview.iOS.csproj b/Pixiview.iOS/Pixiview.iOS.csproj
index dc9916f..35005d7 100644
--- a/Pixiview.iOS/Pixiview.iOS.csproj
+++ b/Pixiview.iOS/Pixiview.iOS.csproj
@@ -76,6 +76,7 @@
+
diff --git a/Pixiview.iOS/Services/FileStore.cs b/Pixiview.iOS/Services/FileStore.cs
new file mode 100644
index 0000000..3e29a7c
--- /dev/null
+++ b/Pixiview.iOS/Services/FileStore.cs
@@ -0,0 +1,45 @@
+using System.Threading.Tasks;
+using Pixiview.iOS.Services;
+using Pixiview.Utils;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.iOS;
+
+[assembly: Dependency(typeof(FileStore))]
+namespace Pixiview.iOS.Services
+{
+ public class FileStore : IFileStore
+ {
+
+ public Task SaveImageToGalleryAsync(ImageSource image)
+ {
+ IImageSourceHandler renderer;
+ if (image is UriImageSource)
+ {
+ renderer = new ImageLoaderSourceHandler();
+ }
+ else if (image is FileImageSource)
+ {
+ renderer = new FileImageSourceHandler();
+ }
+ else
+ {
+ renderer = new StreamImagesourceHandler();
+ }
+ var photo = renderer.LoadImageAsync(image).Result;
+
+ var task = new TaskCompletionSource();
+ if (photo == null)
+ {
+ task.SetResult(null);
+ }
+ else
+ {
+ photo.SaveToPhotosAlbum((img, error) =>
+ {
+ task.SetResult(error?.ToString());
+ });
+ }
+ return task.Task;
+ }
+ }
+}
diff --git a/Pixiview/MainPage.xaml b/Pixiview/MainPage.xaml
index a24ee7d..1055071 100644
--- a/Pixiview/MainPage.xaml
+++ b/Pixiview/MainPage.xaml
@@ -14,8 +14,8 @@
Title="{r:Text Follow}"
OrientationChanged="Page_OrientationChanged">
-
+
diff --git a/Pixiview/MainPage.xaml.cs b/Pixiview/MainPage.xaml.cs
index c3756d6..316badc 100644
--- a/Pixiview/MainPage.xaml.cs
+++ b/Pixiview/MainPage.xaml.cs
@@ -33,7 +33,7 @@ namespace Pixiview
if (!page.loaded && now && Stores.NetworkAvailable)
{
page.loaded = true;
- Task.Run(page.DoLoadIllusts);
+ Task.Run(() => page.DoLoadIllusts());
}
}
@@ -74,10 +74,7 @@ namespace Pixiview
public override void OnLoad()
{
App.DebugPrint($"folder: {Stores.PersonalFolder}");
- if (!loaded)
- {
- Loading = true;
- }
+ Loading = true;
}
protected override void OnAppearing()
@@ -109,9 +106,9 @@ namespace Pixiview
#region - Illust Tasks -
- async void DoLoadIllusts()
+ void DoLoadIllusts(bool force = false)
{
- illustData = await Stores.LoadIllustData();
+ illustData = Stores.LoadIllustData(force);
if (illustData == null)
{
App.DebugError("illusts.load", "failed to load illusts data.");
@@ -215,9 +212,14 @@ namespace Pixiview
}
}
- private void NavigationTitle_RightButtonClicked(object sender, EventArgs e)
+ private void Refresh_Clicked(object sender, EventArgs e)
{
- DisplayAlert("title", "message", "Ok");
+ if (Loading)
+ {
+ return;
+ }
+ Loading = true;
+ Task.Run(() => DoLoadIllusts(true));
}
}
diff --git a/Pixiview/Resources/Languages/zh-CN.xml b/Pixiview/Resources/Languages/zh-CN.xml
index d12a3ae..b6a1bcf 100644
--- a/Pixiview/Resources/Languages/zh-CN.xml
+++ b/Pixiview/Resources/Languages/zh-CN.xml
@@ -1,4 +1,8 @@
+ Pixiview
+ OK
已关注
+ 预览
+ 成功保存图片到照片库。
\ No newline at end of file
diff --git a/Pixiview/Resources/ResourceHelper.cs b/Pixiview/Resources/ResourceHelper.cs
index 46d31e3..6839cd6 100644
--- a/Pixiview/Resources/ResourceHelper.cs
+++ b/Pixiview/Resources/ResourceHelper.cs
@@ -10,6 +10,10 @@ namespace Pixiview.Resources
{
public class ResourceHelper
{
+ public static string Title => GetResource(nameof(Title));
+ public static string Ok => GetResource(nameof(Ok));
+ public static string SaveSuccess => GetResource(nameof(SaveSuccess));
+
static readonly Dictionary dict = new Dictionary();
public static string GetResource(string name, params object[] args)
diff --git a/Pixiview/UI/StyleDefinition.cs b/Pixiview/UI/StyleDefinition.cs
index 950df87..bfbebb3 100644
--- a/Pixiview/UI/StyleDefinition.cs
+++ b/Pixiview/UI/StyleDefinition.cs
@@ -20,6 +20,7 @@ namespace Pixiview.UI
public static Thickness TotalBarOffset;
public const string IconLayer = "\uf302";
+ public const string IconRefresh = "\uf2f1";
public const string IconOption = "\uf013";
public const string IconDownload = "\uf019";
diff --git a/Pixiview/UI/Theme/ThemeBase.cs b/Pixiview/UI/Theme/ThemeBase.cs
index 98a03c7..8a0d40c 100644
--- a/Pixiview/UI/Theme/ThemeBase.cs
+++ b/Pixiview/UI/Theme/ThemeBase.cs
@@ -6,6 +6,7 @@ namespace Pixiview.UI.Theme
{
public const string TitleButton = nameof(TitleButton);
public const string TitleLabel = nameof(TitleLabel);
+ public const string FontIconRefresh = nameof(FontIconRefresh);
public const string FontIconOption = nameof(FontIconOption);
public const string FontIconDownload = nameof(FontIconDownload);
@@ -27,6 +28,7 @@ namespace Pixiview.UI.Theme
//public const string Horizon10 = nameof(Horizon10);
public const string ScreenBottomPadding = nameof(ScreenBottomPadding);
public const string NavigationBarHeight = nameof(NavigationBarHeight);
+ public const string IconRefresh = nameof(IconRefresh);
public const string IconOption = nameof(IconOption);
public const string IconDownload = nameof(IconDownload);
@@ -36,6 +38,7 @@ namespace Pixiview.UI.Theme
Add(FontSizeTitleIcon, StyleDefinition.FontSizeTitleIcon);
//Add(Horizon10, StyleDefinition.Horizon10);
Add(ScreenBottomPadding, StyleDefinition.ScreenBottomPadding);
+ Add(IconRefresh, StyleDefinition.IconRefresh);
Add(IconOption, StyleDefinition.IconOption);
Add(IconDownload, StyleDefinition.IconDownload);
@@ -74,6 +77,12 @@ namespace Pixiview.UI.Theme
}
});
+ Add(FontIconRefresh, new FontImageSource
+ {
+ FontFamily = iconSolidFontFamily,
+ Glyph = StyleDefinition.IconRefresh,
+ Size = StyleDefinition.FontSizeTitle
+ });
Add(FontIconOption, new FontImageSource
{
FontFamily = iconSolidFontFamily,
diff --git a/Pixiview/Utils/IFileStore.cs b/Pixiview/Utils/IFileStore.cs
new file mode 100644
index 0000000..bfba038
--- /dev/null
+++ b/Pixiview/Utils/IFileStore.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+using Xamarin.Forms;
+
+namespace Pixiview.Utils
+{
+ public interface IFileStore
+ {
+ Task SaveImageToGalleryAsync(ImageSource image);
+ }
+}
diff --git a/Pixiview/Utils/IllustData.cs b/Pixiview/Utils/IllustData.cs
index dbfab17..2442de9 100644
--- a/Pixiview/Utils/IllustData.cs
+++ b/Pixiview/Utils/IllustData.cs
@@ -105,4 +105,26 @@ namespace Pixiview.Utils
}
}
}
+
+ public class IllustPageData
+ {
+ public bool error;
+ public string message;
+ public Body[] body;
+
+ public class Body
+ {
+ public Urls urls;
+ public int width;
+ public int height;
+
+ public class Urls
+ {
+ public string thumb_mini;
+ public string small;
+ public string regular;
+ public string original;
+ }
+ }
+ }
}
diff --git a/Pixiview/Utils/Stores.cs b/Pixiview/Utils/Stores.cs
index b7cb81f..3cb0b4e 100644
--- a/Pixiview/Utils/Stores.cs
+++ b/Pixiview/Utils/Stores.cs
@@ -4,7 +4,6 @@ using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
-using System.Threading.Tasks;
using Newtonsoft.Json;
using Xamarin.Essentials;
using Xamarin.Forms;
@@ -15,8 +14,9 @@ namespace Pixiview.Utils
{
public static readonly string PersonalFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
public static readonly string CacheFolder = Environment.GetFolderPath(Environment.SpecialFolder.InternetCache);
- private const string imageFolder = "img-master";
- private const string previewFolder = "img-preview";
+ private const string pagesFolder = "pages";
+ private const string imageFolder = "img-original";
+ private const string previewFolder = "img-master";
private const string thumbFolder = "img-thumb";
private const string userFolder = "user-profile";
private const string illustFile = "illust.json";
@@ -37,11 +37,10 @@ namespace Pixiview.Utils
}
}
- public static async Task LoadIllustData()
+ private static T LoadObject(string file, string url, string referer = null, bool force = false, IEnumerable<(string header, string value)> headers = null)
{
- var file = Path.Combine(PersonalFolder, illustFile);
string content = null;
- if (File.Exists(file))
+ if (!force && File.Exists(file))
{
try
{
@@ -49,44 +48,76 @@ namespace Pixiview.Utils
}
catch (Exception ex)
{
- App.DebugError("illust.load", $"failed to read file: {file}, error: {ex.Message}");
+ App.DebugError("load", $"failed to read file: {file}, error: {ex.Message}");
}
}
if (content == null)
{
- var response = Download(Configs.UrlIllust, headers: new[]
- {
- ("cookie", Configs.Cookie),
- ("x-user-id", Configs.UserId)
- });
+ var response = Download(url, referer, headers);
if (response == null)
{
- return null;
+ return default;
}
using (response)
{
- content = await response.Content.ReadAsStringAsync();
+ content = response.Content.ReadAsStringAsync().Result;
try
{
+ var folder = Path.GetDirectoryName(file);
+ if (!Directory.Exists(folder))
+ {
+ Directory.CreateDirectory(folder);
+ }
File.WriteAllText(file, content, Encoding.UTF8);
}
catch (Exception ex)
{
- App.DebugError("illust.save", $"failed to save illust JSON object, error: {ex.Message}");
+ App.DebugError("save", $"failed to save illust JSON object, error: {ex.Message}");
}
}
}
try
{
- return JsonConvert.DeserializeObject(content);
+ return JsonConvert.DeserializeObject(content);
}
catch (Exception ex)
{
- App.DebugError("illust.load", $"failed to parse illust JSON object, error: {ex.Message}");
- return null;
+ App.DebugError("load", $"failed to parse illust JSON object, error: {ex.Message}");
+ return default;
}
}
+ public static IllustData LoadIllustData(bool force = false)
+ {
+ var file = Path.Combine(PersonalFolder, illustFile);
+ var result = LoadObject(
+ file,
+ Configs.UrlIllustList,
+ force: force,
+ headers: new[]
+ {
+ ("cookie", Configs.Cookie),
+ ("x-user-id", Configs.UserId)
+ });
+ return result;
+ }
+
+ public static IllustPageData LoadIllustPageData(string id, bool force = false)
+ {
+ var file = Path.Combine(PersonalFolder, pagesFolder, $"{id}.json");
+ var result = LoadObject(
+ file,
+ string.Format(Configs.UrlIllustPage, id),
+ string.Format(Configs.UrlIllust, id),
+ force: force,
+ headers: new[]
+ {
+ ("cookie", Configs.Cookie),
+ ("x-user-id", Configs.UserId)
+ });
+ return result;
+ }
+
public static ImageSource LoadIllustImage(string url)
{
return LoadImage(url, PersonalFolder, imageFolder);
@@ -110,15 +141,32 @@ namespace Pixiview.Utils
private static ImageSource LoadImage(string url, string working, string folder)
{
var file = Path.Combine(working, folder, Path.GetFileName(url));
- if (!File.Exists(file))
+ ImageSource image;
+ if (File.Exists(file))
+ {
+ try
+ {
+ image = ImageSource.FromFile(file);
+ }
+ catch (Exception ex)
+ {
+ App.DebugError("image.load", $"failed to load image from file: {file}, error: {ex.Message}");
+ image = null;
+ }
+ }
+ else
+ {
+ image = null;
+ }
+ if (image == null)
{
file = DownloadImage(url, working, folder);
+ if (file != null)
+ {
+ return ImageSource.FromFile(file);
+ }
}
- if (file != null)
- {
- return ImageSource.FromFile(file);
- }
- return null;
+ return image;
}
private static string DownloadImage(string url, string working, string folder)
@@ -216,7 +264,9 @@ namespace Pixiview.Utils
public const int MaxThreads = 3;
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.129 Safari/537.36";
- public const string UrlIllust = "https://www.pixiv.net/ajax/top/illust?mode=all&lang=zh";
+ public const string UrlIllustList = "https://www.pixiv.net/ajax/top/illust?mode=all&lang=zh";
+ public const string UrlIllust = "https://www.pixiv.net/artworks/{0}";
+ public const string UrlIllustPage = "https://www.pixiv.net/ajax/illust/{0}/pages?lang=zh";
public const string Cookie = "first_visit_datetime_pc=2019-10-29+22%3A05%3A30; p_ab_id=2; p_ab_id_2=6;" +
" p_ab_d_id=1155161977; a_type=0; b_type=1; d_type=2; module_orders_mypage=%5B%7B%22name%22%3A%22s" +
"ketch_live%22%2C%22visible%22%3Atrue%7D%2C%7B%22name%22%3A%22tag_follow%22%2C%22visible%22%3Atrue" +
diff --git a/Pixiview/ViewIllustPage.xaml b/Pixiview/ViewIllustPage.xaml
index 3025028..482cd67 100644
--- a/Pixiview/ViewIllustPage.xaml
+++ b/Pixiview/ViewIllustPage.xaml
@@ -18,6 +18,9 @@
+
+
+
@@ -26,6 +29,10 @@
Aspect="AspectFit"/>
+
diff --git a/Pixiview/ViewIllustPage.xaml.cs b/Pixiview/ViewIllustPage.xaml.cs
index c8bd921..149163b 100644
--- a/Pixiview/ViewIllustPage.xaml.cs
+++ b/Pixiview/ViewIllustPage.xaml.cs
@@ -1,6 +1,8 @@
using System;
+using System.Threading.Tasks;
+using Pixiview.Resources;
using Pixiview.UI;
-using Pixiview.UI.Theme;
+using Pixiview.Utils;
using Xamarin.Essentials;
using Xamarin.Forms;
@@ -45,6 +47,8 @@ namespace Pixiview
private set => SetValue(IllustItemProperty, value);
}
+ public int CurrentPage { get; private set; }
+
public ViewIllustPage(IllustItem illust)
{
IllustItem = illust;
@@ -62,25 +66,51 @@ namespace Pixiview
}
var items = new IllustDetailItem[illust.PageCount];
- if (items.Length > 0)
+ if (items.Length > 1)
{
IsPageVisible = true;
PagePositionText = $"1/{items.Length}";
}
-
- items[0] = new IllustDetailItem
+ else
{
- Image = illust.Image,
- Loading = true
- };
+ IsPageVisible = false;
+ }
+
+ for (var i = 0; i < items.Length; i++)
+ {
+ items[i] = new IllustDetailItem();
+ if (i == 0)
+ {
+ items[i].Image = illust.Image;
+ }
+ }
Illusts = items;
UpdatePageTopMargin(CurrentOrientation);
+ Task.Run(DoLoadImages);
}
private void CarouselView_PositionChanged(object sender, PositionChangedEventArgs e)
{
- PagePositionText = $"{e.CurrentPosition + 1}/{Illusts.Length}";
+ var index = e.CurrentPosition;
+ CurrentPage = index;
+ var items = Illusts;
+ var length = items.Length;
+ PagePositionText = $"{index + 1}/{length}";
+
+ var item = items[index];
+ if (!item.Loading && item.Image == null)
+ {
+ Task.Run(() => DoLoadImage(index));
+ }
+ if (index < length - 1)
+ {
+ item = items[index + 1];
+ if (!item.Loading && item.Image == null)
+ {
+ Task.Run(() => DoLoadImage(index + 1));
+ }
+ }
}
private void Page_OrientationChanged(object sender, OrientationEventArgs e)
@@ -114,9 +144,103 @@ namespace Pixiview
}
}
- private void Download_Clicked(object sender, EventArgs e)
+ private void DoLoadImages()
{
+ var pages = Stores.LoadIllustPageData(IllustItem.Id);
+ if (pages == null)
+ {
+ App.DebugError("illustPage.load", $"failed to load illust page data, id: {IllustItem.Id}");
+ return;
+ }
+ var items = Illusts;
+ if (pages.body.Length > items.Length)
+ {
+ App.DebugPrint($"local page count ({items.Length}) is not equals the remote one ({pages.body.Length})");
+ var tmp = new IllustDetailItem[pages.body.Length];
+ items.CopyTo(items, 0);
+ for (var i = items.Length; i < tmp.Length; i++)
+ {
+ tmp[i] = new IllustDetailItem();
+ }
+ items = tmp;
+ }
+ for (var i = 0; i < items.Length; i++)
+ {
+ var item = items[i];
+ var p = pages.body[i];
+ item.PreviewUrl = p.urls.regular;
+ item.OriginalUrl = p.urls.original;
+ }
+
+ DoLoadImage(0);
+ if (items.Length > 1)
+ {
+ DoLoadImage(1);
+ }
+ }
+
+ private void DoLoadImage(int index)
+ {
+ var items = Illusts;
+ if (index < 0 || index >= items.Length)
+ {
+ App.DebugPrint($"invalid index: {index}");
+ return;
+ }
+
+ var item = items[index];
+ if (item.Loading || (index > 0 && item.Image != null))
+ {
+ App.DebugPrint($"skipped, loading or already loaded, index: {index}, loading: {item.Loading}");
+ return;
+ }
+ item.Loading = true;
+ var image = Stores.LoadPreviewImage(item.PreviewUrl);
+ if (image != null)
+ {
+ item.Image = image;
+ }
+ item.Loading = false;
+ }
+
+ private async void Download_Clicked(object sender, EventArgs e)
+ {
+ var status = await Permissions.CheckStatusAsync();
+ if (status != PermissionStatus.Granted)
+ {
+ status = await Permissions.RequestAsync();
+ if (status != PermissionStatus.Granted)
+ {
+ App.DebugPrint("access denied to gallery.");
+ return;
+ }
+ }
+
+ var item = Illusts[CurrentPage];
+ if (item.Downloading)
+ {
+ return;
+ }
+ item.Downloading = true;
+ _ = Task.Run(() => DoLoadOriginalImage(item));
+ }
+
+ private void DoLoadOriginalImage(IllustDetailItem item)
+ {
+ var image = Stores.LoadIllustImage(item.OriginalUrl);
+ if (image != null)
+ {
+ Device.BeginInvokeOnMainThread(async () =>
+ {
+ var service = DependencyService.Get();
+ var result = await service.SaveImageToGalleryAsync(image);
+
+ string message = result ?? ResourceHelper.SaveSuccess;
+ await DisplayAlert(ResourceHelper.Title, message, ResourceHelper.Ok);
+ });
+ }
+ item.Downloading = false;
}
}
@@ -126,6 +250,8 @@ namespace Pixiview
nameof(Image), typeof(ImageSource), typeof(IllustDetailItem));
public static readonly BindableProperty LoadingProperty = BindableProperty.Create(
nameof(Loading), typeof(bool), typeof(IllustDetailItem));
+ public static readonly BindableProperty DownloadingProperty = BindableProperty.Create(
+ nameof(Downloading), typeof(bool), typeof(IllustDetailItem));
public ImageSource Image
{
@@ -137,5 +263,12 @@ namespace Pixiview
get => (bool)GetValue(LoadingProperty);
set => SetValue(LoadingProperty, value);
}
+ public bool Downloading
+ {
+ get => (bool)GetValue(DownloadingProperty);
+ set => SetValue(DownloadingProperty, value);
+ }
+ public string PreviewUrl { get; set; }
+ public string OriginalUrl { get; set; }
}
}