* Pixiview/UI/CircleUIs.cs:

* Pixiview.iOS/Pixiview.iOS.csproj:
* Pixiview.iOS/Renderers/RoundLabelRenderer.cs:
* Pixiview.iOS/Renderers/CircleImageRenderer.cs: custom round corner
  controls

* Pixiview/App.xaml:
* Pixiview/Utils/Converters.cs:
* Pixiview/GlobalSuppressions.cs:
* Pixiview/UI/StyleDefinition.cs:

* Pixiview/UI/AdaptedPage.cs:
* Pixiview.iOS/Renderers/AdaptedPageRenderer.cs: observe orientation

* Pixiview/MainPage.xaml:
* Pixiview/Utils/Stores.cs:
* Pixiview/MainPage.xaml.cs:
* Pixiview/Utils/IllustData.cs: data and UI adjust
This commit is contained in:
Tsanie Lily 2020-05-05 01:53:33 +08:00
parent e7fbee8d41
commit 66f0c1ba1b
14 changed files with 536 additions and 30 deletions

View File

@ -70,6 +70,8 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Renderers\AdaptedPageRenderer.cs" />
<Compile Include="Services\EnvironmentService.cs" />
<Compile Include="Renderers\CircleImageRenderer.cs" />
<Compile Include="Renderers\RoundLabelRenderer.cs" />
</ItemGroup>
<ItemGroup>
<InterfaceDefinition Include="Resources\LaunchScreen.storyboard" />

View File

@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using Foundation;
using Pixiview.iOS.Renderers;
using Pixiview.UI;
using Pixiview.Utils;
@ -11,6 +12,9 @@ namespace Pixiview.iOS.Renderers
{
public class AdaptedPageRenderer : PageRenderer
{
UIDeviceOrientation lastOrientation;
NSObject observer;
public override void ViewDidLoad()
{
base.ViewDidLoad();
@ -30,6 +34,20 @@ namespace Pixiview.iOS.Renderers
{
SetStatusBarStyle(style);
}
observer = UIDevice.Notifications.ObserveOrientationDidChange(ChangeOrientation);
ChangeOrientation(null, null);
}
public override void ViewWillDisappear(bool animated)
{
if (observer != null)
{
observer.Dispose();
observer = null;
}
base.ViewWillDisappear(animated);
}
private void SetStatusBarStyle(UIStatusBarStyle style)
@ -64,5 +82,23 @@ namespace Pixiview.iOS.Renderers
return UIStatusBarStyle.Default;
}
}
void ChangeOrientation(object sender, NSNotificationEventArgs e)
{
var current = UIDevice.CurrentDevice.Orientation;
if (current == UIDeviceOrientation.FaceUp || current == UIDeviceOrientation.FaceDown)
{
//current = UIDeviceOrientation.Portrait;
return;
}
if (lastOrientation != current)
{
lastOrientation = current;
if (Element is AdaptedPage page)
{
page.OnOrientationChanged((Orientation)lastOrientation);
}
}
}
}
}

View File

@ -0,0 +1,31 @@
using Pixiview.iOS.Renderers;
using Pixiview.UI;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(CircleImage), typeof(CircleImageRenderer))]
namespace Pixiview.iOS.Renderers
{
public class CircleImageRenderer : ImageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.Layer.MasksToBounds = true;
}
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
if (Control != null)
{
Control.Layer.CornerRadius = Control.Frame.Size.Width / 2;
}
}
}
}

View File

@ -0,0 +1,47 @@
using System.ComponentModel;
using Pixiview.iOS.Renderers;
using Pixiview.UI;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(RoundLabel), typeof(RoundLabelRenderer))]
namespace Pixiview.iOS.Renderers
{
public class RoundLabelRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control != null && Element is RoundLabel label)
{
int radius = label.CornerRadius;
if (radius > 0)
{
Control.Layer.CornerRadius = radius;
Control.BackgroundColor = label.BackgroundColor.ToUIColor();
Control.Layer.MasksToBounds = true;
}
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == RoundLabel.CornerRadiusProperty.PropertyName)
{
if (Control != null && Element is RoundLabel label)
{
int radius = label.CornerRadius;
if (radius > 0)
{
Control.Layer.CornerRadius = radius;
Control.BackgroundColor = label.BackgroundColor.ToUIColor();
Control.Layer.MasksToBounds = true;
}
}
}
}
}
}

