adjust UI
This commit is contained in:
@@ -6,6 +6,7 @@ using Gallery.Util;
|
||||
using Gallery.Resources.Theme;
|
||||
using System.Collections.Generic;
|
||||
using Gallery.Util.Interface;
|
||||
using Gallery.Resources.UI;
|
||||
|
||||
namespace Gallery
|
||||
{
|
||||
@@ -26,12 +27,15 @@ namespace Gallery
|
||||
Preferences.Set(Config.IsProxiedKey, true);
|
||||
Preferences.Set(Config.ProxyHostKey, "192.168.25.9");
|
||||
Preferences.Set(Config.ProxyPortKey, 1081);
|
||||
|
||||
DependencyService.Register<MockDataStore>();
|
||||
}
|
||||
|
||||
private void InitResource()
|
||||
{
|
||||
foreach (var source in GallerySources)
|
||||
{
|
||||
source.InitDynamicResources(Definition.IconSolidFamily, LightTheme.Instance, DarkTheme.Instance);
|
||||
}
|
||||
|
||||
var theme = AppInfo.RequestedTheme;
|
||||
SetTheme(theme, true);
|
||||
}
|
||||
|
@@ -1,62 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:local="clr-namespace:Gallery.Views"
|
||||
Title="Gallery"
|
||||
x:Class="Gallery.AppShell">
|
||||
|
||||
<!--
|
||||
The overall app visual hierarchy is defined here, along with navigation.
|
||||
|
||||
https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/shell/
|
||||
-->
|
||||
xmlns:local="clr-namespace:Gallery"
|
||||
xmlns:r="clr-namespace:Gallery.Resources"
|
||||
xmlns:ui="clr-namespace:Gallery.Resources.UI"
|
||||
xmlns:util="clr-namespace:Gallery.Util;assembly=Gallery.Util"
|
||||
x:Class="Gallery.AppShell"
|
||||
x:Name="appShell"
|
||||
BackgroundColor="{DynamicResource NavigationColor}"
|
||||
FlyoutBackgroundColor="{DynamicResource WindowColor}"
|
||||
x:DataType="{x:Type local:AppShell}"
|
||||
BindingContext="{x:Reference appShell}">
|
||||
|
||||
<Shell.Resources>
|
||||
<ResourceDictionary>
|
||||
<Style x:Key="BaseStyle" TargetType="Element">
|
||||
<Setter Property="Shell.BackgroundColor" Value="{DynamicResource Primary}" />
|
||||
<Setter Property="Shell.ForegroundColor" Value="White" />
|
||||
<Setter Property="Shell.TitleColor" Value="White" />
|
||||
<Setter Property="Shell.BackgroundColor" Value="{DynamicResource NavigationColor}" />
|
||||
<Setter Property="Shell.ForegroundColor" Value="{DynamicResource TintColor}" />
|
||||
<Setter Property="Shell.TitleColor" Value="{DynamicResource TextColor}" />
|
||||
<Setter Property="Shell.DisabledColor" Value="#B4FFFFFF" />
|
||||
<Setter Property="Shell.UnselectedColor" Value="#95FFFFFF" />
|
||||
<Setter Property="Shell.TabBarBackgroundColor" Value="{DynamicResource Primary}" />
|
||||
<Setter Property="Shell.TabBarForegroundColor" Value="White"/>
|
||||
<Setter Property="Shell.TabBarUnselectedColor" Value="#95FFFFFF"/>
|
||||
<Setter Property="Shell.TabBarTitleColor" Value="White"/>
|
||||
<Setter Property="Shell.UnselectedColor" Value="{DynamicResource TintColor}" />
|
||||
<Setter Property="Shell.TabBarBackgroundColor" Value="{DynamicResource NavigationColor}" />
|
||||
<Setter Property="Shell.TabBarForegroundColor" Value="{DynamicResource TintColor}"/>
|
||||
<Setter Property="Shell.TabBarUnselectedColor" Value="{DynamicResource TintColor}"/>
|
||||
<Setter Property="Shell.TabBarTitleColor" Value="{DynamicResource TextColor}"/>
|
||||
</Style>
|
||||
<Style TargetType="TabBar" BasedOn="{StaticResource BaseStyle}" />
|
||||
<Style TargetType="FlyoutItem" BasedOn="{StaticResource BaseStyle}" />
|
||||
|
||||
<!--
|
||||
Default Styles for all Flyout Items
|
||||
https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/shell/flyout#flyoutitem-and-menuitem-style-classes
|
||||
-->
|
||||
<Style Class="FlyoutItemLabelStyle" TargetType="Label">
|
||||
<Setter Property="TextColor" Value="White"></Setter>
|
||||
</Style>
|
||||
<Style Class="FlyoutItemLayoutStyle" TargetType="Layout" ApplyToDerivedTypes="True">
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="BackgroundColor" Value="{x:OnPlatform UWP=Transparent, iOS=White}" />
|
||||
<Setter TargetName="FlyoutItemLabel" Property="Label.TextColor" Value="{DynamicResource Primary}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Selected">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="BackgroundColor" Value="{DynamicResource Primary}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!--
|
||||
Custom Style you can apply to any Flyout Item
|
||||
-->
|
||||
<Style Class="MenuItemLayoutStyle" TargetType="Layout" ApplyToDerivedTypes="True">
|
||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||
<VisualStateGroupList>
|
||||
@@ -70,24 +42,77 @@
|
||||
</VisualStateGroupList>
|
||||
</Setter>
|
||||
</Style>
|
||||
-->
|
||||
</ResourceDictionary>
|
||||
</Shell.Resources>
|
||||
|
||||
<Shell.FlyoutHeaderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid RowSpacing="0" BackgroundColor="{DynamicResource WindowColor}" Padding="20, 0, 0, 20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="80"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:CircleImage Aspect="AspectFill" Source="xamarin_logo.png"
|
||||
HeightRequest="60" WidthRequest="60"
|
||||
VerticalOptions="Center"/>
|
||||
<Label Grid.Column="1" VerticalOptions="Center" FontAttributes="Bold"
|
||||
Margin="10, 0, 0, 0"
|
||||
Text="{r:Text Title}" TextColor="{DynamicResource TextColor}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</Shell.FlyoutHeaderTemplate>
|
||||
|
||||
<Shell.ItemTemplate>
|
||||
<DataTemplate x:DataType="BaseShellItem">
|
||||
<Grid HeightRequest="40">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroupList>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="BackgroundColor"
|
||||
Value="{DynamicResource WindowColor}"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Selected">
|
||||
<VisualState.Setters>
|
||||
<Setter Property="BackgroundColor"
|
||||
Value="{DynamicResource NavigationSelectedColor}"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateGroupList>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{x:OnPlatform Android=54, iOS=50}"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Source="{Binding FlyoutIcon}"
|
||||
HorizontalOptions="Center" VerticalOptions="Center"
|
||||
HeightRequest="22"/>
|
||||
<Label Grid.Column="1" TextColor="{DynamicResource TextColor}"
|
||||
Text="{Binding Title}"
|
||||
FontSize="{x:OnPlatform Android=14, iOS=Small}"
|
||||
VerticalTextAlignment="Center"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</Shell.ItemTemplate>
|
||||
|
||||
<!--
|
||||
When the Flyout is visible this defines the content to display in the flyout.
|
||||
FlyoutDisplayOptions="AsMultipleItems" will create a separate flyout item for each child element
|
||||
https://docs.microsoft.com/dotnet/api/xamarin.forms.shellgroupitem.flyoutdisplayoptions?view=xamarin-forms
|
||||
-->
|
||||
<FlyoutItem Title="About" Icon="icon_about.png">
|
||||
<ShellContent Route="AboutPage" ContentTemplate="{DataTemplate local:AboutPage}" />
|
||||
</FlyoutItem>
|
||||
<FlyoutItem Title="Browse" Icon="icon_feed.png">
|
||||
<ShellContent Route="ItemsPage" ContentTemplate="{DataTemplate local:ItemsPage}" />
|
||||
</FlyoutItem>
|
||||
<FlyoutItem x:Name="flyoutItems"
|
||||
FlyoutDisplayOptions="AsMultipleItems"
|
||||
Route="{x:Static util:Routes.Gallery}" />
|
||||
|
||||
<!-- When the Flyout is visible this will be a menu item you can tie a click behavior to -->
|
||||
<MenuItem Text="Logout" StyleClass="MenuItemLayoutStyle" Clicked="OnMenuItemClicked">
|
||||
</MenuItem>
|
||||
<!--<MenuItem Text="Logout" StyleClass="MenuItemLayoutStyle" Clicked="OnMenuItemClicked" />-->
|
||||
|
||||
<!--
|
||||
TabBar lets you define content that won't show up in a flyout menu. When this content is active
|
||||
@@ -95,32 +120,12 @@
|
||||
you don't want users to be able to navigate away from. If you would like to navigate to this
|
||||
content you can do so by calling
|
||||
await Shell.Current.GoToAsync("//LoginPage");
|
||||
-->
|
||||
<TabBar>
|
||||
<ShellContent Route="LoginPage" ContentTemplate="{DataTemplate local:LoginPage}" />
|
||||
</TabBar>
|
||||
-->
|
||||
|
||||
<!-- Optional Templates
|
||||
// These may be provided inline as below or as separate classes.
|
||||
|
||||
// This header appears at the top of the Flyout.
|
||||
// https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/shell/flyout#flyout-header
|
||||
<Shell.FlyoutHeaderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>ContentHere</Grid>
|
||||
</DataTemplate>
|
||||
</Shell.FlyoutHeaderTemplate>
|
||||
|
||||
// ItemTemplate is for ShellItems as displayed in a Flyout
|
||||
// https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/shell/flyout#define-flyoutitem-appearance
|
||||
<Shell.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentView>
|
||||
Bindable Properties: Title, Icon
|
||||
</ContentView>
|
||||
</DataTemplate>
|
||||
</Shell.ItemTemplate>
|
||||
|
||||
// MenuItemTemplate is for MenuItems as displayed in a Flyout
|
||||
// https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/shell/flyout#define-menuitem-appearance
|
||||
<Shell.MenuItemTemplate>
|
||||
@@ -130,7 +135,6 @@
|
||||
</ContentView>
|
||||
</DataTemplate>
|
||||
</Shell.MenuItemTemplate>
|
||||
|
||||
-->
|
||||
|
||||
</Shell>
|
||||
|
@@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using Gallery.Resources;
|
||||
using Gallery.Resources.UI;
|
||||
using Gallery.Util;
|
||||
using Gallery.Views;
|
||||
using Xamarin.Forms;
|
||||
|
||||
@@ -6,16 +9,52 @@ namespace Gallery
|
||||
{
|
||||
public partial class AppShell : Shell
|
||||
{
|
||||
public static new AppShell Current => Shell.Current as AppShell;
|
||||
|
||||
public static Thickness NavigationBarOffset { get; private set; }
|
||||
public static Thickness TotalBarOffset { get; private set; }
|
||||
|
||||
public AppShell()
|
||||
{
|
||||
InitializeComponent();
|
||||
Routing.RegisterRoute(nameof(ItemDetailPage), typeof(ItemDetailPage));
|
||||
Routing.RegisterRoute(nameof(NewItemPage), typeof(NewItemPage));
|
||||
|
||||
#if DEBUG
|
||||
Log.Print($"folder: {Store.PersonalFolder}");
|
||||
Log.Print($"cache: {Store.CacheFolder}");
|
||||
#endif
|
||||
InitFlyouts();
|
||||
}
|
||||
|
||||
private async void OnMenuItemClicked(object sender, EventArgs e)
|
||||
private void InitFlyouts()
|
||||
{
|
||||
await Current.GoToAsync("//LoginPage");
|
||||
foreach (var source in App.GallerySources)
|
||||
{
|
||||
var s = source;
|
||||
var tab = new Tab
|
||||
{
|
||||
Title = source.Name,
|
||||
Route = source.Route,
|
||||
Items =
|
||||
{
|
||||
new ShellContent
|
||||
{
|
||||
ContentTemplate = new DataTemplate(() => new GalleryPage(s))
|
||||
}
|
||||
}
|
||||
}
|
||||
.DynamicResource(BaseShellItem.FlyoutIconProperty, source.FlyoutIconKey);
|
||||
flyoutItems.Items.Add(tab);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetNavigationBarHeight(double height)
|
||||
{
|
||||
NavigationBarOffset = new Thickness(0, height, 0, 0);
|
||||
}
|
||||
|
||||
public void SetStatusBarHeight(double navigation, double height)
|
||||
{
|
||||
TotalBarOffset = new Thickness(0, navigation + height, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -10,30 +10,6 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)App.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Models\Item.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Services\IDataStore.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Services\MockDataStore.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\AboutViewModel.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\BaseViewModel.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\ItemDetailViewModel.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\ItemsViewModel.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\LoginViewModel.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\NewItemViewModel.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Views\AboutPage.xaml.cs">
|
||||
<DependentUpon>Views\AboutPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Views\ItemDetailPage.xaml.cs">
|
||||
<DependentUpon>Views\ItemDetailPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Views\ItemsPage.xaml.cs">
|
||||
<DependentUpon>Views\ItemsPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Views\LoginPage.xaml.cs">
|
||||
<DependentUpon>Views\LoginPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Views\NewItemPage.xaml.cs">
|
||||
<DependentUpon>Views\NewItemPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)AppShell.xaml.cs">
|
||||
<DependentUpon>AppShell.xaml</DependentUpon>
|
||||
</Compile>
|
||||
@@ -44,11 +20,17 @@
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Resources\Theme\LightTheme.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Resources\Theme\DarkTheme.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Resources\UI\Definition.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Resources\UI\AdaptedPage.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Resources\UI\CardView.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Resources\UI\GalleryCollectionPage.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Services\GalleryCollection.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Resources\Converters.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Views\GalleryPage.xaml.cs">
|
||||
<DependentUpon>GalleryPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="$(MSBuildThisFileDirectory)Models\" />
|
||||
<Folder Include="$(MSBuildThisFileDirectory)Services\" />
|
||||
<Folder Include="$(MSBuildThisFileDirectory)ViewModels\" />
|
||||
<Folder Include="$(MSBuildThisFileDirectory)Views\" />
|
||||
<Folder Include="$(MSBuildThisFileDirectory)Resources\" />
|
||||
<Folder Include="$(MSBuildThisFileDirectory)Resources\Theme\" />
|
||||
@@ -56,32 +38,14 @@
|
||||
<Folder Include="$(MSBuildThisFileDirectory)Resources\UI\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\AboutPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\ItemDetailPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\ItemsPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\LoginPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\NewItemPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="$(MSBuildThisFileDirectory)AppShell.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="$(MSBuildThisFileDirectory)Resources\Languages\zh-CN.xml" />
|
||||
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\GalleryPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Resources\Languages\zh-CN.xml" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@@ -1,11 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Gallery.Models
|
||||
{
|
||||
public class Item
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Text { get; set; }
|
||||
public string Description { get; set; }
|
||||
}
|
||||
}
|
22
Gallery.Share/Resources/Converters.cs
Normal file
22
Gallery.Share/Resources/Converters.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Gallery.Resources.UI;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Gallery.Resources
|
||||
{
|
||||
public class FavoriteIconConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value == null ?
|
||||
Definition.IconLove :
|
||||
Definition.IconCircleLove;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml;
|
||||
using Gallery.Util;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace Gallery.Resources
|
||||
@@ -84,6 +85,7 @@ namespace Gallery.Resources
|
||||
}
|
||||
}
|
||||
|
||||
[ContentProperty(nameof(Text))]
|
||||
public class TextExtension : IMarkupExtension
|
||||
{
|
||||
public string Text { get; set; }
|
||||
|
@@ -27,9 +27,16 @@ namespace Gallery.Resources.Theme
|
||||
private void InitColors()
|
||||
{
|
||||
Add(StatusBarStyle, StatusBarStyles.WhiteText);
|
||||
Add(WindowColor, Color.Black);
|
||||
Add(TintColor, Color.FromRgb(0x94, 0x95, 0x9a));
|
||||
Add(TextColor, Color.White);
|
||||
Add(SubTextColor, Color.LightGray);
|
||||
Add(CardBackgroundColor, Color.FromRgb(0x33, 0x33, 0x33));
|
||||
Add(NavigationColor, Color.FromRgb(0x11, 0x11, 0x11));
|
||||
Add(NavigationSelectedColor, Color.FromRgb(0x22, 0x22, 0x22));
|
||||
Add(OptionBackColor, Color.Black);
|
||||
Add(OptionTintColor, Color.FromRgb(0x11, 0x11, 0x11));
|
||||
|
||||
Add(Primary, Color.FromRgb(33, 150, 243));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -27,9 +27,16 @@ namespace Gallery.Resources.Theme
|
||||
private void InitColors()
|
||||
{
|
||||
Add(StatusBarStyle, StatusBarStyles.DarkText);
|
||||
Add(WindowColor, Color.White);
|
||||
Add(TintColor, Color.FromRgb(0x87, 0x87, 0x8b)); // 0x7f, 0x99, 0xc6
|
||||
Add(TextColor, Color.Black);
|
||||
Add(SubTextColor, Color.DimGray);
|
||||
Add(CardBackgroundColor, Color.FromRgb(0xf3, 0xf3, 0xf3));
|
||||
Add(NavigationColor, Color.FromRgb(0xf0, 0xf0, 0xf0));
|
||||
Add(NavigationSelectedColor, Color.LightGray);
|
||||
Add(OptionBackColor, Color.FromRgb(0xf0, 0xf0, 0xf0));
|
||||
Add(OptionTintColor, Color.White);
|
||||
|
||||
Add(Primary, Color.FromRgb(33, 150, 243));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,15 @@ namespace Gallery.Resources.Theme
|
||||
public abstract class Theme : ResourceDictionary
|
||||
{
|
||||
public const string StatusBarStyle = nameof(StatusBarStyle);
|
||||
public const string WindowColor = nameof(WindowColor);
|
||||
public const string TintColor = nameof(TintColor);
|
||||
public const string TextColor = nameof(TextColor);
|
||||
public const string SubTextColor = nameof(SubTextColor);
|
||||
public const string CardBackgroundColor = nameof(CardBackgroundColor);
|
||||
public const string NavigationColor = nameof(NavigationColor);
|
||||
public const string NavigationSelectedColor = nameof(NavigationSelectedColor);
|
||||
public const string OptionBackColor = nameof(OptionBackColor);
|
||||
public const string OptionTintColor = nameof(OptionTintColor);
|
||||
|
||||
public const string IconLightFamily = nameof(IconLightFamily);
|
||||
public const string IconRegularFamily = nameof(IconRegularFamily);
|
||||
@@ -16,8 +24,6 @@ namespace Gallery.Resources.Theme
|
||||
public const string IconClose = nameof(IconClose);
|
||||
public const string FontIconRefresh = nameof(FontIconRefresh);
|
||||
|
||||
public const string Primary = nameof(Primary);
|
||||
|
||||
protected void InitResources()
|
||||
{
|
||||
Add(IconLightFamily, Definition.IconLightFamily);
|
||||
|
148
Gallery.Share/Resources/UI/AdaptedPage.cs
Normal file
148
Gallery.Share/Resources/UI/AdaptedPage.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using Gallery.Services;
|
||||
using Gallery.Util;
|
||||
using Xamarin.Essentials;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Gallery.Resources.UI
|
||||
{
|
||||
public class AdaptedPage : ContentPage
|
||||
{
|
||||
public static readonly BindableProperty TopMarginProperty = BindableProperty.Create(nameof(TopMargin), typeof(Thickness), typeof(AdaptedPage));
|
||||
|
||||
public Thickness TopMargin
|
||||
{
|
||||
get => (Thickness)GetValue(TopMarginProperty);
|
||||
set => SetValue(TopMarginProperty, value);
|
||||
}
|
||||
|
||||
public event EventHandler Load;
|
||||
public event EventHandler Unload;
|
||||
|
||||
protected static readonly bool isPhone = DeviceInfo.Idiom == DeviceIdiom.Phone;
|
||||
|
||||
public AdaptedPage()
|
||||
{
|
||||
SetDynamicResource(Screen.StatusBarStyleProperty, Theme.Theme.StatusBarStyle);
|
||||
Shell.SetNavBarHasShadow(this, true);
|
||||
}
|
||||
|
||||
public virtual void OnLoad() => Load?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
public virtual void OnUnload() => Unload?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
public virtual void OnOrientationChanged(bool landscape)
|
||||
{
|
||||
var old = TopMargin;
|
||||
Thickness @new;
|
||||
if (Definition.IsFullscreenDevice)
|
||||
{
|
||||
@new = landscape ?
|
||||
AppShell.NavigationBarOffset :
|
||||
AppShell.TotalBarOffset;
|
||||
}
|
||||
else if (isPhone)
|
||||
{
|
||||
@new = landscape ?
|
||||
Definition.TopOffset32 :
|
||||
AppShell.TotalBarOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
// iPad
|
||||
@new = AppShell.TotalBarOffset;
|
||||
}
|
||||
if (old != @new)
|
||||
{
|
||||
TopMargin = @new;
|
||||
OnTopMarginChanged(old, @new);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnTopMarginChanged(Thickness old, Thickness @new) { }
|
||||
|
||||
protected override void OnSizeAllocated(double width, double height)
|
||||
{
|
||||
base.OnSizeAllocated(width, height);
|
||||
OnOrientationChanged(width > height);
|
||||
}
|
||||
|
||||
protected void AnimateToMargin(View element, Thickness margin, bool animate = true)
|
||||
{
|
||||
var m = margin;
|
||||
var start = element.Margin.Top - m.Top;
|
||||
element.Margin = m;
|
||||
element.CancelAnimations();
|
||||
if (start > 0 && animate)
|
||||
{
|
||||
element.Animate("margin", top =>
|
||||
{
|
||||
element.TranslationY = top;
|
||||
},
|
||||
start, 0,
|
||||
#if DEBUG
|
||||
length: 500,
|
||||
#else
|
||||
length: 300,
|
||||
#endif
|
||||
easing: Easing.SinInOut,
|
||||
finished: (v, r) =>
|
||||
{
|
||||
element.TranslationY = 0;
|
||||
});
|
||||
}
|
||||
else if (element.TranslationY != 0)
|
||||
{
|
||||
element.TranslationY = 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected void Start(Action action)
|
||||
{
|
||||
if (Tap.IsBusy)
|
||||
{
|
||||
Log.Error($"{GetType()}.tap", "gesture recognizer is now busy...");
|
||||
return;
|
||||
}
|
||||
using (Tap.Start())
|
||||
{
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
private class Tap : IDisposable
|
||||
{
|
||||
public static bool IsBusy
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
return instance?.isBusy == true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly object sync = new();
|
||||
private static readonly Tap instance = new();
|
||||
|
||||
private Tap() { }
|
||||
|
||||
public static Tap Start()
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
instance.isBusy = true;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private bool isBusy = false;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
isBusy = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
Gallery.Share/Resources/UI/CardView.cs
Normal file
44
Gallery.Share/Resources/UI/CardView.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Gallery.Util.Model;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Gallery.Resources.UI
|
||||
{
|
||||
public class CardView : ContentView
|
||||
{
|
||||
public static readonly BindableProperty CornerRadiusProperty = BindableProperty.Create(nameof(CornerRadius), typeof(float), typeof(CardView));
|
||||
public static readonly BindableProperty ShadowColorProperty = BindableProperty.Create(nameof(ShadowColor), typeof(Color), typeof(CardView));
|
||||
public static readonly BindableProperty ShadowRadiusProperty = BindableProperty.Create(nameof(ShadowRadius), typeof(float), typeof(CardView), 3f);
|
||||
public static readonly BindableProperty ShadowOffsetProperty = BindableProperty.Create(nameof(ShadowOffset), typeof(Size), typeof(CardView));
|
||||
|
||||
public float CornerRadius
|
||||
{
|
||||
get => (float)GetValue(CornerRadiusProperty);
|
||||
set => SetValue(CornerRadiusProperty, value);
|
||||
}
|
||||
public Color ShadowColor
|
||||
{
|
||||
get => (Color)GetValue(ShadowColorProperty);
|
||||
set => SetValue(ShadowColorProperty, value);
|
||||
}
|
||||
public float ShadowRadius
|
||||
{
|
||||
get => (float)GetValue(ShadowRadiusProperty);
|
||||
set => SetValue(ShadowRadiusProperty, value);
|
||||
}
|
||||
public Size ShadowOffset
|
||||
{
|
||||
get => (Size)GetValue(ShadowOffsetProperty);
|
||||
set => SetValue(ShadowOffsetProperty, value);
|
||||
}
|
||||
|
||||
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
|
||||
{
|
||||
if (BindingContext is GalleryItem item &&
|
||||
item.Width > 0 && item.ImageHeight.IsAuto)
|
||||
{
|
||||
item.ImageHeight = widthConstraint * item.Height / item.Width;
|
||||
}
|
||||
return base.OnMeasure(widthConstraint, heightConstraint);
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,6 +10,12 @@ namespace Gallery.Resources.UI
|
||||
public const double FontSizeTitle = 18.0;
|
||||
|
||||
public static readonly Thickness ScreenBottomPadding;
|
||||
public static readonly Thickness TopOffset32 = new(0, 32, 0, 0);
|
||||
public static readonly Color ColorLightShadow = Color.FromRgba(0, 0, 0, 0x20);
|
||||
public static readonly Color ColorRedBackground = Color.FromRgb(0xfd, 0x43, 0x63);
|
||||
public static readonly Color ColorDownloadBackground = Color.FromRgb(0xd7, 0xd9, 0xe0);
|
||||
public static readonly ImageSource DownloadBackground = ImageSource.FromFile("download.png");
|
||||
public static readonly double FontSizeSmall = Device.GetNamedSize(NamedSize.Small, typeof(Label));
|
||||
|
||||
#if __IOS__
|
||||
public const string IconLightFamily = "FontAwesome5Pro-Light";
|
||||
@@ -26,6 +32,8 @@ namespace Gallery.Resources.UI
|
||||
#endif
|
||||
|
||||
public const string IconRefresh = "\uf2f9";
|
||||
public const string IconLove = "\uf004";
|
||||
public const string IconCircleLove = "\uf4c7";
|
||||
public const string IconClose = "\uf057";
|
||||
|
||||
static Definition()
|
||||
|
557
Gallery.Share/Resources/UI/GalleryCollectionPage.cs
Normal file
557
Gallery.Share/Resources/UI/GalleryCollectionPage.cs
Normal file
@@ -0,0 +1,557 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Gallery.Services;
|
||||
using Gallery.Util;
|
||||
using Gallery.Util.Interface;
|
||||
using Gallery.Util.Model;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Gallery.Resources.UI
|
||||
{
|
||||
public abstract class GalleryCollectionPage : GalleryScrollableCollectionPage<GalleryItem[]>
|
||||
{
|
||||
protected readonly IGallerySource source;
|
||||
|
||||
public GalleryCollectionPage(IGallerySource source)
|
||||
{
|
||||
this.source = source;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IGalleryCollectionPage
|
||||
{
|
||||
GalleryCollection GalleryCollection { get; set; }
|
||||
}
|
||||
|
||||
public abstract class GalleryCollectionPage<T> : AdaptedPage, IGalleryCollectionPage
|
||||
{
|
||||
const int EXPIRED_MINUTES = 5;
|
||||
|
||||
protected const double loadingOffset = -40;
|
||||
|
||||
public static readonly BindableProperty GalleryProperty = BindableProperty.Create(nameof(Gallery), typeof(GalleryCollection), typeof(GalleryCollectionPage<T>));
|
||||
public static readonly BindableProperty ColumnsProperty = BindableProperty.Create(nameof(Columns), typeof(int), typeof(GalleryCollectionPage<T>),
|
||||
defaultValue: 2);
|
||||
public static readonly BindableProperty IsLoadingProperty = BindableProperty.Create(nameof(IsLoading), typeof(bool), typeof(GalleryCollectionPage<T>),
|
||||
defaultValue: true);
|
||||
public static readonly BindableProperty IsBottomLoadingProperty = BindableProperty.Create(nameof(IsBottomLoading), typeof(bool), typeof(GalleryCollectionPage<T>));
|
||||
|
||||
public GalleryCollection Gallery
|
||||
{
|
||||
get => (GalleryCollection)GetValue(GalleryProperty);
|
||||
set => SetValue(GalleryProperty, value);
|
||||
}
|
||||
public int Columns
|
||||
{
|
||||
get => (int)GetValue(ColumnsProperty);
|
||||
set => SetValue(ColumnsProperty, value);
|
||||
}
|
||||
public bool IsLoading
|
||||
{
|
||||
get => (bool)GetValue(IsLoadingProperty);
|
||||
set => SetValue(IsLoadingProperty, value);
|
||||
}
|
||||
public bool IsBottomLoading
|
||||
{
|
||||
get => (bool)GetValue(IsBottomLoadingProperty);
|
||||
set => SetValue(IsBottomLoadingProperty, value);
|
||||
}
|
||||
|
||||
public GalleryCollection GalleryCollection { get; set; }
|
||||
|
||||
protected virtual ActivityIndicator LoadingIndicator => null;
|
||||
protected virtual double IndicatorMarginTop => 16;
|
||||
|
||||
protected bool Expired => lastUpdated == default || (DateTime.Now - lastUpdated).TotalMinutes > EXPIRED_MINUTES;
|
||||
|
||||
protected readonly Command<GalleryItem> commandGalleryItemTapped;
|
||||
protected DateTime lastUpdated;
|
||||
protected double topOffset;
|
||||
protected string lastError;
|
||||
|
||||
private readonly object sync = new();
|
||||
private readonly Stack<ParallelTask> tasks = new();
|
||||
private T galleryData;
|
||||
|
||||
public GalleryCollectionPage()
|
||||
{
|
||||
commandGalleryItemTapped = new Command<GalleryItem>(OnGalleryItemTapped);
|
||||
}
|
||||
|
||||
private void OnGalleryItemTapped(GalleryItem item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
//Start(async () =>
|
||||
//{
|
||||
// var page = new GalleryItemPage(item);
|
||||
// await Navigation.PushAsync(page);
|
||||
//});
|
||||
}
|
||||
|
||||
public override void OnUnload()
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
while (tasks.TryPop(out var task))
|
||||
{
|
||||
if (task != null)
|
||||
{
|
||||
task.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
InvalidateCollection();
|
||||
Gallery = null;
|
||||
lastUpdated = default;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
if (lastUpdated == default)
|
||||
{
|
||||
StartLoading();
|
||||
}
|
||||
}
|
||||
|
||||
#if __IOS__
|
||||
public override void OnOrientationChanged(bool landscape)
|
||||
{
|
||||
base.OnOrientationChanged(landscape);
|
||||
|
||||
if (Definition.IsFullscreenDevice)
|
||||
{
|
||||
topOffset = landscape ?
|
||||
AppShell.NavigationBarOffset.Top :
|
||||
AppShell.TotalBarOffset.Top;
|
||||
}
|
||||
else if (isPhone)
|
||||
{
|
||||
topOffset = landscape ?
|
||||
Definition.TopOffset32.Top :
|
||||
AppShell.TotalBarOffset.Top;
|
||||
}
|
||||
else
|
||||
{
|
||||
// iPad
|
||||
topOffset = AppShell.TotalBarOffset.Top;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
protected override void OnSizeAllocated(double width, double height)
|
||||
{
|
||||
base.OnSizeAllocated(width, height);
|
||||
|
||||
int columns;
|
||||
if (width > height)
|
||||
{
|
||||
columns = isPhone ? 4 : 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
columns = isPhone ? 2 : 4;
|
||||
}
|
||||
if (Columns != columns)
|
||||
{
|
||||
Columns = columns;
|
||||
#if DEBUG
|
||||
Log.Print($"changing columns to {columns}");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Task<T> DoloadGalleryData(bool force);
|
||||
protected abstract IEnumerable<GalleryItem> DoGetGalleryList(T data, out int tag);
|
||||
|
||||
protected virtual GalleryCollection FilterGalleryCollection(GalleryCollection collection, bool bottom)
|
||||
{
|
||||
GalleryCollection = collection;
|
||||
return collection;
|
||||
}
|
||||
|
||||
protected void InvalidateCollection()
|
||||
{
|
||||
var collection = GalleryCollection;
|
||||
if (collection != null)
|
||||
{
|
||||
collection.Running = false;
|
||||
GalleryCollection = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void StartLoading(bool force = false, bool isBottom = false)
|
||||
{
|
||||
if (force || Expired)
|
||||
{
|
||||
var indicator = LoadingIndicator;
|
||||
if (indicator == null || isBottom)
|
||||
{
|
||||
if (isBottom)
|
||||
{
|
||||
IsBottomLoading = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
InvalidateCollection();
|
||||
IsLoading = true;
|
||||
}
|
||||
#if __IOS__
|
||||
Device.StartTimer(TimeSpan.FromMilliseconds(48), () =>
|
||||
#else
|
||||
Device.StartTimer(TimeSpan.FromMilliseconds(150), () =>
|
||||
#endif
|
||||
{
|
||||
_ = DoloadGallerySource(force, isBottom);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
InvalidateCollection();
|
||||
IsLoading = true;
|
||||
|
||||
var offset = 16 - IndicatorMarginTop;
|
||||
indicator.CancelAnimations();
|
||||
indicator.Animate("margin", top =>
|
||||
{
|
||||
indicator.Margin = new Thickness(0, top, 0, offset);
|
||||
},
|
||||
loadingOffset - offset, 16 - offset,
|
||||
easing: Easing.CubicOut,
|
||||
finished: (v, r) =>
|
||||
{
|
||||
_ = DoloadGallerySource(force, isBottom);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void DoGalleryLoaded(GalleryCollection collection, bool bottom)
|
||||
{
|
||||
collection = FilterGalleryCollection(collection, bottom);
|
||||
|
||||
var indicator = LoadingIndicator;
|
||||
if (indicator == null || bottom)
|
||||
{
|
||||
IsLoading = false;
|
||||
IsBottomLoading = false;
|
||||
#if __IOS__
|
||||
Device.StartTimer(TimeSpan.FromMilliseconds(48), () =>
|
||||
#else
|
||||
Device.StartTimer(TimeSpan.FromMilliseconds(150), () =>
|
||||
#endif
|
||||
{
|
||||
Gallery = collection;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var offset = 16 - IndicatorMarginTop;
|
||||
indicator.CancelAnimations();
|
||||
indicator.Animate("margin", top =>
|
||||
{
|
||||
indicator.Margin = new Thickness(0, top, 0, offset);
|
||||
},
|
||||
16 - offset, loadingOffset - offset,
|
||||
easing: Easing.CubicIn,
|
||||
finished: (v, r) =>
|
||||
{
|
||||
indicator.Margin = new Thickness(0, v, 0, offset);
|
||||
IsLoading = false;
|
||||
IsBottomLoading = false;
|
||||
#if __IOS__
|
||||
Device.StartTimer(TimeSpan.FromMilliseconds(48), () =>
|
||||
#else
|
||||
Device.StartTimer(TimeSpan.FromMilliseconds(150), () =>
|
||||
#endif
|
||||
{
|
||||
Gallery = collection;
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task ScrollToTopAsync(ScrollView scrollView)
|
||||
{
|
||||
if (scrollView.ScrollY > -topOffset)
|
||||
{
|
||||
#if __IOS__
|
||||
await scrollView.ScrollToAsync(scrollView.ScrollX, -topOffset, true);
|
||||
#else
|
||||
await scrollView.ScrollToAsync(0, -topOffset, false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
protected DataTemplate GetCardViewTemplate(string titleBinding = null)
|
||||
{
|
||||
return new DataTemplate(() =>
|
||||
{
|
||||
var image = new RoundImage
|
||||
{
|
||||
BackgroundColor = Definition.ColorDownloadBackground,
|
||||
CornerRadius = 10,
|
||||
CornerMasks = CornerMask.Top,
|
||||
HorizontalOptions = LayoutOptions.Fill,
|
||||
Aspect = Aspect.AspectFill,
|
||||
GestureRecognizers =
|
||||
{
|
||||
new TapGestureRecognizer
|
||||
{
|
||||
Command = commandGalleryItemTapped
|
||||
}
|
||||
.Binding(TapGestureRecognizer.CommandParameterProperty, ".")
|
||||
}
|
||||
}
|
||||
.Binding(Image.SourceProperty, nameof(GalleryItem.PreviewImage));
|
||||
|
||||
var title = new Label
|
||||
{
|
||||
Padding = new Thickness(8, 2),
|
||||
HorizontalOptions = LayoutOptions.FillAndExpand,
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
LineBreakMode = LineBreakMode.TailTruncation,
|
||||
FontSize = Definition.FontSizeSmall
|
||||
}
|
||||
.DynamicResource(Label.TextColorProperty, Theme.Theme.TextColor);
|
||||
|
||||
var favorite = new Label
|
||||
{
|
||||
WidthRequest = 26,
|
||||
HorizontalOptions = LayoutOptions.End,
|
||||
HorizontalTextAlignment = TextAlignment.End,
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
FontSize = Definition.FontSizeSmall,
|
||||
TextColor = Definition.ColorRedBackground,
|
||||
IsVisible = false
|
||||
}
|
||||
.Binding(Label.TextProperty, nameof(GalleryItem.BookmarkId), converter: new FavoriteIconConverter())
|
||||
.Binding(IsVisibleProperty, nameof(GalleryItem.IsFavorite))
|
||||
.DynamicResource(Label.FontFamilyProperty, Theme.Theme.IconSolidFamily);
|
||||
|
||||
return new CardView
|
||||
{
|
||||
Padding = 0,
|
||||
Margin = 0,
|
||||
CornerRadius = 10,
|
||||
ShadowColor = Definition.ColorLightShadow,
|
||||
ShadowOffset = new Size(1, 1),
|
||||
Content = new Grid
|
||||
{
|
||||
HorizontalOptions = LayoutOptions.Fill,
|
||||
RowSpacing = 0,
|
||||
RowDefinitions =
|
||||
{
|
||||
new RowDefinition().Binding(RowDefinition.HeightProperty, nameof(GalleryItem.ImageHeight)),
|
||||
new RowDefinition { Height = 30 }
|
||||
},
|
||||
Children =
|
||||
{
|
||||
image,
|
||||
new Grid
|
||||
{
|
||||
ColumnDefinitions =
|
||||
{
|
||||
new ColumnDefinition(),
|
||||
new ColumnDefinition { Width = 20 }
|
||||
},
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
Padding = new Thickness(0, 0, 8, 0),
|
||||
Children =
|
||||
{
|
||||
title.Binding(Label.TextProperty, titleBinding ?? nameof(GalleryItem.TagDescription)),
|
||||
favorite.GridColumn(1)
|
||||
}
|
||||
}
|
||||
.GridRow(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
.DynamicResource(BackgroundColorProperty, Theme.Theme.CardBackgroundColor);
|
||||
});
|
||||
}
|
||||
|
||||
protected async Task DoloadGallerySource(bool force = false, bool bottom = false)
|
||||
{
|
||||
#if DEBUG
|
||||
Log.Print($"start loading data, force: {force}");
|
||||
#endif
|
||||
galleryData = await DoloadGalleryData(force);
|
||||
if (galleryData == null)
|
||||
{
|
||||
Log.Error("gallery.load", "failed to load gallery data.");
|
||||
return;
|
||||
}
|
||||
if (force)
|
||||
{
|
||||
lastUpdated = DateTime.Now;
|
||||
}
|
||||
|
||||
var data = DoGetGalleryList(galleryData, out int tag).Where(i => i != null);
|
||||
var collection = new GalleryCollection(data);
|
||||
foreach (var item in collection)
|
||||
{
|
||||
if (item.PreviewImage == null)
|
||||
{
|
||||
var image = await Store.LoadPreviewImage(item.PreviewUrl, false);
|
||||
if (image != null)
|
||||
{
|
||||
item.PreviewImage = image;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DoGalleryLoaded(collection, bottom);
|
||||
DoloadImages(collection, tag);
|
||||
}
|
||||
|
||||
private void DoloadImages(GalleryCollection collection, int tag)
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
if (tasks.TryPeek(out var peek))
|
||||
{
|
||||
if (peek != null && peek.TagIndex >= tag)
|
||||
{
|
||||
Log.Print($"tasks expired ({tasks.Count}, peek: {peek.TagIndex}, now: {tag}, will be disposed.");
|
||||
while (tasks.TryPop(out var t))
|
||||
{
|
||||
t?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var list = collection.Where(i => i.PreviewImage == null).ToArray();
|
||||
var task = ParallelTask.Start("collection.load", 0, list.Length, 2, i =>
|
||||
{
|
||||
if (!collection.Running)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var item = list[i];
|
||||
if (item.PreviewImage == null && item.PreviewUrl != null)
|
||||
{
|
||||
item.PreviewImage = Definition.DownloadBackground;
|
||||
var image = Store.LoadPreviewImage(item.PreviewUrl, true, force: true).Result;
|
||||
if (image != null)
|
||||
{
|
||||
item.PreviewImage = image;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}, tagIndex: tag);
|
||||
|
||||
if (task != null)
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
tasks.Push(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class GalleryScrollableCollectionPage<T> : GalleryCollectionPage<T>
|
||||
{
|
||||
protected const int SCROLL_OFFSET = 33;
|
||||
protected ScrollDirection scrollDirection = ScrollDirection.Stop;
|
||||
protected double lastScrollY = double.MinValue;
|
||||
|
||||
private double lastRefreshY = double.MinValue;
|
||||
private double offset;
|
||||
|
||||
protected bool IsScrollingDown(double y)
|
||||
{
|
||||
if (y > lastScrollY)
|
||||
{
|
||||
if (scrollDirection != ScrollDirection.Down)
|
||||
{
|
||||
scrollDirection = ScrollDirection.Down;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (scrollDirection != ScrollDirection.Up)
|
||||
{
|
||||
scrollDirection = ScrollDirection.Up;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void SetOffset(double off)
|
||||
{
|
||||
offset = off;
|
||||
}
|
||||
|
||||
protected abstract bool CheckRefresh();
|
||||
|
||||
protected override void StartLoading(bool force = false, bool isBottom = false)
|
||||
{
|
||||
if (!isBottom)
|
||||
{
|
||||
lastRefreshY = double.MinValue;
|
||||
}
|
||||
base.StartLoading(force, isBottom);
|
||||
}
|
||||
|
||||
protected override GalleryCollection FilterGalleryCollection(GalleryCollection collection, bool bottom)
|
||||
{
|
||||
var now = GalleryCollection;
|
||||
if (now == null)
|
||||
{
|
||||
now = collection;
|
||||
GalleryCollection = now;
|
||||
}
|
||||
else
|
||||
{
|
||||
now.AddRange(collection);
|
||||
}
|
||||
return now;
|
||||
}
|
||||
|
||||
protected void OnScrolled(double y)
|
||||
{
|
||||
lastScrollY = y;
|
||||
if (scrollDirection == ScrollDirection.Up)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (y > 0 && offset > 0 && y - topOffset > offset)
|
||||
{
|
||||
if (IsLoading || IsBottomLoading)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (y - lastRefreshY > 200)
|
||||
{
|
||||
if (CheckRefresh())
|
||||
{
|
||||
lastRefreshY = y;
|
||||
#if DEBUG
|
||||
Log.Print("start to load next page");
|
||||
#endif
|
||||
StartLoading(true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ScrollDirection
|
||||
{
|
||||
Stop,
|
||||
Up,
|
||||
Down
|
||||
}
|
||||
}
|
57
Gallery.Share/Services/GalleryCollection.cs
Normal file
57
Gallery.Share/Services/GalleryCollection.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Gallery.Resources.UI;
|
||||
using Gallery.Util.Model;
|
||||
using Xamarin.Essentials;
|
||||
|
||||
namespace Gallery.Services
|
||||
{
|
||||
public class GalleryCollection : List<GalleryItem>, ICollectionChanged
|
||||
{
|
||||
private static GalleryCollection empty;
|
||||
|
||||
public static GalleryCollection Empty
|
||||
{
|
||||
get
|
||||
{
|
||||
if (empty == null)
|
||||
{
|
||||
empty = new GalleryCollection();
|
||||
}
|
||||
return empty;
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler<CollectionChangedEventArgs> CollectionChanged;
|
||||
|
||||
public bool Running { get; set; }
|
||||
|
||||
public GalleryCollection() : base()
|
||||
{
|
||||
Running = true;
|
||||
}
|
||||
|
||||
public GalleryCollection(IEnumerable<GalleryItem> gallery) : base(gallery)
|
||||
{
|
||||
Running = true;
|
||||
}
|
||||
|
||||
public void AddRange(List<GalleryItem> items)
|
||||
{
|
||||
var e = new CollectionChangedEventArgs
|
||||
{
|
||||
NewStartingIndex = Count,
|
||||
NewItems = items
|
||||
};
|
||||
base.AddRange(items);
|
||||
if (MainThread.IsMainThread)
|
||||
{
|
||||
CollectionChanged?.Invoke(this, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() => CollectionChanged?.Invoke(this, e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Gallery.Services
|
||||
{
|
||||
public interface IDataStore<T>
|
||||
{
|
||||
Task<bool> AddItemAsync(T item);
|
||||
Task<bool> UpdateItemAsync(T item);
|
||||
Task<bool> DeleteItemAsync(string id);
|
||||
Task<T> GetItemAsync(string id);
|
||||
Task<IEnumerable<T>> GetItemsAsync(bool forceRefresh = false);
|
||||
}
|
||||
}
|
@@ -1,60 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Gallery.Models;
|
||||
|
||||
namespace Gallery.Services
|
||||
{
|
||||
public class MockDataStore : IDataStore<Item>
|
||||
{
|
||||
readonly List<Item> items;
|
||||
|
||||
public MockDataStore()
|
||||
{
|
||||
items = new List<Item>()
|
||||
{
|
||||
new Item { Id = Guid.NewGuid().ToString(), Text = "First item", Description="This is an item description." },
|
||||
new Item { Id = Guid.NewGuid().ToString(), Text = "Second item", Description="This is an item description." },
|
||||
new Item { Id = Guid.NewGuid().ToString(), Text = "Third item", Description="This is an item description." },
|
||||
new Item { Id = Guid.NewGuid().ToString(), Text = "Fourth item", Description="This is an item description." },
|
||||
new Item { Id = Guid.NewGuid().ToString(), Text = "Fifth item", Description="This is an item description." },
|
||||
new Item { Id = Guid.NewGuid().ToString(), Text = "Sixth item", Description="This is an item description." }
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<bool> AddItemAsync(Item item)
|
||||
{
|
||||
items.Add(item);
|
||||
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateItemAsync(Item item)
|
||||
{
|
||||
var oldItem = items.Where((Item arg) => arg.Id == item.Id).FirstOrDefault();
|
||||
items.Remove(oldItem);
|
||||
items.Add(item);
|
||||
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteItemAsync(string id)
|
||||
{
|
||||
var oldItem = items.Where((Item arg) => arg.Id == id).FirstOrDefault();
|
||||
items.Remove(oldItem);
|
||||
|
||||
return await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public async Task<Item> GetItemAsync(string id)
|
||||
{
|
||||
return await Task.FromResult(items.FirstOrDefault(s => s.Id == id));
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Item>> GetItemsAsync(bool forceRefresh = false)
|
||||
{
|
||||
return await Task.FromResult(items);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
using Xamarin.Essentials;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Gallery.ViewModels
|
||||
{
|
||||
public class AboutViewModel : BaseViewModel
|
||||
{
|
||||
public AboutViewModel()
|
||||
{
|
||||
Title = "About";
|
||||
OpenWebCommand = new Command(async () => await Browser.OpenAsync("https://aka.ms/xamarin-quickstart"));
|
||||
}
|
||||
|
||||
public ICommand OpenWebCommand { get; }
|
||||
}
|
||||
}
|
@@ -1,56 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Xamarin.Forms;
|
||||
|
||||
using Gallery.Models;
|
||||
using Gallery.Services;
|
||||
|
||||
namespace Gallery.ViewModels
|
||||
{
|
||||
public class BaseViewModel : INotifyPropertyChanged
|
||||
{
|
||||
public IDataStore<Item> DataStore => DependencyService.Get<IDataStore<Item>>();
|
||||
|
||||
bool isBusy = false;
|
||||
public bool IsBusy
|
||||
{
|
||||
get { return isBusy; }
|
||||
set { SetProperty(ref isBusy, value); }
|
||||
}
|
||||
|
||||
string title = string.Empty;
|
||||
public string Title
|
||||
{
|
||||
get { return title; }
|
||||
set { SetProperty(ref title, value); }
|
||||
}
|
||||
|
||||
protected bool SetProperty<T>(ref T backingStore, T value,
|
||||
[CallerMemberName] string propertyName = "",
|
||||
Action onChanged = null)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(backingStore, value))
|
||||
return false;
|
||||
|
||||
backingStore = value;
|
||||
onChanged?.Invoke();
|
||||
OnPropertyChanged(propertyName);
|
||||
return true;
|
||||
}
|
||||
|
||||
#region INotifyPropertyChanged
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
var changed = PropertyChanged;
|
||||
if (changed == null)
|
||||
return;
|
||||
|
||||
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -1,57 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Gallery.Models;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Gallery.ViewModels
|
||||
{
|
||||
[QueryProperty(nameof(ItemId), nameof(ItemId))]
|
||||
public class ItemDetailViewModel : BaseViewModel
|
||||
{
|
||||
private string itemId;
|
||||
private string text;
|
||||
private string description;
|
||||
public string Id { get; set; }
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => text;
|
||||
set => SetProperty(ref text, value);
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get => description;
|
||||
set => SetProperty(ref description, value);
|
||||
}
|
||||
|
||||
public string ItemId
|
||||
{
|
||||
get
|
||||
{
|
||||
return itemId;
|
||||
}
|
||||
set
|
||||
{
|
||||
itemId = value;
|
||||
LoadItemId(value);
|
||||
}
|
||||
}
|
||||
|
||||
public async void LoadItemId(string itemId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = await DataStore.GetItemAsync(itemId);
|
||||
Id = item.Id;
|
||||
Text = item.Text;
|
||||
Description = item.Description;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Debug.WriteLine("Failed to Load Item");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,86 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms;
|
||||
|
||||
using Gallery.Models;
|
||||
using Gallery.Views;
|
||||
|
||||
namespace Gallery.ViewModels
|
||||
{
|
||||
public class ItemsViewModel : BaseViewModel
|
||||
{
|
||||
private Item _selectedItem;
|
||||
|
||||
public ObservableCollection<Item> Items { get; }
|
||||
public Command LoadItemsCommand { get; }
|
||||
public Command AddItemCommand { get; }
|
||||
public Command<Item> ItemTapped { get; }
|
||||
|
||||
public ItemsViewModel()
|
||||
{
|
||||
Title = "Browse";
|
||||
Items = new ObservableCollection<Item>();
|
||||
LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());
|
||||
|
||||
ItemTapped = new Command<Item>(OnItemSelected);
|
||||
|
||||
AddItemCommand = new Command(OnAddItem);
|
||||
}
|
||||
|
||||
async Task ExecuteLoadItemsCommand()
|
||||
{
|
||||
IsBusy = true;
|
||||
|
||||
try
|
||||
{
|
||||
Items.Clear();
|
||||
var items = await DataStore.GetItemsAsync(true);
|
||||
foreach (var item in items)
|
||||
{
|
||||
Items.Add(item);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnAppearing()
|
||||
{
|
||||
IsBusy = true;
|
||||
SelectedItem = null;
|
||||
}
|
||||
|
||||
public Item SelectedItem
|
||||
{
|
||||
get => _selectedItem;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _selectedItem, value);
|
||||
OnItemSelected(value);
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnAddItem(object obj)
|
||||
{
|
||||
await Shell.Current.GoToAsync(nameof(NewItemPage));
|
||||
}
|
||||
|
||||
async void OnItemSelected(Item item)
|
||||
{
|
||||
if (item == null)
|
||||
return;
|
||||
|
||||
// This will push the ItemDetailPage onto the navigation stack
|
||||
await Shell.Current.GoToAsync($"{nameof(ItemDetailPage)}?{nameof(ItemDetailViewModel.ItemId)}={item.Id}");
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
using Gallery.Views;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Gallery.ViewModels
|
||||
{
|
||||
public class LoginViewModel : BaseViewModel
|
||||
{
|
||||
public Command LoginCommand { get; }
|
||||
|
||||
public LoginViewModel()
|
||||
{
|
||||
LoginCommand = new Command(OnLoginClicked);
|
||||
}
|
||||
|
||||
private async void OnLoginClicked(object obj)
|
||||
{
|
||||
// Prefixing with `//` switches to a different navigation stack instead of pushing to the active one
|
||||
await Shell.Current.GoToAsync($"//{nameof(AboutPage)}");
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,65 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows.Input;
|
||||
using Gallery.Models;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Gallery.ViewModels
|
||||
{
|
||||
public class NewItemViewModel : BaseViewModel
|
||||
{
|
||||
private string text;
|
||||
private string description;
|
||||
|
||||
public NewItemViewModel()
|
||||
{
|
||||
SaveCommand = new Command(OnSave, ValidateSave);
|
||||
CancelCommand = new Command(OnCancel);
|
||||
this.PropertyChanged +=
|
||||
(_, __) => SaveCommand.ChangeCanExecute();
|
||||
}
|
||||
|
||||
private bool ValidateSave()
|
||||
{
|
||||
return !String.IsNullOrWhiteSpace(text)
|
||||
&& !String.IsNullOrWhiteSpace(description);
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => text;
|
||||
set => SetProperty(ref text, value);
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get => description;
|
||||
set => SetProperty(ref description, value);
|
||||
}
|
||||
|
||||
public Command SaveCommand { get; }
|
||||
public Command CancelCommand { get; }
|
||||
|
||||
private async void OnCancel()
|
||||
{
|
||||
// This will pop the current page off the navigation stack
|
||||
await Shell.Current.GoToAsync("..");
|
||||
}
|
||||
|
||||
private async void OnSave()
|
||||
{
|
||||
Item newItem = new Item()
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Text = Text,
|
||||
Description = Description
|
||||
};
|
||||
|
||||
await DataStore.AddItemAsync(newItem);
|
||||
|
||||
// This will pop the current page off the navigation stack
|
||||
await Shell.Current.GoToAsync("..");
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,52 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Gallery.Views.AboutPage"
|
||||
xmlns:vm="clr-namespace:Gallery.ViewModels"
|
||||
Title="{Binding Title}">
|
||||
|
||||
<ContentPage.BindingContext>
|
||||
<vm:AboutViewModel />
|
||||
</ContentPage.BindingContext>
|
||||
|
||||
<ContentPage.Resources>
|
||||
<ResourceDictionary>
|
||||
<Color x:Key="Accent">#96d1ff</Color>
|
||||
</ResourceDictionary>
|
||||
</ContentPage.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackLayout BackgroundColor="{StaticResource Accent}" VerticalOptions="FillAndExpand" HorizontalOptions="Fill">
|
||||
<StackLayout Orientation="Horizontal" HorizontalOptions="Center" VerticalOptions="Center">
|
||||
<ContentView Padding="0,40,0,40" VerticalOptions="FillAndExpand">
|
||||
<Image Source="xamarin_logo.png" VerticalOptions="Center" HeightRequest="64" />
|
||||
</ContentView>
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
<ScrollView Grid.Row="1">
|
||||
<StackLayout Orientation="Vertical" Padding="30,24,30,24" Spacing="10">
|
||||
<Label Text="Start developing now" FontSize="Title"/>
|
||||
<Label Text="Make changes to your XAML file and save to see your UI update in the running app with XAML Hot Reload. Give it a try!" FontSize="16" Padding="0,0,0,0"/>
|
||||
<Label FontSize="16" Padding="0,24,0,0">
|
||||
<Label.FormattedText>
|
||||
<FormattedString>
|
||||
<FormattedString.Spans>
|
||||
<Span Text="Learn more at "/>
|
||||
<Span Text="https://aka.ms/xamarin-quickstart" FontAttributes="Bold"/>
|
||||
</FormattedString.Spans>
|
||||
</FormattedString>
|
||||
</Label.FormattedText>
|
||||
</Label>
|
||||
<Button Margin="0,10,0,0" Text="Learn more"
|
||||
Command="{Binding OpenWebCommand}"
|
||||
BackgroundColor="{DynamicResource Primary}"
|
||||
TextColor="White" />
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</Grid>
|
||||
|
||||
</ContentPage>
|
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Gallery.Util;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace Gallery.Views
|
||||
{
|
||||
public partial class AboutPage : ContentPage
|
||||
{
|
||||
public AboutPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override async void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
var result = await App.GallerySources[0].GetRecentItemsAsync(1);
|
||||
if (result != null)
|
||||
{
|
||||
for (var i = 0; i < result.Length; i++)
|
||||
{
|
||||
var item = result[i];
|
||||
Log.Print($"id: {item.Id}, url: {item.RawUrl}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
Gallery.Share/Views/GalleryPage.xaml
Normal file
31
Gallery.Share/Views/GalleryPage.xaml
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<ui:GalleryCollectionPage
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:ui="clr-namespace:Gallery.Resources.UI"
|
||||
x:Class="Gallery.Views.GalleryPage"
|
||||
x:Name="yanderePage"
|
||||
BackgroundColor="{DynamicResource WindowColor}"
|
||||
BindingContext="{x:Reference yanderePage}">
|
||||
<ContentPage.Content>
|
||||
<Grid>
|
||||
<ScrollView x:Name="scrollView" Scrolled="ScrollView_Scrolled"
|
||||
HorizontalOptions="Fill" HorizontalScrollBarVisibility="Never">
|
||||
<StackLayout>
|
||||
<ActivityIndicator x:Name="activityLoading" Margin="0, -40, 0, 0"
|
||||
HeightRequest="40"
|
||||
IsRunning="{Binding IsLoading}"
|
||||
IsVisible="{Binding IsLoading}"/>
|
||||
<ui:FlowLayout ItemsSource="{Binding Gallery}"
|
||||
MaxHeightChanged="FlowLayout_MaxHeightChanged"
|
||||
HorizontalOptions="Fill" Column="{Binding Columns}"
|
||||
Margin="16" RowSpacing="16" ColumnSpacing="16"
|
||||
ItemTemplate="{StaticResource cardView}"/>
|
||||
<ActivityIndicator x:Name="activityBottomLoading" Margin="0, -10, 0, 16"
|
||||
IsRunning="{Binding IsBottomLoading}"
|
||||
IsVisible="{Binding IsBottomLoading}"/>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</Grid>
|
||||
</ContentPage.Content>
|
||||
</ui:GalleryCollectionPage>
|
66
Gallery.Share/Views/GalleryPage.xaml.cs
Normal file
66
Gallery.Share/Views/GalleryPage.xaml.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Gallery.Resources.UI;
|
||||
using Gallery.Util;
|
||||
using Gallery.Util.Interface;
|
||||
using Gallery.Util.Model;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Gallery.Views
|
||||
{
|
||||
public partial class GalleryPage : GalleryCollectionPage
|
||||
{
|
||||
private int currentPage;
|
||||
|
||||
public GalleryPage(IGallerySource source) : base(source)
|
||||
{
|
||||
Resources.Add("cardView", GetCardViewTemplate());
|
||||
InitializeComponent();
|
||||
|
||||
currentPage = 1;
|
||||
}
|
||||
|
||||
protected override ActivityIndicator LoadingIndicator => activityLoading;
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
if (currentPage != 1 && Gallery == null)
|
||||
{
|
||||
currentPage = 1;
|
||||
}
|
||||
base.OnAppearing();
|
||||
}
|
||||
|
||||
protected override async Task<GalleryItem[]> DoloadGalleryData(bool force)
|
||||
{
|
||||
var result = await source.GetRecentItemsAsync(currentPage);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override IEnumerable<GalleryItem> DoGetGalleryList(GalleryItem[] data, out int tag)
|
||||
{
|
||||
tag = currentPage;
|
||||
return data;
|
||||
}
|
||||
|
||||
private void FlowLayout_MaxHeightChanged(object sender, HeightEventArgs e)
|
||||
{
|
||||
SetOffset(e.ContentHeight - scrollView.Bounds.Height - SCROLL_OFFSET);
|
||||
}
|
||||
|
||||
protected override bool CheckRefresh()
|
||||
{
|
||||
currentPage++;
|
||||
#if DEBUG
|
||||
Log.Print($"loading page: {currentPage}");
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ScrollView_Scrolled(object sender, ScrolledEventArgs e)
|
||||
{
|
||||
var y = e.ScrollY;
|
||||
OnScrolled(y);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Gallery.Views.ItemDetailPage"
|
||||
Title="{Binding Title}">
|
||||
|
||||
<StackLayout Spacing="20" Padding="15">
|
||||
<Label Text="Text:" FontSize="Medium" />
|
||||
<Label Text="{Binding Text}" FontSize="Small"/>
|
||||
<Label Text="Description:" FontSize="Medium" />
|
||||
<Label Text="{Binding Description}" FontSize="Small"/>
|
||||
</StackLayout>
|
||||
|
||||
</ContentPage>
|
@@ -1,15 +0,0 @@
|
||||
using System.ComponentModel;
|
||||
using Xamarin.Forms;
|
||||
using Gallery.ViewModels;
|
||||
|
||||
namespace Gallery.Views
|
||||
{
|
||||
public partial class ItemDetailPage : ContentPage
|
||||
{
|
||||
public ItemDetailPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
BindingContext = new ItemDetailViewModel();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Gallery.Views.ItemsPage"
|
||||
Title="{Binding Title}"
|
||||
xmlns:local="clr-namespace:Gallery.ViewModels"
|
||||
xmlns:model="clr-namespace:Gallery.Models"
|
||||
x:Name="BrowseItemsPage">
|
||||
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Text="Add" Command="{Binding AddItemCommand}" />
|
||||
</ContentPage.ToolbarItems>
|
||||
<!--
|
||||
x:DataType enables compiled bindings for better performance and compile time validation of binding expressions.
|
||||
https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/data-binding/compiled-bindings
|
||||
-->
|
||||
<RefreshView x:DataType="local:ItemsViewModel" Command="{Binding LoadItemsCommand}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}">
|
||||
<CollectionView x:Name="ItemsListView"
|
||||
ItemsSource="{Binding Items}"
|
||||
SelectionMode="None">
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackLayout Padding="10" x:DataType="model:Item">
|
||||
<Label Text="{Binding Text}"
|
||||
LineBreakMode="NoWrap"
|
||||
Style="{DynamicResource ListItemTextStyle}"
|
||||
FontSize="16" />
|
||||
<Label Text="{Binding Description}"
|
||||
LineBreakMode="NoWrap"
|
||||
Style="{DynamicResource ListItemDetailTextStyle}"
|
||||
FontSize="13" />
|
||||
<StackLayout.GestureRecognizers>
|
||||
<TapGestureRecognizer
|
||||
NumberOfTapsRequired="1"
|
||||
Command="{Binding Source={RelativeSource AncestorType={x:Type local:ItemsViewModel}}, Path=ItemTapped}"
|
||||
CommandParameter="{Binding .}">
|
||||
</TapGestureRecognizer>
|
||||
</StackLayout.GestureRecognizers>
|
||||
</StackLayout>
|
||||
</DataTemplate>
|
||||
</CollectionView.ItemTemplate>
|
||||
</CollectionView>
|
||||
</RefreshView>
|
||||
</ContentPage>
|
@@ -1,33 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
using Gallery.Models;
|
||||
using Gallery.Views;
|
||||
using Gallery.ViewModels;
|
||||
|
||||
namespace Gallery.Views
|
||||
{
|
||||
public partial class ItemsPage : ContentPage
|
||||
{
|
||||
ItemsViewModel _viewModel;
|
||||
|
||||
public ItemsPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
BindingContext = _viewModel = new ItemsViewModel();
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
_viewModel.OnAppearing();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Gallery.Views.LoginPage"
|
||||
Shell.NavBarIsVisible="False">
|
||||
<ContentPage.Content>
|
||||
<StackLayout Padding="10,0,10,0" VerticalOptions="Center">
|
||||
<Button VerticalOptions="Center" Text="Login" Command="{Binding LoginCommand}"/>
|
||||
</StackLayout>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
@@ -1,21 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Gallery.ViewModels;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace Gallery.Views
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class LoginPage : ContentPage
|
||||
{
|
||||
public LoginPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.BindingContext = new LoginViewModel();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Gallery.Views.NewItemPage"
|
||||
Shell.PresentationMode="ModalAnimated"
|
||||
Title="New Item"
|
||||
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
|
||||
ios:Page.UseSafeArea="true">
|
||||
<ContentPage.Content>
|
||||
<StackLayout Spacing="3" Padding="15">
|
||||
<Label Text="Text" FontSize="Medium" />
|
||||
<Entry Text="{Binding Text, Mode=TwoWay}" FontSize="Medium" />
|
||||
<Label Text="Description" FontSize="Medium" />
|
||||
<Editor Text="{Binding Description, Mode=TwoWay}" AutoSize="TextChanges" FontSize="Medium" Margin="0" />
|
||||
<StackLayout Orientation="Horizontal">
|
||||
<Button Text="Cancel" Command="{Binding CancelCommand}" HorizontalOptions="FillAndExpand"></Button>
|
||||
<Button Text="Save" Command="{Binding SaveCommand}" HorizontalOptions="FillAndExpand"></Button>
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
</ContentPage.Content>
|
||||
|
||||
</ContentPage>
|
@@ -1,22 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
using Gallery.Models;
|
||||
using Gallery.ViewModels;
|
||||
|
||||
namespace Gallery.Views
|
||||
{
|
||||
public partial class NewItemPage : ContentPage
|
||||
{
|
||||
public Item Item { get; set; }
|
||||
|
||||
public NewItemPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
BindingContext = new NewItemViewModel();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user