favorite & view page & etc.

This commit is contained in:
Tsanie 2021-08-12 16:27:42 +08:00
parent 3152f47db5
commit fa0b033f2a
33 changed files with 728 additions and 94 deletions

View File

@ -107,9 +107,11 @@ namespace Gallery
GallerySources = new List<IGallerySource>() GallerySources = new List<IGallerySource>()
{ {
new Sources.FavoriteGallerySource(),
new Sources.Yandere.GallerySource(), // https://yande.re new Sources.Yandere.GallerySource(), // https://yande.re
new Sources.Danbooru.GallerySource(), // https://danbooru.donmai.us new Sources.Danbooru.GallerySource(), // https://danbooru.donmai.us
new Sources.Gelbooru.GallerySource() // https://gelbooru.com new Sources.Gelbooru.GallerySource(), // https://gelbooru.com
}; };
MainPage = new AppShell(); MainPage = new AppShell();

View File

@ -1,4 +1,5 @@
using Gallery.Resources.UI; using Gallery.Resources;
using Gallery.Resources.UI;
using Gallery.Util; using Gallery.Util;
using Gallery.Util.Interface; using Gallery.Util.Interface;
using Gallery.Views; using Gallery.Views;
@ -36,7 +37,7 @@ namespace Gallery
} }
var tab = new Tab var tab = new Tab
{ {
Title = source.Name, Title = Helper.GetResource(source.Name),
Route = source.Route, Route = source.Route,
Items = Items =
{ {

View File

@ -45,6 +45,8 @@
<Compile Include="$(MSBuildThisFileDirectory)Sources\Yandere\YandereItem.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Sources\Yandere\YandereItem.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Sources\Danbooru\GallerySource.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Sources\Danbooru\GallerySource.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Sources\Gelbooru\GallerySource.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Sources\Gelbooru\GallerySource.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Util\Consts.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Sources\FavoriteGallerySource.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="$(MSBuildThisFileDirectory)Services\" /> <Folder Include="$(MSBuildThisFileDirectory)Services\" />

View File

@ -11,4 +11,5 @@
<Detail>详细</Detail> <Detail>详细</Detail>
<ProxyHost>代理主机</ProxyHost> <ProxyHost>代理主机</ProxyHost>
<ProxyPort>代理端口</ProxyPort> <ProxyPort>代理端口</ProxyPort>
<Favorite>收藏夹</Favorite>
</root> </root>

View File

@ -32,6 +32,7 @@ namespace Gallery.Resources.Theme
Add(TextColor, Color.White); Add(TextColor, Color.White);
Add(SubTextColor, Color.LightGray); Add(SubTextColor, Color.LightGray);
Add(CardBackgroundColor, Color.FromRgb(0x33, 0x33, 0x33)); Add(CardBackgroundColor, Color.FromRgb(0x33, 0x33, 0x33));
Add(MaskColor, Color.FromRgba(0, 0, 0, 0x64));
Add(NavigationColor, Color.FromRgb(0x11, 0x11, 0x11)); Add(NavigationColor, Color.FromRgb(0x11, 0x11, 0x11));
Add(NavigationSelectedColor, Color.FromRgb(0x22, 0x22, 0x22)); Add(NavigationSelectedColor, Color.FromRgb(0x22, 0x22, 0x22));
Add(OptionBackColor, Color.Black); Add(OptionBackColor, Color.Black);

View File

@ -32,6 +32,7 @@ namespace Gallery.Resources.Theme
Add(TextColor, Color.Black); Add(TextColor, Color.Black);
Add(SubTextColor, Color.DimGray); Add(SubTextColor, Color.DimGray);
Add(CardBackgroundColor, Color.FromRgb(0xf3, 0xf3, 0xf3)); Add(CardBackgroundColor, Color.FromRgb(0xf3, 0xf3, 0xf3));
Add(MaskColor, Color.FromRgba(0, 0, 0, 0x64));
Add(NavigationColor, Color.FromRgb(0xf0, 0xf0, 0xf0)); Add(NavigationColor, Color.FromRgb(0xf0, 0xf0, 0xf0));
Add(NavigationSelectedColor, Color.LightGray); Add(NavigationSelectedColor, Color.LightGray);
Add(OptionBackColor, Color.FromRgb(0xf0, 0xf0, 0xf0)); Add(OptionBackColor, Color.FromRgb(0xf0, 0xf0, 0xf0));

View File

@ -11,6 +11,7 @@ namespace Gallery.Resources.Theme
public const string TextColor = nameof(TextColor); public const string TextColor = nameof(TextColor);
public const string SubTextColor = nameof(SubTextColor); public const string SubTextColor = nameof(SubTextColor);
public const string CardBackgroundColor = nameof(CardBackgroundColor); public const string CardBackgroundColor = nameof(CardBackgroundColor);
public const string MaskColor = nameof(MaskColor);
public const string NavigationColor = nameof(NavigationColor); public const string NavigationColor = nameof(NavigationColor);
public const string NavigationSelectedColor = nameof(NavigationSelectedColor); public const string NavigationSelectedColor = nameof(NavigationSelectedColor);
public const string OptionBackColor = nameof(OptionBackColor); public const string OptionBackColor = nameof(OptionBackColor);
@ -24,6 +25,9 @@ namespace Gallery.Resources.Theme
public const string IconClose = nameof(IconClose); public const string IconClose = nameof(IconClose);
public const string FontIconOption = nameof(FontIconOption); public const string FontIconOption = nameof(FontIconOption);
public const string FontIconRefresh = nameof(FontIconRefresh); public const string FontIconRefresh = nameof(FontIconRefresh);
public const string FontIconLove = nameof(FontIconLove);
public const string FontIconNotLove = nameof(FontIconNotLove);
public const string FontIconShare = nameof(FontIconShare);
protected void InitResources() protected void InitResources()
{ {
@ -34,6 +38,9 @@ namespace Gallery.Resources.Theme
Add(FontIconOption, GetFontIcon(Definition.IconOption, Definition.IconSolidFamily)); Add(FontIconOption, GetFontIcon(Definition.IconOption, Definition.IconSolidFamily));
Add(FontIconRefresh, GetFontIcon(Definition.IconRefresh, Definition.IconSolidFamily)); Add(FontIconRefresh, GetFontIcon(Definition.IconRefresh, Definition.IconSolidFamily));
Add(FontIconLove, GetFontIcon(Definition.IconLove, Definition.IconSolidFamily, color: Definition.ColorRedBackground));
Add(FontIconNotLove, GetFontIcon(Definition.IconLove, Definition.IconRegularFamily, color: Definition.ColorRedBackground));
Add(FontIconShare, GetFontIcon(Definition.IconShare, Definition.IconSolidFamily));
Add(IconClose, Definition.IconClose); Add(IconClose, Definition.IconClose);
} }

View File

@ -36,6 +36,7 @@ namespace Gallery.Resources.UI
public const string IconLove = "\uf004"; public const string IconLove = "\uf004";
public const string IconCircleLove = "\uf4c7"; public const string IconCircleLove = "\uf4c7";
public const string IconClose = "\uf057"; public const string IconClose = "\uf057";
public const string IconShare = "\uf1e0";
static Definition() static Definition()
{ {

View File

@ -10,7 +10,7 @@ using Xamarin.Forms;
namespace Gallery.Resources.UI namespace Gallery.Resources.UI
{ {
public abstract class GalleryCollectionPage : GalleryScrollableCollectionPage<GalleryItem[]> public abstract class GalleryCollectionPage : GalleryScrollableCollectionPage<IEnumerable<GalleryItem>>
{ {
public GalleryCollectionPage(IGallerySource source) : base(source) { } public GalleryCollectionPage(IGallerySource source) : base(source) { }
} }
@ -93,11 +93,11 @@ namespace Gallery.Resources.UI
} }
} }
protected readonly Command<GalleryItem> commandGalleryItemTapped;
protected double topOffset; protected double topOffset;
protected string lastError; protected string lastError;
private readonly object sync = new(); private readonly object sync = new();
private readonly Command<GalleryItem> commandGalleryItemTapped;
private readonly Stack<ParallelTask> tasks = new(); private readonly Stack<ParallelTask> tasks = new();
private T galleryData; private T galleryData;
@ -107,17 +107,8 @@ namespace Gallery.Resources.UI
commandGalleryItemTapped = new Command<GalleryItem>(OnGalleryItemTapped); commandGalleryItemTapped = new Command<GalleryItem>(OnGalleryItemTapped);
} }
private void OnGalleryItemTapped(GalleryItem item) protected virtual void OnGalleryItemTapped(GalleryItem item)
{ {
if (item == null)
{
return;
}
//Start(async () =>
//{
// var page = new GalleryItemPage(item);
// await Navigation.PushAsync(page);
//});
} }
public override void OnUnload() public override void OnUnload()
@ -145,6 +136,14 @@ namespace Gallery.Resources.UI
{ {
StartLoading(); StartLoading();
} }
else if (GalleryCollection != null)
{
var favorites = Store.FavoriteList;
foreach (var item in GalleryCollection)
{
item.IsFavorite = favorites.Any(i => i.SourceEquals(item));
}
}
} }
#if __IOS__ #if __IOS__
@ -217,6 +216,17 @@ namespace Gallery.Resources.UI
{ {
if (force || Expired) if (force || Expired)
{ {
if (!isBottom)
{
lock (sync)
{
// destory all the tasks
while (tasks.TryPop(out var t))
{
t?.Dispose();
}
}
}
var indicator = LoadingIndicator; var indicator = LoadingIndicator;
if (indicator == null || isBottom) if (indicator == null || isBottom)
{ {
@ -257,6 +267,7 @@ namespace Gallery.Resources.UI
_ = DoloadGallerySource(force, isBottom); _ = DoloadGallerySource(force, isBottom);
}); });
} }
BeforeLoading();
} }
} }
@ -276,6 +287,7 @@ namespace Gallery.Resources.UI
#endif #endif
{ {
Gallery = collection; Gallery = collection;
AfterLoaded();
return false; return false;
}); });
} }
@ -301,12 +313,16 @@ namespace Gallery.Resources.UI
#endif #endif
{ {
Gallery = collection; Gallery = collection;
AfterLoaded();
return false; return false;
}); });
}); });
} }
} }
protected virtual void BeforeLoading() { }
protected virtual void AfterLoaded() { }
protected async Task ScrollToTopAsync(ScrollView scrollView) protected async Task ScrollToTopAsync(ScrollView scrollView)
{ {
if (scrollView.ScrollY > -topOffset) if (scrollView.ScrollY > -topOffset)
@ -425,16 +441,18 @@ namespace Gallery.Resources.UI
var data = DoGetGalleryList(galleryData, out int tag).Where(i => i != null); var data = DoGetGalleryList(galleryData, out int tag).Where(i => i != null);
var collection = new GalleryCollection(data); var collection = new GalleryCollection(data);
var favorites = Store.FavoriteList;
foreach (var item in collection) foreach (var item in collection)
{ {
if (item.PreviewImage == null) if (item.PreviewImage == null)
{ {
var image = await Store.LoadPreviewImage(item.PreviewUrl, false); var image = await Store.LoadPreviewImage(item, false);
if (image != null) if (image != null)
{ {
item.PreviewImage = image; item.PreviewImage = image;
} }
} }
item.IsFavorite = favorites.Any(i => i.SourceEquals(item));
} }
DoGalleryLoaded(collection, bottom); DoGalleryLoaded(collection, bottom);
@ -473,7 +491,7 @@ namespace Gallery.Resources.UI
if (model.StartsWith("iPhone") || model.StartsWith("iPad")) if (model.StartsWith("iPhone") || model.StartsWith("iPad"))
{ {
#endif #endif
var image = Store.LoadPreviewImage(item.PreviewUrl, true, force: true).Result; var image = Store.LoadPreviewImage(item, true, force: true).Result;
if (image != null) if (image != null)
{ {
item.PreviewImage = image; item.PreviewImage = image;
@ -539,7 +557,7 @@ namespace Gallery.Resources.UI
{ {
lastRefreshY = double.MinValue; lastRefreshY = double.MinValue;
} }
base.StartLoading(force, isBottom); base.StartLoading(force: force, isBottom: isBottom);
} }
protected override GalleryCollection FilterGalleryCollection(GalleryCollection collection, bool bottom) protected override GalleryCollection FilterGalleryCollection(GalleryCollection collection, bool bottom)
@ -578,7 +596,7 @@ namespace Gallery.Resources.UI
#if DEBUG #if DEBUG
Log.Print("start to load next page"); Log.Print("start to load next page");
#endif #endif
StartLoading(true, true); StartLoading(force: true, isBottom: true);
} }
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Gallery.Util; using Gallery.Util;
@ -8,14 +9,14 @@ using Xamarin.Forms;
namespace Gallery.Sources.Danbooru namespace Gallery.Sources.Danbooru
{ {
public class GallerySource : IGallerySource public class GallerySource : GallerySourceBase
{ {
public string Name => "Danbooru"; public override string Name => "Danbooru";
public string Route => "danbooru"; public override string Route => "danbooru";
public string FlyoutIconKey => "Danbooru"; public override string FlyoutIconKey => "Danbooru";
public string HomePage => "https://danbooru.donmai.us"; public override string HomePage => "https://danbooru.donmai.us";
public async Task<GalleryItem[]> GetRecentItemsAsync(int page) public override async Task<IEnumerable<GalleryItem>> GetRecentItemsAsync(int page)
{ {
var url = $"https://danbooru.donmai.us/posts?page={page}"; var url = $"https://danbooru.donmai.us/posts?page={page}";
var (result, error) = await NetHelper.RequestObject(url, @return: content => ResolveGalleryItems(content)); var (result, error) = await NetHelper.RequestObject(url, @return: content => ResolveGalleryItems(content));
@ -47,7 +48,8 @@ namespace Gallery.Sources.Danbooru
Width = int.Parse(g[3].Value), Width = int.Parse(g[3].Value),
Height = int.Parse(g[4].Value), Height = int.Parse(g[4].Value),
UserId = g[6].Value, UserId = g[6].Value,
Source = g[7].Value, Source = Route,
SourceUrl = g[7].Value,
RawUrl = g[8].Value, RawUrl = g[8].Value,
PreviewUrl = g[9].Value PreviewUrl = g[9].Value
}; };
@ -55,12 +57,12 @@ namespace Gallery.Sources.Danbooru
return items; return items;
} }
public void SetCookie() public override void SetCookie()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void InitDynamicResources(string family, ResourceDictionary light, ResourceDictionary dark) public override void InitDynamicResources(string family, ResourceDictionary light, ResourceDictionary dark)
{ {
var icon = new FontImageSource var icon = new FontImageSource
{ {

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Gallery.Util;
using Gallery.Util.Interface;
using Gallery.Util.Model;
using Xamarin.Forms;
namespace Gallery.Sources
{
public class FavoriteGallerySource : GallerySourceBase
{
public override string Name => "Favorite";
public override string Route => Routes.Favorite;
public override string FlyoutIconKey => "Favorite";
public override string HomePage => null;
public override bool IsScrollable => false;
public override Task<IEnumerable<GalleryItem>> GetRecentItemsAsync(int page)
{
return Task.FromResult((IEnumerable<GalleryItem>)Store.FavoriteList);
}
public override void SetCookie()
{
throw new NotImplementedException();
}
public override void InitDynamicResources(string family, ResourceDictionary light, ResourceDictionary dark)
{
var icon = new FontImageSource
{
FontFamily = family,
Glyph = "\uf02e",
Size = 18.0
};
light.Add(FlyoutIconKey, icon);
dark.Add(FlyoutIconKey, icon);
}
}
}

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Gallery.Util; using Gallery.Util;
@ -8,14 +9,14 @@ using Xamarin.Forms;
namespace Gallery.Sources.Gelbooru namespace Gallery.Sources.Gelbooru
{ {
public class GallerySource : IGallerySource public class GallerySource : GallerySourceBase
{ {
public string Name => "Gelbooru"; public override string Name => "Gelbooru";
public string Route => "gelbooru"; public override string Route => "gelbooru";
public string FlyoutIconKey => "Gelbooru"; public override string FlyoutIconKey => "Gelbooru";
public string HomePage => "https://gelbooru.com"; public override string HomePage => "https://gelbooru.com";
public async Task<GalleryItem[]> GetRecentItemsAsync(int page) public override async Task<IEnumerable<GalleryItem>> GetRecentItemsAsync(int page)
{ {
var offset = (page - 1) * 42; var offset = (page - 1) * 42;
var url = $"https://gelbooru.com/index.php?page=post&s=list&tags=all&pid={offset}"; var url = $"https://gelbooru.com/index.php?page=post&s=list&tags=all&pid={offset}";
@ -42,21 +43,54 @@ namespace Gallery.Sources.Gelbooru
var g = matches[i].Groups; var g = matches[i].Groups;
items[i] = new GalleryItem(int.Parse(g[2].Value)) items[i] = new GalleryItem(int.Parse(g[2].Value))
{ {
RawUrl = g[3].Value, RawUrl = g[3].Value.Replace("&amp;", "&"),
PreviewUrl = g[5].Value, PreviewUrl = g[5].Value,
Tags = g[6].Value.Split(' '), Tags = g[6].Value.Split(' '),
Source = Route,
IsRawPage = true IsRawPage = true
}; };
} }
return items; return items;
} }
public void SetCookie() public override async Task<string> ResolveImageUrl(GalleryItem item)
{
var (result, error) = await NetHelper.RequestObject(item.RawUrl, @return: content =>
{
var regex = new Regex(
@"<section class=""image-container.+?" + // data-id=""(\d+?)"".+?data-tags=""([^""]+?)"".+?
@"data-width=""(\d+?)"" data-height=""(\d+?)"".+?data-uploader-id=""([^""]*?)"" " +
@"data-normalized-source=""([^""]+?)""(.|\n)*?<img .*?src=""([^""]+?)""",
RegexOptions.Multiline);
var m = regex.Match(content);
if (m.Success)
{
var g = m.Groups;
item.Width = int.Parse(g[1].Value);
item.Height = int.Parse(g[2].Value);
item.UserId = g[3].Value;
item.SourceUrl = g[4].Value;
item.RawUrl = g[6].Value;
return item.RawUrl;
}
return null;
});
if (result == null || !string.IsNullOrEmpty(error))
{
Log.Error("gelbooru.imageUrl.resolve", $"failed to load data, error: {error}");
return null;
}
return result;
}
public override void SetCookie()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void InitDynamicResources(string family, ResourceDictionary light, ResourceDictionary dark) public override void InitDynamicResources(string family, ResourceDictionary light, ResourceDictionary dark)
{ {
var icon = new FontImageSource var icon = new FontImageSource
{ {

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Gallery.Util; using Gallery.Util;
@ -8,14 +9,14 @@ using Xamarin.Forms;
namespace Gallery.Sources.Yandere namespace Gallery.Sources.Yandere
{ {
public class GallerySource : IGallerySource public class GallerySource : GallerySourceBase
{ {
public string Name => "Yande.re"; public override string Name => "Yande.re";
public string Route => "yandere"; public override string Route => "yandere";
public string FlyoutIconKey => "Yandere"; public override string FlyoutIconKey => "Yandere";
public string HomePage => "https://yande.re"; public override string HomePage => "https://yande.re";
public async Task<GalleryItem[]> GetRecentItemsAsync(int page) public override async Task<IEnumerable<GalleryItem>> GetRecentItemsAsync(int page)
{ {
var url = $"https://yande.re/post?page={page}"; var url = $"https://yande.re/post?page={page}";
var (result, error) = await NetHelper.RequestObject<YandereItem[]>(url, contentHandler: ContentHandler); var (result, error) = await NetHelper.RequestObject<YandereItem[]>(url, contentHandler: ContentHandler);
@ -37,7 +38,8 @@ namespace Gallery.Sources.Yandere
UpdatedTime = y.updated_at.ToLocalTime(), UpdatedTime = y.updated_at.ToLocalTime(),
UserId = y.creator_id.ToString(), UserId = y.creator_id.ToString(),
UserName = y.author, UserName = y.author,
Source = y.source, Source = Route,
SourceUrl = y.source,
PreviewUrl = y.preview_url, PreviewUrl = y.preview_url,
RawUrl = y.file_url, RawUrl = y.file_url,
Width = y.width, Width = y.width,
@ -60,12 +62,12 @@ namespace Gallery.Sources.Yandere
return $"[{string.Join(',', array)}]"; return $"[{string.Join(',', array)}]";
} }
public void SetCookie() public override void SetCookie()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void InitDynamicResources(string family, ResourceDictionary light, ResourceDictionary dark) public override void InitDynamicResources(string family, ResourceDictionary light, ResourceDictionary dark)
{ {
var icon = new FontImageSource var icon = new FontImageSource
{ {

View File

@ -0,0 +1,30 @@
using System;
using System.Net;
namespace Gallery.Util
{
public static class Config
{
public static readonly TimeSpan Timeout = TimeSpan.FromSeconds(30);
public const string DownloadThreadsKey = "download_threads";
public const string IsProxiedKey = "is_proxied";
public const string ProxyHostKey = "proxy_host";
public const string ProxyPortKey = "proxy_port";
public const int MaxThreads = 4;
public const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36";
public const string AcceptLanguage = "zh-cn";
public const string AcceptImage = "image/png,image/*,*/*;q=0.8";
public static int DownloadThreads;
public static WebProxy Proxy;
}
public static class Routes
{
public const string Gallery = "gallery";
public const string Favorite = "favorite";
public const string Option = "option";
}
}

View File

@ -1,4 +1,5 @@
using System; using System;
using Gallery.Util.Model;
namespace Gallery.Util namespace Gallery.Util
{ {
@ -63,5 +64,14 @@ namespace Gallery.Util
var ticks = datetime.Ticks; var ticks = datetime.Ticks;
return (ticks - 621355968000000000L) / 10000000; return (ticks - 621355968000000000L) / 10000000;
} }
public static bool SourceEquals(this GalleryItem a, GalleryItem b)
{
if (a == null || b == null)
{
return false;
}
return a.Id == b.Id && a.Source == b.Source;
}
} }
} }

View File

@ -1,4 +1,5 @@
using System.Threading.Tasks; using System.Collections.Generic;
using System.Threading.Tasks;
using Gallery.Util.Model; using Gallery.Util.Model;
using Xamarin.Forms; using Xamarin.Forms;
@ -7,17 +8,28 @@ namespace Gallery.Util.Interface
public interface IGallerySource public interface IGallerySource
{ {
string Name { get; } string Name { get; }
string Route { get; } string Route { get; }
string FlyoutIconKey { get; } string FlyoutIconKey { get; }
string HomePage { get; } string HomePage { get; }
bool IsScrollable { get; }
void SetCookie(); void SetCookie();
void InitDynamicResources(string family, ResourceDictionary light, ResourceDictionary dark); void InitDynamicResources(string family, ResourceDictionary light, ResourceDictionary dark);
Task<IEnumerable<GalleryItem>> GetRecentItemsAsync(int page);
Task<string> ResolveImageUrl(GalleryItem item);
}
Task<GalleryItem[]> GetRecentItemsAsync(int page); public abstract class GallerySourceBase : IGallerySource
{
public abstract string Name { get; }
public abstract string Route { get; }
public abstract string FlyoutIconKey { get; }
public abstract string HomePage { get; }
public virtual bool IsScrollable => true;
public abstract void SetCookie();
public abstract void InitDynamicResources(string family, ResourceDictionary light, ResourceDictionary dark);
public abstract Task<IEnumerable<GalleryItem>> GetRecentItemsAsync(int page);
public virtual Task<string> ResolveImageUrl(GalleryItem item) => Task.FromResult(item.RawUrl);
} }
} }

View File

@ -80,6 +80,8 @@ namespace Gallery.Util.Model
[JsonProperty] [JsonProperty]
public string Source { get; set; } public string Source { get; set; }
[JsonProperty] [JsonProperty]
public string SourceUrl { get; set; }
[JsonProperty]
public string PreviewUrl { get; set; } public string PreviewUrl { get; set; }
[JsonProperty] [JsonProperty]
public string RawUrl { get; set; } public string RawUrl { get; set; }
@ -124,7 +126,7 @@ namespace Gallery.Util.Model
public override string ToString() public override string ToString()
{ {
var source = string.IsNullOrEmpty(Source) ? RawUrl : Source; var source = string.IsNullOrEmpty(SourceUrl) ? RawUrl : SourceUrl;
return $"{Id}, {source}"; return $"{Id}, {source}";
} }
} }

View File

@ -125,7 +125,7 @@ namespace Gallery.Util
} }
} }
public static async Task<string> DownloadImageAsync(string url, string id, string working, string folder) public static async Task<string> DownloadImageAsync(string url, string id, string working, string folder, Action<(int loc, int size)> action = null)
{ {
try try
{ {
@ -149,7 +149,7 @@ namespace Gallery.Util
{ {
Timeout = Config.Timeout Timeout = Config.Timeout
}; };
long size; int size;
DateTimeOffset lastModified; DateTimeOffset lastModified;
using (var request = new HttpRequestMessage(HttpMethod.Head, url)) using (var request = new HttpRequestMessage(HttpMethod.Head, url))
{ {
@ -158,7 +158,7 @@ namespace Gallery.Util
headers.Add("Accept-Language", Config.AcceptLanguage); headers.Add("Accept-Language", Config.AcceptLanguage);
headers.Add("User-Agent", Config.UserAgent); headers.Add("User-Agent", Config.UserAgent);
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
size = response.Content.Headers.ContentLength.Value; size = (int)response.Content.Headers.ContentLength.Value;
lastModified = response.Content.Headers.LastModified.Value; lastModified = response.Content.Headers.LastModified.Value;
#if DEBUG #if DEBUG
Log.Print($"content length: {size:n0} bytes, last modified: {lastModified}"); Log.Print($"content length: {size:n0} bytes, last modified: {lastModified}");
@ -167,10 +167,10 @@ namespace Gallery.Util
// segments // segments
const int SIZE = 150000; const int SIZE = 150000;
var list = new List<(long from, long to)>(); var list = new List<(int from, int to)>();
for (long i = 0; i < size; i += SIZE) for (var i = 0; i < size; i += SIZE)
{ {
long to; int to;
if (i + SIZE >= size) if (i + SIZE >= size)
{ {
to = size - 1; to = size - 1;
@ -198,11 +198,12 @@ namespace Gallery.Util
headers.Range = new RangeHeaderValue(from, to); headers.Range = new RangeHeaderValue(from, to);
headers.Add("User-Agent", Config.UserAgent); headers.Add("User-Agent", Config.UserAgent);
using var response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result; using var response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result;
using var ms = new MemoryStream(data, (int)from, (int)(to - from + 1)); using var ms = new MemoryStream(data, from, to - from + 1);
response.Content.CopyToAsync(ms).Wait(); response.Content.CopyToAsync(ms).Wait();
#if DEBUG #if DEBUG
Log.Print($"downloaded range: from({from:n0}) to ({to:n0})"); Log.Print($"downloaded range: from({from:n0}) to ({to:n0})");
#endif #endif
action?.Invoke((to, size));
} }
return true; return true;
}, },

View File

@ -1,7 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Gallery.Util.Model;
using Newtonsoft.Json;
using Xamarin.Essentials; using Xamarin.Essentials;
using Xamarin.Forms; using Xamarin.Forms;
@ -12,17 +15,120 @@ namespace Gallery.Util
public static readonly string PersonalFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal); public static readonly string PersonalFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
public static readonly string CacheFolder = FileSystem.CacheDirectory; public static readonly string CacheFolder = FileSystem.CacheDirectory;
private const string favoriteFile = "favorites.json";
private const string imageFolder = "img-original"; private const string imageFolder = "img-original";
private const string previewFolder = "img-preview"; private const string previewFolder = "img-preview";
public static async Task<ImageSource> LoadRawImage(string url) public static FavoriteList FavoriteList => GetFavoriteList();
public static string FavoriteListPath => Path.Combine(PersonalFolder, favoriteFile);
private static FavoriteList favoriteList;
public static FavoriteList GetFavoriteList(bool force = false)
{ {
return await LoadImageAsync(url, null, PersonalFolder, imageFolder, force: true); if (force || favoriteList == null)
{
var favorites = LoadFavoriteList();
if (favorites != null)
{
favoriteList = favorites;
}
else
{
favoriteList = new FavoriteList();
}
}
return favoriteList;
} }
public static async Task<ImageSource> LoadPreviewImage(string url, bool downloading, bool force = false) private static FavoriteList LoadFavoriteList(string file = null)
{ {
return await LoadImage(url, CacheFolder, previewFolder, downloading, force: force); if (file == null)
{
file = FavoriteListPath;
}
return LoadObject<FavoriteList>(file);
}
public static void SaveFavoriteList()
{
var file = FavoriteListPath;
WriteObject(file, FavoriteList);
}
private static T LoadObject<T>(string file)
{
string content = null;
if (File.Exists(file))
{
try
{
content = File.ReadAllText(file);
}
catch (Exception ex)
{
Log.Error("file.read", $"failed to read file: {file}, error: {ex.Message}");
}
}
else
{
return default;
}
try
{
return JsonConvert.DeserializeObject<T>(content);
}
catch (Exception ex)
{
Log.Error("file.deserialize", $"failed to parse JSON object, error: {ex.Message}");
return default;
}
}
private static void WriteObject(string file, object obj)
{
var folder = Path.GetDirectoryName(file);
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
string content;
try
{
content = JsonConvert.SerializeObject(obj, Formatting.None);
}
catch (Exception ex)
{
Log.Error("object.serialize", $"failed to serialize object, error: {ex.Message}");
return;
}
try
{
File.WriteAllText(file, content, Encoding.UTF8);
}
catch (Exception ex)
{
Log.Error("file.write", $"failed to write file: {file}, error: {ex.Message}");
}
}
public static async Task<ImageSource> LoadRawImage(GalleryItem item, bool force = false, Action<(int loc, int size)> action = null)
{
return await LoadImageAsync(item.RawUrl, null, PersonalFolder, Path.Combine(imageFolder, item.Source), force, action);
}
public static string GetRawImagePath(GalleryItem item)
{
var file = Path.Combine(PersonalFolder, imageFolder, item.Source, Path.GetFileName(item.RawUrl));
if (File.Exists(file))
{
return file;
}
return null;
}
public static async Task<ImageSource> LoadPreviewImage(GalleryItem item, bool downloading, bool force = false)
{
return await LoadImage(item.PreviewUrl, CacheFolder, Path.Combine(previewFolder, item.Source), downloading, force: force);
} }
private static async Task<ImageSource> LoadImage(string url, string working, string folder, bool downloading, bool force = false) private static async Task<ImageSource> LoadImage(string url, string working, string folder, bool downloading, bool force = false)
@ -48,7 +154,7 @@ namespace Gallery.Util
return image; return image;
} }
private static async Task<ImageSource> LoadImageAsync(string url, string id, string working, string folder, bool force = false) private static async Task<ImageSource> LoadImageAsync(string url, string id, string working, string folder, bool force, Action<(int loc, int size)> action)
{ {
var file = Path.Combine(working, folder, Path.GetFileName(url)); var file = Path.Combine(working, folder, Path.GetFileName(url));
ImageSource image; ImageSource image;
@ -62,7 +168,7 @@ namespace Gallery.Util
} }
if (image == null) if (image == null)
{ {
file = await NetHelper.DownloadImageAsync(url, id, working, folder); file = await NetHelper.DownloadImageAsync(url, id, working, folder, action);
if (file != null) if (file != null)
{ {
image = ImageSource.FromFile(file); image = ImageSource.FromFile(file);
@ -72,27 +178,33 @@ namespace Gallery.Util
} }
} }
public static class Config public class FavoriteList : List<GalleryItem>
{ {
public static readonly TimeSpan Timeout = TimeSpan.FromSeconds(30); public bool Changed { get; private set; }
public const string DownloadThreadsKey = "download_threads"; public FavoriteList() : base() { }
public const string IsProxiedKey = "is_proxied"; public FavoriteList(IEnumerable<GalleryItem> collection) : base(collection) { }
public const string ProxyHostKey = "proxy_host";
public const string ProxyPortKey = "proxy_port";
public const int MaxThreads = 2; public new void Insert(int index, GalleryItem item)
public const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"; {
public const string AcceptLanguage = "zh-cn"; base.Insert(index, item);
public const string AcceptImage = "image/png,image/*,*/*;q=0.8"; Changed = true;
}
public new void InsertRange(int index, IEnumerable<GalleryItem> collection)
{
base.InsertRange(index, collection);
Changed = true;
}
public new void RemoveAt(int index)
{
base.RemoveAt(index);
Changed = true;
}
public static int DownloadThreads; public FavoriteList Reload()
public static WebProxy Proxy; {
} Changed = false;
return this;
public static class Routes }
{
public const string Gallery = "gallery";
public const string Option = "option";
} }
} }

View File

@ -2,11 +2,44 @@
<ui:AdaptedPage <ui:AdaptedPage
xmlns="http://xamarin.com/schemas/2014/forms" xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
xmlns:ui="clr-namespace:Gallery.Resources.UI" xmlns:ui="clr-namespace:Gallery.Resources.UI"
x:Class="Gallery.Views.GalleryItemPage" x:Class="Gallery.Views.GalleryItemPage"
x:Name="galleryItemPage" x:Name="galleryItemPage"
ios:Page.UseSafeArea="False"
Shell.TabBarIsVisible="False"
BindingContext="{x:Reference galleryItemPage}"> BindingContext="{x:Reference galleryItemPage}">
<ContentPage.Content>
<ContentPage.ToolbarItems>
<ToolbarItem Command="{Binding ToolbarCommand}" CommandParameter="favorite"
Order="Primary" IconImageSource="{Binding FavoriteIcon}"/>
<ToolbarItem Command="{Binding ToolbarCommand}" CommandParameter="refresh"
Order="Primary" IconImageSource="{DynamicResource FontIconRefresh}"/>
<ToolbarItem Command="{Binding ToolbarCommand}" CommandParameter="share"
Order="Primary" IconImageSource="{DynamicResource FontIconShare}"/>
</ContentPage.ToolbarItems>
<ContentPage.Content>
<Grid Padding="{Binding TopMargin}">
<Image HorizontalOptions="Fill"
VerticalOptions="Fill"
Aspect="AspectFit"
Source="{Binding GalleryItem.PreviewImage}"/>
<Frame HasShadow="False" Margin="0" Padding="20" CornerRadius="8"
HorizontalOptions="Center" VerticalOptions="Center"
IsVisible="{Binding IsBusy}"
BackgroundColor="{DynamicResource MaskColor}">
<ActivityIndicator IsVisible="{Binding IsBusy}"
IsRunning="{Binding IsBusy}"
Color="{DynamicResource WindowColor}"/>
</Frame>
<ProgressBar x:Name="progress" IsVisible="{Binding ProgressVisible}"
VerticalOptions="Start">
<ProgressBar.Margin>
<OnPlatform x:TypeArguments="Thickness" Android="0, -6, 0, 0"/>
</ProgressBar.Margin>
</ProgressBar>
</Grid>
</ContentPage.Content> </ContentPage.Content>
</ui:AdaptedPage> </ui:AdaptedPage>

View File

@ -1,15 +1,213 @@
using System; using System.Linq;
using System.Collections.Generic; using System.Threading.Tasks;
using Gallery.Resources.Theme;
using Gallery.Resources.UI; using Gallery.Resources.UI;
using Gallery.Services;
using Gallery.Util;
using Gallery.Util.Model;
using Xamarin.Essentials;
using Xamarin.Forms; using Xamarin.Forms;
namespace Gallery.Views namespace Gallery.Views
{ {
[QueryProperty("GalleryId", "id")]
public partial class GalleryItemPage : AdaptedPage public partial class GalleryItemPage : AdaptedPage
{ {
public GalleryItemPage() public static readonly BindableProperty FavoriteIconProperty = BindableProperty.Create(nameof(FavoriteIcon), typeof(ImageSource), typeof(GalleryItemPage));
public static readonly BindableProperty GalleryItemProperty = BindableProperty.Create(nameof(GalleryItem), typeof(GalleryItem), typeof(GalleryItemPage));
public static readonly BindableProperty ProgressVisibleProperty = BindableProperty.Create(nameof(ProgressVisible), typeof(bool), typeof(GalleryItemPage));
public static readonly BindableProperty ToolbarCommandProperty = BindableProperty.Create(nameof(ToolbarCommand), typeof(Command<string>), typeof(GalleryItemPage));
public ImageSource FavoriteIcon
{ {
get => (ImageSource)GetValue(FavoriteIconProperty);
set => SetValue(FavoriteIconProperty, value);
}
public GalleryItem GalleryItem
{
get => (GalleryItem)GetValue(GalleryItemProperty);
}
public bool ProgressVisible
{
get => (bool)GetValue(ProgressVisibleProperty);
set => SetValue(ProgressVisibleProperty, value);
}
public Command<string> ToolbarCommand
{
get => (Command<string>)GetValue(ToolbarCommandProperty);
}
private readonly ImageSource fontIconLove;
private readonly ImageSource fontIconNotLove;
private bool favoriteChanged;
private bool isLoaded;
public GalleryItemPage(GalleryItem item)
{
SetValue(GalleryItemProperty, item);
SetValue(ToolbarCommandProperty, new Command<string>(HandleCommand, cmd => !IsBusy));
fontIconLove = (ImageSource)Application.Current.Resources[Theme.FontIconLove];
fontIconNotLove = (ImageSource)Application.Current.Resources[Theme.FontIconNotLove];
if (item != null)
{
InitInformation(item);
}
InitializeComponent(); InitializeComponent();
} }
protected override void OnAppearing()
{
base.OnAppearing();
Screen.SetHomeIndicatorAutoHidden(Shell.Current, true);
if (!isLoaded)
{
isLoaded = true;
Task.Run(() => LoadImage());
}
}
protected override void OnDisappearing()
{
Screen.SetHomeIndicatorAutoHidden(Shell.Current, false);
base.OnDisappearing();
if (favoriteChanged)
{
Store.SaveFavoriteList();
}
}
private void InitInformation(GalleryItem item)
{
Title = item.TagDescription;
FavoriteIcon = Store.FavoriteList.Any(i => i.SourceEquals(item)) ?
fontIconLove :
fontIconNotLove;
}
private async void LoadImage(bool force = false)
{
IsBusy = true;
MainThread.BeginInvokeOnMainThread(async () =>
{
ToolbarCommand.ChangeCanExecute();
if (force)
{
ProgressVisible = true;
progress.Progress = 0;
await progress.FadeTo(1, easing: Easing.CubicIn);
}
});
var item = GalleryItem;
if (item.IsRawPage)
{
var source = App.GallerySources.FirstOrDefault(s => s.Route == item.Source);
if (source != null)
{
var url = await source.ResolveImageUrl(item);
if (!string.IsNullOrEmpty(url))
{
item.IsRawPage = false;
}
}
}
var image = await Store.LoadRawImage(item, force: force, o =>
{
var val = o.loc / (double)o.size;
if (val > progress.Progress)
{
MainThread.BeginInvokeOnMainThread(async () =>
{
if (!ProgressVisible)
{
ProgressVisible = true;
}
progress.CancelAnimations();
await progress.ProgressTo(val, 250, Easing.CubicIn);
});
}
});
if (image != null)
{
item.PreviewImage = image;
}
IsBusy = false;
MainThread.BeginInvokeOnMainThread(async () =>
{
ToolbarCommand.ChangeCanExecute();
progress.CancelAnimations();
await progress.ProgressTo(1, 250, Easing.CubicIn);
await progress.FadeTo(0, easing: Easing.CubicIn);
ProgressVisible = false;
});
}
private void HandleCommand(string cmd)
{
switch (cmd)
{
case "favorite": Favorite_Clicked(); break;
case "refresh": Refresh_Clicked(); break;
case "share": Share_Clicked(); break;
}
}
private void Favorite_Clicked()
{
if (IsBusy)
{
return;
}
Start(() =>
{
var favorites = Store.FavoriteList;
var item = GalleryItem;
var index = favorites.FindIndex(i => i.SourceEquals(item));
if (index < 0)
{
item.IsFavorite = true;
favorites.Insert(0, item);
FavoriteIcon = fontIconLove;
}
else
{
favorites.RemoveAt(index);
item.IsFavorite = false;
FavoriteIcon = fontIconNotLove;
}
favoriteChanged = true;
});
}
private void Refresh_Clicked()
{
if (IsBusy)
{
return;
}
Start(async () => await Task.Run(() => LoadImage(true)));
}
private void Share_Clicked()
{
if (IsBusy)
{
return;
}
Start(async () =>
{
var item = GalleryItem;
var path = Store.GetRawImagePath(item);
await Share.RequestAsync(new ShareFileRequest
{
Title = item.TagDescription,
File = new ShareFile(path)
});
});
}
} }
} }

View File

@ -8,6 +8,11 @@
BackgroundColor="{DynamicResource WindowColor}" BackgroundColor="{DynamicResource WindowColor}"
BindingContext="{x:Reference yanderePage}" BindingContext="{x:Reference yanderePage}"
Title="{Binding Source.Name}"> Title="{Binding Source.Name}">
<ContentPage.ToolbarItems>
<ToolbarItem Order="Primary" Command="{Binding ToolbarCommand}"
IconImageSource="{DynamicResource FontIconRefresh}"/>
</ContentPage.ToolbarItems>
<ContentPage.Content> <ContentPage.Content>
<Grid> <Grid>
<ScrollView x:Name="scrollView" Scrolled="ScrollView_Scrolled" <ScrollView x:Name="scrollView" Scrolled="ScrollView_Scrolled"

View File

@ -4,17 +4,25 @@ using Gallery.Resources.UI;
using Gallery.Util; using Gallery.Util;
using Gallery.Util.Interface; using Gallery.Util.Interface;
using Gallery.Util.Model; using Gallery.Util.Model;
using Xamarin.Essentials;
using Xamarin.Forms; using Xamarin.Forms;
namespace Gallery.Views namespace Gallery.Views
{ {
public partial class GalleryPage : GalleryCollectionPage public partial class GalleryPage : GalleryCollectionPage
{ {
public static readonly BindableProperty ToolbarCommandProperty = BindableProperty.Create(nameof(ToolbarCommand), typeof(Command), typeof(GalleryPage));
public Command ToolbarCommand
{
get => (Command)GetValue(ToolbarCommandProperty);
}
private int currentPage; private int currentPage;
public GalleryPage(IGallerySource source) : base(source) public GalleryPage(IGallerySource source) : base(source)
{ {
Resources.Add("cardView", GetCardViewTemplate()); Resources.Add("cardView", GetCardViewTemplate());
SetValue(ToolbarCommandProperty, new Command(DoRefresh, () => !IsLoading && !IsBottomLoading));
InitializeComponent(); InitializeComponent();
currentPage = 1; currentPage = 1;
@ -28,21 +36,62 @@ namespace Gallery.Views
{ {
currentPage = 1; currentPage = 1;
} }
if (Store.FavoriteList.Changed && Source is Sources.FavoriteGallerySource)
{
Store.FavoriteList.Reload();
LastUpdated = default;
}
base.OnAppearing(); base.OnAppearing();
} }
protected override async Task<GalleryItem[]> DoloadGalleryData(bool force) protected override void BeforeLoading()
{
MainThread.BeginInvokeOnMainThread(ToolbarCommand.ChangeCanExecute);
}
protected override void AfterLoaded()
{
MainThread.BeginInvokeOnMainThread(ToolbarCommand.ChangeCanExecute);
}
protected override async Task<IEnumerable<GalleryItem>> DoloadGalleryData(bool force)
{ {
var result = await Source.GetRecentItemsAsync(currentPage); var result = await Source.GetRecentItemsAsync(currentPage);
return result; return result;
} }
protected override IEnumerable<GalleryItem> DoGetGalleryList(GalleryItem[] data, out int tag) protected override IEnumerable<GalleryItem> DoGetGalleryList(IEnumerable<GalleryItem> data, out int tag)
{ {
tag = currentPage; tag = currentPage;
return data; return data;
} }
protected override void OnGalleryItemTapped(GalleryItem item)
{
if (item == null)
{
return;
}
Start(async () =>
{
var page = new GalleryItemPage(item);
await Navigation.PushAsync(page);
});
}
private void DoRefresh()
{
if (IsLoading)
{
return;
}
Start(async () =>
{
await ScrollToTopAsync(scrollView);
StartLoading(force: true);
});
}
private void FlowLayout_MaxHeightChanged(object sender, HeightEventArgs e) private void FlowLayout_MaxHeightChanged(object sender, HeightEventArgs e)
{ {
SetOffset(e.ContentHeight - scrollView.Bounds.Height - SCROLL_OFFSET); SetOffset(e.ContentHeight - scrollView.Bounds.Height - SCROLL_OFFSET);
@ -50,6 +99,10 @@ namespace Gallery.Views
protected override bool CheckRefresh() protected override bool CheckRefresh()
{ {
if (!Source.IsScrollable)
{
return false;
}
currentPage++; currentPage++;
#if DEBUG #if DEBUG
Log.Print($"loading page: {currentPage}"); Log.Print($"loading page: {currentPage}");

View File

@ -0,0 +1,26 @@
{
"images": [
{
"idiom": "universal"
},
{
"filename": "bookmark-solid.png",
"scale": "1x",
"idiom": "universal"
},
{
"filename": "bookmark-solid@2x.png",
"scale": "2x",
"idiom": "universal"
},
{
"filename": "bookmark-solid@3x.png",
"scale": "3x",
"idiom": "universal"
}
],
"info": {
"version": 1,
"author": "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 B

View File

@ -0,0 +1,26 @@
{
"images": [
{
"idiom": "universal"
},
{
"filename": "bookmark-regular.png",
"scale": "1x",
"idiom": "universal"
},
{
"filename": "bookmark-regular@2x.png",
"scale": "2x",
"idiom": "universal"
},
{
"filename": "bookmark-regular@3x.png",
"scale": "3x",
"idiom": "universal"
}
],
"info": {
"version": 1,
"author": "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 B

View File

@ -135,6 +135,14 @@
<BundleResource Include="Resources\logo_light.png" /> <BundleResource Include="Resources\logo_light.png" />
<BundleResource Include="Resources\logo_light%402x.png" /> <BundleResource Include="Resources\logo_light%402x.png" />
<BundleResource Include="Resources\logo_light%403x.png" /> <BundleResource Include="Resources\logo_light%403x.png" />
<ImageAsset Include="Assets.xcassets\IconBookmark.imageset\Contents.json" />
<ImageAsset Include="Assets.xcassets\IconBookmarkRegular.imageset\Contents.json" />
<ImageAsset Include="Assets.xcassets\IconBookmark.imageset\bookmark-solid.png" />
<ImageAsset Include="Assets.xcassets\IconBookmark.imageset\bookmark-solid%402x.png" />
<ImageAsset Include="Assets.xcassets\IconBookmark.imageset\bookmark-solid%403x.png" />
<ImageAsset Include="Assets.xcassets\IconBookmarkRegular.imageset\bookmark-regular.png" />
<ImageAsset Include="Assets.xcassets\IconBookmarkRegular.imageset\bookmark-regular%402x.png" />
<ImageAsset Include="Assets.xcassets\IconBookmarkRegular.imageset\bookmark-regular%403x.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
@ -158,6 +166,8 @@
<Folder Include="Assets.xcassets\IconSourceRegular.imageset\" /> <Folder Include="Assets.xcassets\IconSourceRegular.imageset\" />
<Folder Include="Assets.xcassets\LaunchLogo.imageset\" /> <Folder Include="Assets.xcassets\LaunchLogo.imageset\" />
<Folder Include="Assets.xcassets\LauncherLogo.imageset\" /> <Folder Include="Assets.xcassets\LauncherLogo.imageset\" />
<Folder Include="Assets.xcassets\IconBookmark.imageset\" />
<Folder Include="Assets.xcassets\IconBookmarkRegular.imageset\" />
</ItemGroup> </ItemGroup>
<Import Project="..\Gallery.UI\Gallery.UI.projitems" Label="Shared" Condition="Exists('..\Gallery.UI\Gallery.UI.projitems')" /> <Import Project="..\Gallery.UI\Gallery.UI.projitems" Label="Shared" Condition="Exists('..\Gallery.UI\Gallery.UI.projitems')" />
<Import Project="..\Gallery.Share\Gallery.Share.projitems" Label="Shared" Condition="Exists('..\Gallery.Share\Gallery.Share.projitems')" /> <Import Project="..\Gallery.Share\Gallery.Share.projitems" Label="Shared" Condition="Exists('..\Gallery.Share\Gallery.Share.projitems')" />

View File

@ -151,15 +151,18 @@ namespace Gallery.iOS.Renderers.AppShellSection
public void UpdateLayout(UITabBarController controller) public void UpdateLayout(UITabBarController controller)
{ {
var tabBar = controller.TabBar; var tabBar = controller.TabBar;
if (tabBar != null && tabBar.Items != null && tabBar.Items.Length == 3) if (tabBar != null && tabBar.Items != null && tabBar.Items.Length >= 4)
{ {
var tabBarItem = tabBar.Items[0]; var tabBarItem = tabBar.Items[0];
tabBarItem.Image = UIImage.FromBundle("IconBookmarkRegular");
tabBarItem.SelectedImage = UIImage.FromBundle("IconBookmark");
tabBarItem = tabBar.Items[1];
tabBarItem.Image = UIImage.FromBundle("IconYandereRegular"); tabBarItem.Image = UIImage.FromBundle("IconYandereRegular");
tabBarItem.SelectedImage = UIImage.FromBundle("IconYandere"); tabBarItem.SelectedImage = UIImage.FromBundle("IconYandere");
tabBarItem = tabBar.Items[1]; tabBarItem = tabBar.Items[2];
tabBarItem.Image = UIImage.FromBundle("IconSourceRegular"); tabBarItem.Image = UIImage.FromBundle("IconSourceRegular");
tabBarItem.SelectedImage = UIImage.FromBundle("IconSource"); tabBarItem.SelectedImage = UIImage.FromBundle("IconSource");
tabBarItem = tabBar.Items[2]; tabBarItem = tabBar.Items[3];
tabBarItem.Image = UIImage.FromBundle("IconSourceRegular"); tabBarItem.Image = UIImage.FromBundle("IconSourceRegular");
tabBarItem.SelectedImage = UIImage.FromBundle("IconSource"); tabBarItem.SelectedImage = UIImage.FromBundle("IconSource");
} }