View File

@ -8,6 +8,7 @@
<Application.Resources>
<Color x:Key="WindowColor">Black</Color>
<Color x:Key="TextColor">White</Color>
<Color x:Key="SubTextColor">LightGray</Color>
<Color x:Key="MainColor">#333333</Color>
<Color x:Key="MainTextColor">White</Color>
<Style x:Key="TitleLabel" TargetType="Label">

View File

@ -6,3 +6,4 @@
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0063:Use simple 'using' statement", Justification = "<Pending>")]
[assembly: SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "<Pending>")]

View File

@ -5,14 +5,63 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:u="clr-namespace:Pixiview.UI"
mc:Ignorable="d"
x:Class="Pixiview.MainPage">
x:Class="Pixiview.MainPage"
BackgroundColor="{DynamicResource WindowColor}"
OrientationChanged="Page_OrientationChanged">
<NavigationPage.TitleView>
<u:NavigationTitle Title="Startup Name Generator"
<u:NavigationTitle Title="Follow"
IsLeftButtonVisible="True"
LeftButtonClicked="NavigationTitle_LeftButtonClicked"
RightButtonClicked="NavigationTitle_RightButtonClicked"/>
</NavigationPage.TitleView>
<Grid>
</Grid>
<ScrollView HorizontalOptions="Fill" Padding="{Binding StatusBarPadding}">
<u:FlowLayout ItemsSource="{Binding Illusts}"
HorizontalOptions="Fill" Column="{Binding Columns}"
Margin="16" RowSpacing="16" ColumnSpacing="16">
<u:FlowLayout.ItemTemplate>
<DataTemplate>
<Frame HasShadow="False" Padding="0" Margin="0" CornerRadius="10"
BackgroundColor="{DynamicResource MainColor}">
<Grid HorizontalOptions="Fill" Margin="0, -5, 0, 0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Image BackgroundColor="LightGray"
Source="{Binding Image}"
HorizontalOptions="Fill"
Aspect="AspectFit"/>
<u:RoundLabel Text="R-18" BackgroundColor="#fd4363" Margin="6, 11, 0, 0"
Padding="6, 2" CornerRadius="4"
HorizontalOptions="Start" VerticalOptions="Start"
FontSize="Micro" TextColor="White"
IsVisible="{Binding IsRestrict}"/>
<u:RoundLabel Text="{Binding PageCountText}"
FontFamily="{DynamicResource IconSolidFontFamily}"
BackgroundColor="#50000000" Margin="0, 11, 6, 0"
Padding="6, 4" CornerRadius="6"
HorizontalOptions="End" VerticalOptions="Start"
FontSize="Micro" TextColor="White"
IsVisible="{Binding IsPageVisible}"/>
<Label Grid.Row="1" Text="{Binding Title}"
Padding="8, 2"
TextColor="{DynamicResource TextColor}"
LineBreakMode="TailTruncation"
FontSize="Small"/>
<StackLayout Grid.Row="2" Orientation="Horizontal" Padding="8, 0, 8, 8">
<u:CircleImage WidthRequest="30" HeightRequest="30" Aspect="AspectFill"
Source="{Binding ProfileImage}"/>
<Label Text="{Binding UserName}"
VerticalOptions="Center"
TextColor="{DynamicResource SubTextColor}"
LineBreakMode="TailTruncation"
FontSize="Micro"/>
</StackLayout>
</Grid>
</Frame>
</DataTemplate>
</u:FlowLayout.ItemTemplate>
</u:FlowLayout>
</ScrollView>
</u:AdaptedPage>

View File

@ -1,8 +1,12 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Pixiview.UI;
using Pixiview.Utils;
using Xamarin.Forms;
namespace Pixiview
{
@ -11,24 +15,115 @@ namespace Pixiview
[DesignTimeVisible(false)]
public partial class MainPage : AdaptedPage
{
public static readonly BindableProperty IllustsProperty = BindableProperty.Create(
nameof(Illusts), typeof(IllustCollection), typeof(MainPage));
public static readonly BindableProperty ColumnsProperty = BindableProperty.Create(
nameof(Columns), typeof(int), typeof(MainPage), 2);
public IllustCollection Illusts
{
get => (IllustCollection)GetValue(IllustsProperty);
set => SetValue(IllustsProperty, value);
}
public int Columns
{
get => (int)GetValue(ColumnsProperty);
set => SetValue(ColumnsProperty, value);
}
private readonly ParallelOptions parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = Configs.MaxThreads };
private IllustData illustData;
public MainPage()
{
InitializeComponent();
BindingContext = this;
}
public override void OnLoad()
{
App.DebugPrint($"folder: {Stores.PersonalFolder}");
Task.Run(async () =>
Task.Run(DoLoadIllusts);
}
async void DoLoadIllusts()
{
illustData = await Stores.LoadIllustData();
var data = illustData.body.page.follow.Select(i =>
{
illustData = await Stores.LoadIllustData();
App.DebugPrint(illustData.message);
var illust = illustData.body.thumbnails.illust.FirstOrDefault(l => l.illustId == i.ToString());
if (illust == null)
{
return null;
}
return new IllustItem
{
ImageUrl = illust.urls.x360 ?? illust.url,
Title = illust.illustTitle,
IsRestrict = illust.xRestrict == 1,
ProfileUrl = illust.profileImageUrl,
UserName = illust.userName,
Width = illust.width,
Height = illust.height,
PageCount = illust.pageCount
};
}).Where(i => i != null);
var collection = new IllustCollection(data);
Illusts = collection;
DoLoadImages(collection);
}
void DoLoadImages(IllustCollection collection)
{
Parallel.ForEach(collection, parallelOptions, illust =>
{
if (!collection.Running)
{
return;
}
if (illust.ImageUrl != null)
{
var url = Configs.GetThumbnailUrl(illust.ImageUrl);
var image = Stores.LoadThumbnailImage(url);
if (image != null)
{
illust.Image = image;
}
}
if (illust.ProfileUrl != null)
{
var userImage = Stores.LoadUserProfileImage(illust.ProfileUrl);
if (userImage != null)
{
illust.ProfileImage = userImage;
}
}
});
}
void Page_OrientationChanged(object sender, OrientationEventArgs e)
{
switch (e.CurrentOrientation)
{
case Orientation.Portrait:
Columns = 2;
break;
case Orientation.Unknown:
case Orientation.PortraitUpsideDown:
case Orientation.LandscapeLeft:
case Orientation.LandscapeRight:
default:
Columns = 4;
break;
}
}
void NavigationTitle_RightButtonClicked(object sender, EventArgs e)
{
}
@ -38,4 +133,57 @@ namespace Pixiview
DisplayAlert("title", "message", "Ok");
}
}
public class IllustCollection : ObservableCollection<IllustItem>
{
private static readonly object sync = new object();
public IllustCollection(IEnumerable<IllustItem> illusts) : base(illusts)
{
running = true;
}
private bool running;
public bool Running
{
get => running;
set
{
lock (sync)
{
running = value;
}
}
}
}
public class IllustItem : BindableObject
{
public static readonly BindableProperty ImageProperty = BindableProperty.Create(
nameof(Image), typeof(ImageSource), typeof(IllustItem));
public static readonly BindableProperty ProfileImageProperty = BindableProperty.Create(
nameof(ProfileImage), typeof(ImageSource), typeof(IllustItem));
public ImageSource Image
{
get => (ImageSource)GetValue(ImageProperty);
set => SetValue(ImageProperty, value);
}
public ImageSource ProfileImage
{
get => (ImageSource)GetValue(ProfileImageProperty);
set => SetValue(ProfileImageProperty, value);
}
public string ImageUrl { get; set; }
public string Title { get; set; }
public bool IsRestrict { get; set; }
public string ProfileUrl { get; set; }
public string UserName { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public int PageCount { get; set; }
public string PageCountText => $"{StyleDefinition.IconLayer} {PageCount}";
public bool IsPageVisible => PageCount > 1;
}
}

View File

@ -5,16 +5,56 @@ namespace Pixiview.UI
{
public class AdaptedPage : ContentPage
{
public event EventHandler Load;
static readonly Thickness LandscapeLeftPadding = new Thickness(34, 0, 0, 0);
static readonly Thickness LandscapeRightPadding = new Thickness(0, 0, 34, 0);
public AdaptedPage()
public static readonly BindableProperty StatusBarPaddingProperty = BindableProperty.Create(
nameof(StatusBarPadding), typeof(Thickness), typeof(AdaptedPage), default(Thickness));
public Thickness StatusBarPadding
{
SetDynamicResource(BackgroundColorProperty, App.WindowColor);
get => (Thickness)GetValue(StatusBarPaddingProperty);
set => SetValue(StatusBarPaddingProperty, value);
}
public event EventHandler Load;
public event EventHandler<OrientationEventArgs> OrientationChanged;
public virtual void OnLoad()
{
Load?.Invoke(this, EventArgs.Empty);
}
public virtual void OnOrientationChanged(Orientation orientation)
{
if (orientation == Orientation.LandscapeLeft || orientation == Orientation.PortraitUpsideDown)
{
StatusBarPadding = StyleDefinition.IsFullscreenDevice ? LandscapeLeftPadding : default;
}
else if (orientation == Orientation.LandscapeRight)
{
StatusBarPadding = StyleDefinition.IsFullscreenDevice ? LandscapeRightPadding : default;
}
else
{
StatusBarPadding = default;
}
OrientationChanged?.Invoke(this, new OrientationEventArgs { CurrentOrientation = orientation });
}
}
public class OrientationEventArgs : EventArgs
{
public Orientation CurrentOrientation { get; set; }
}
public enum Orientation
{
Unknown,
Portrait,
PortraitUpsideDown,
LandscapeLeft,
LandscapeRight
}
}

25
Pixiview/UI/CircleUIs.cs Normal file
View File

@ -0,0 +1,25 @@
using Xamarin.Forms;
namespace Pixiview.UI
{
public class CircleImage : Image { }
public class RoundLabel : Label
{
public static readonly BindableProperty CornerRadiusProperty = BindableProperty.Create(
nameof(CornerRadius), typeof(int), typeof(RoundLabel));
public static new readonly BindableProperty BackgroundColorProperty = BindableProperty.Create(
nameof(BackgroundColor), typeof(Color), typeof(RoundLabel), Color.Transparent);
public int CornerRadius
{
get => (int)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
public new Color BackgroundColor
{
get => (Color)GetValue(BackgroundColorProperty);
set => SetValue(BackgroundColorProperty, value);
}
}
}

View File

@ -1,13 +1,86 @@
using Xamarin.Forms;
using System;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Pixiview.UI
{
public static class StyleDefinition
{
public static readonly double FontSizeTitle = 18.0;
public static readonly double FontSizeTitleIcon = 24.0;
public const double FontSizeTitle = 18.0;
public const double FontSizeTitleIcon = 24.0;
public static readonly Thickness HorizonLeft10 = new Thickness(10, 0, 0, 0);
public static readonly Thickness HorizonRight10 = new Thickness(0, 0, 10, 0);
public static readonly Thickness LeftBottom10 = new Thickness(10, 0, 0, 10);
public const string IconLayer = "\uf302";
private static bool? _isFullscreenDevice;
public static bool IsFullscreenDevice
{
get
{
if (_isFullscreenDevice != null)
{
return _isFullscreenDevice.Value;
}
if (Device.RuntimePlatform == Device.iOS)
{
try
{
var model = DeviceInfo.Model;
if (model == "iPhone10,3")
{
// iPhone X
_isFullscreenDevice = true;
}
else if (model.StartsWith("iPhone"))
{
var vs = model.Substring(6).Split(',');
if (vs.Length == 2 && int.TryParse(vs[0], out int main) && int.TryParse(vs[1], out int sub))
{
// iPhone X/XS/XR or iPhone 11
_isFullscreenDevice = (main == 10 && sub >= 6) || (main > 10);
}
else
{
_isFullscreenDevice = false;
}
}
else if (model.StartsWith("iPad8,"))
{
// iPad 11-inch or 12.9-inch (3rd+)
_isFullscreenDevice = true;
}
#if DEBUG
else
{
// iPad or Simulator
var name = DeviceInfo.Name;
_isFullscreenDevice = name.StartsWith("iPhone X")
|| name.StartsWith("iPhone 11")
|| name.StartsWith("iPad Pro (11-inch)")
|| name.StartsWith("iPad Pro (12.9-inch) (3rd generation)")
|| name.StartsWith("iPad Pro (12.9-inch) (4th generation)");
}
#endif
}
catch (Exception ex)
{
App.DebugError("device.get", $"failed to get the device model. {ex.Message}");
}
}
else
{
// TODO:
_isFullscreenDevice = false;
}
if (_isFullscreenDevice == null)
{
_isFullscreenDevice = false;
}
return _isFullscreenDevice.Value;
}
}
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Globalization;
using Pixiview.UI;
using Xamarin.Forms;
namespace Pixiview.Utils
{
public class PageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return StyleDefinition.IconLayer + value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -72,11 +72,13 @@ namespace Pixiview.Utils
public string userName;
public int width;
public int height;
public int pageCount;
public string alt;
public IllustUrls urls;
public string seriesId;
public string seriesTitle;
public string profileImageUrl;
public int xRestrict;
public class IllustUrls
{

View File

@ -6,6 +6,7 @@ using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Xamarin.Forms;
namespace Pixiview.Utils
{
@ -14,6 +15,7 @@ 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 thumbFolder = "img-thumb";
private const string userFolder = "user-profile";
private const string illustFile = "illust.json";
@ -67,35 +69,46 @@ namespace Pixiview.Utils
}
}
public static string LoadIllustImage(string url)
public static ImageSource LoadIllustImage(string url)
{
return LoadImage(url, imageFolder);
}
public static string LoadUserProfileImage(string url)
public static ImageSource LoadThumbnailImage(string url)
{
return LoadImage(url, thumbFolder);
}
public static ImageSource LoadUserProfileImage(string url)
{
return LoadImage(url, userFolder);
}
public static string LoadImage(string url, string folder)
public static ImageSource LoadImage(string url, string folder)
{
var file = Path.Combine(PersonalFolder, imageFolder, Path.GetFileName(url));
if (File.Exists(file))
var file = Path.Combine(PersonalFolder, folder, Path.GetFileName(url));
if (!File.Exists(file))
{
return file;
file = DownloadImage(url, folder);
}
else
if (file != null)
{
file = DownloadImage(url, folder).Result;
return file;
return ImageSource.FromFile(file);
}
return null;
}
public static async Task<string> DownloadImage(string url, string folder)
public static string DownloadImage(string url, string folder)
{
try
{
var file = Path.Combine(PersonalFolder, folder, Path.GetFileName(url));
var directory = Path.Combine(PersonalFolder, 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);
if (response == null)
{
@ -104,11 +117,11 @@ namespace Pixiview.Utils
using (response)
using (var fs = File.OpenWrite(file))
{
await response.Content.CopyToAsync(fs);
if (response.Headers.Date != null)
{
File.SetLastWriteTimeUtc(file, response.Headers.Date.Value.UtcDateTime);
}
response.Content.CopyToAsync(fs).Wait();
//if (response.Headers.Date != null)
//{
// File.SetLastWriteTimeUtc(file, response.Headers.Date.Value.UtcDateTime);
//}
}
return file;
}
@ -156,6 +169,7 @@ namespace Pixiview.Utils
})
{
request.Headers.Referrer = referer == null ? Configs.Referer : new Uri(referer);
request.Headers.Add("user-agent", Configs.UserAgent);
if (headers != null)
{
foreach (var (header, value) in headers)
@ -173,6 +187,8 @@ namespace Pixiview.Utils
public static readonly WebProxy Proxy = new WebProxy("10.0.10.100", 8088);
public static readonly Uri Referer = new Uri("https://www.pixiv.net/");
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 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" +
@ -188,5 +204,20 @@ namespace Pixiview.Utils
"SID=2603358_VHyGPeRaz7LpeoFkRsHvjXIpApCMb56a; __cfduid=d9fa2d4d1ddd30db85ebb519f9855d256158780674" +
"7; privacy_policy_agreement=2";
public const string UserId = "2603358";
public static string GetThumbnailUrl(string url)
{
url = url.Replace("/custom-thumb/", "/img-master/");
var index = url.LastIndexOf("_square1200.jpg");
if (index < 0)
{
index = url.LastIndexOf("_custom1200.jpg");
}
if (index >= 0)
{
return url.Substring(0, index) + "_master1200.jpg";
}
return url;
}
}
}