add account page detail

This commit is contained in:
Tsanie Lily 2022-02-28 17:32:40 +08:00
parent 283acf7d35
commit 589c7514f2
17 changed files with 307 additions and 56 deletions

View File

@ -36,12 +36,13 @@
<Compile Include="$(MSBuildThisFileDirectory)UI\CustomControl.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UI\CustomEffect.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UI\Definition.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UI\GroupStackLayout.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UI\ItemSelectPage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Views\AccountPage.xaml.cs">
<DependentUpon>AccountPage.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Views\AddAccountPage.xaml.cs">
<DependentUpon>AddAccountPage.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Views\AddBillPage.xaml.cs">
<DependentUpon>AddBillPage.xaml</DependentUpon>
@ -67,6 +68,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\AddAccountPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\AddBillPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
@ -80,9 +85,4 @@
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Views\AddAccountPage.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@ -8,6 +8,7 @@ namespace Billing.Models
public string Icon { get; set; } = ICON_DEFAULT;
public AccountCategory Category { get; set; }
public string Name { get; set; }
public decimal Initial { get; set; }
public decimal Balance { get; set; }
public string Memo { get; set; }
@ -17,6 +18,7 @@ namespace Billing.Models
Icon = Read(node, nameof(Icon), ICON_DEFAULT);
Category = (AccountCategory)Read(node, nameof(Category), 0);
Name = Read(node, nameof(Name), string.Empty);
Initial = Read(node, nameof(Initial), 0m);
Balance = Read(node, nameof(Balance), 0m);
Memo = Read(node, nameof(Memo), null);
}
@ -27,6 +29,7 @@ namespace Billing.Models
Write(node, nameof(Icon), Icon);
Write(node, nameof(Category), (int)Category);
Write(node, nameof(Name), Name);
Write(node, nameof(Initial), Initial);
Write(node, nameof(Balance), Balance);
Write(node, nameof(Memo), Memo);
}

View File

@ -7,13 +7,13 @@ namespace Billing.UI
{
#region UI Properties
private static readonly BindableProperty SundayProperty = BindableProperty.Create(nameof(Sunday), typeof(BillingDay), typeof(BillingDate));
private static readonly BindableProperty MondayProperty = BindableProperty.Create(nameof(Monday), typeof(BillingDay), typeof(BillingDate));
private static readonly BindableProperty TuesdayProperty = BindableProperty.Create(nameof(Tuesday), typeof(BillingDay), typeof(BillingDate));
private static readonly BindableProperty WednesdayProperty = BindableProperty.Create(nameof(Wednesday), typeof(BillingDay), typeof(BillingDate));
private static readonly BindableProperty ThursdayProperty = BindableProperty.Create(nameof(Thursday), typeof(BillingDay), typeof(BillingDate));
private static readonly BindableProperty FridayProperty = BindableProperty.Create(nameof(Friday), typeof(BillingDay), typeof(BillingDate));
private static readonly BindableProperty SaturdayProperty = BindableProperty.Create(nameof(Saturday), typeof(BillingDay), typeof(BillingDate));
public static readonly BindableProperty SundayProperty = BindableProperty.Create(nameof(Sunday), typeof(BillingDay), typeof(BillingDate));
public static readonly BindableProperty MondayProperty = BindableProperty.Create(nameof(Monday), typeof(BillingDay), typeof(BillingDate));
public static readonly BindableProperty TuesdayProperty = BindableProperty.Create(nameof(Tuesday), typeof(BillingDay), typeof(BillingDate));
public static readonly BindableProperty WednesdayProperty = BindableProperty.Create(nameof(Wednesday), typeof(BillingDay), typeof(BillingDate));
public static readonly BindableProperty ThursdayProperty = BindableProperty.Create(nameof(Thursday), typeof(BillingDay), typeof(BillingDate));
public static readonly BindableProperty FridayProperty = BindableProperty.Create(nameof(Friday), typeof(BillingDay), typeof(BillingDate));
public static readonly BindableProperty SaturdayProperty = BindableProperty.Create(nameof(Saturday), typeof(BillingDay), typeof(BillingDate));
public BillingDay Sunday => (BillingDay)GetValue(SundayProperty);
public BillingDay Monday => (BillingDay)GetValue(MondayProperty);
@ -219,12 +219,12 @@ namespace Billing.UI
public class BillingDay : BindableObject
{
private static readonly BindableProperty DateProperty = BindableProperty.Create(nameof(Date), typeof(DateTime), typeof(BillingDay), propertyChanged: OnDatePropertyChanged);
private static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(BillingDay));
private static readonly BindableProperty FontFamilyProperty = BindableProperty.Create(nameof(FontFamily), typeof(string), typeof(BillingDay), defaultValue: Definition.GetCascadiaRegularFontFamily());
private static readonly BindableProperty IsSelectedProperty = BindableProperty.Create(nameof(IsSelected), typeof(bool), typeof(BillingDay));
private static readonly BindableProperty OpacityProperty = BindableProperty.Create(nameof(Opacity), typeof(double), typeof(BillingDay), defaultValue: 1.0);
private static readonly BindableProperty TextOpacityProperty = BindableProperty.Create(nameof(TextOpacity), typeof(double), typeof(BillingDay), defaultValue: 1.0);
public static readonly BindableProperty DateProperty = BindableProperty.Create(nameof(Date), typeof(DateTime), typeof(BillingDay), propertyChanged: OnDatePropertyChanged);
public static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(BillingDay));
public static readonly BindableProperty FontFamilyProperty = BindableProperty.Create(nameof(FontFamily), typeof(string), typeof(BillingDay), defaultValue: Definition.GetCascadiaRegularFontFamily());
public static readonly BindableProperty IsSelectedProperty = BindableProperty.Create(nameof(IsSelected), typeof(bool), typeof(BillingDay));
public static readonly BindableProperty OpacityProperty = BindableProperty.Create(nameof(Opacity), typeof(double), typeof(BillingDay), defaultValue: 1.0);
public static readonly BindableProperty TextOpacityProperty = BindableProperty.Create(nameof(TextOpacity), typeof(double), typeof(BillingDay), defaultValue: 1.0);
private static void OnDatePropertyChanged(BindableObject obj, object old, object @new)
{

View File

@ -0,0 +1,159 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Xamarin.Forms;
namespace Billing.UI
{
public class GroupStackLayout : Layout<View>
{
public static readonly BindableProperty GroupHeaderTemplateProperty = BindableProperty.Create(nameof(GroupHeaderTemplate), typeof(DataTemplate), typeof(GroupStackLayout));
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(nameof(ItemTemplate), typeof(DataTemplate), typeof(GroupStackLayout));
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(GroupStackLayout), propertyChanged: OnItemsSourcePropertyChanged);
public static readonly BindableProperty SpacingProperty = BindableProperty.Create(nameof(Spacing), typeof(double), typeof(GroupStackLayout), defaultValue: 4d);
public DataTemplate GroupHeaderTemplate
{
get => (DataTemplate)GetValue(GroupHeaderTemplateProperty);
set => SetValue(GroupHeaderTemplateProperty, value);
}
public DataTemplate ItemTemplate
{
get => (DataTemplate)GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
public IList ItemsSource
{
get => (IList)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
public double Spacing
{
get => (double)GetValue(SpacingProperty);
set => SetValue(SpacingProperty, value);
}
private static void OnItemsSourcePropertyChanged(BindableObject obj, object old, object @new)
{
var stack = (GroupStackLayout)obj;
stack.lastWidth = -1;
if (@new == null)
{
stack.cachedLayout.Clear();
stack.Children.Clear();
stack.InvalidateLayout();
}
else if (@new is IList list)
{
stack.freezed = true;
stack.cachedLayout.Clear();
stack.Children.Clear();
var groupTemplate = stack.GroupHeaderTemplate;
var itemTemplate = stack.ItemTemplate;
for (var i = 0; i < list.Count; i++)
{
var item = list[i];
if (item is IList sublist)
{
if (groupTemplate != null)
{
var child = groupTemplate.CreateContent();
if (child is View view)
{
view.BindingContext = item;
stack.Children.Add(view);
}
}
foreach (var it in sublist)
{
var child = itemTemplate.CreateContent();
if (child is View view)
{
view.BindingContext = it;
stack.Children.Add(view);
}
}
}
else
{
var child = itemTemplate.CreateContent();
if (child is View view)
{
view.BindingContext = list[i];
stack.Children.Add(view);
}
}
}
stack.freezed = false;
stack.UpdateChildrenLayout();
stack.InvalidateLayout();
}
}
private readonly Dictionary<View, Rectangle> cachedLayout = new();
private bool freezed;
private double lastWidth = -1;
private SizeRequest lastSizeRequest;
public void Refresh(IList list)
{
OnItemsSourcePropertyChanged(this, null, list);
}
protected override void LayoutChildren(double x, double y, double width, double height)
{
if (freezed)
{
return;
}
var source = ItemsSource;
if (source == null || source.Count <= 0)
{
return;
}
var spacing = Spacing;
var lastHeight = 0.0;
foreach (var item in Children)
{
var measured = item.Measure(width, height, MeasureFlags.IncludeMargins);
var rect = new Rectangle(
0, lastHeight, width,
measured.Request.Height);
if (cachedLayout.TryGetValue(item, out var v))
{
if (v != rect)
{
cachedLayout[item] = rect;
item.Layout(rect);
}
}
else
{
cachedLayout.Add(item, rect);
item.Layout(rect);
}
lastHeight += rect.Height + spacing;
}
}
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
if (lastWidth == widthConstraint)
{
return lastSizeRequest;
}
lastWidth = widthConstraint;
var spacing = Spacing;
var lastHeight = 0.0;
foreach (var item in Children)
{
var measured = item.Measure(widthConstraint, heightConstraint, MeasureFlags.IncludeMargins);
lastHeight += measured.Request.Height + spacing;
}
lastSizeRequest = new SizeRequest(new Size(widthConstraint, lastHeight));
return lastSizeRequest;
}
}
}

View File

@ -0,0 +1,51 @@
using Billing.Themes;
using System.Collections;
using Xamarin.Forms;
namespace Billing.UI
{
public class ItemSelectPage : ContentPage
{
public ItemSelectPage(IList source)
{
Content = new ListView
{
ItemsSource = source,
ItemTemplate = new DataTemplate(() => new StackLayout
{
Orientation = StackOrientation.Horizontal,
Padding = new Thickness(20, 0),
Spacing = 10,
Children =
{
new Image
{
WidthRequest = 22,
HeightRequest = 22,
Aspect = Aspect.AspectFit,
VerticalOptions = LayoutOptions.Center
}
.Binding(Image.SourceProperty, "Icon"),
new Label
{
VerticalOptions = LayoutOptions.Center,
LineBreakMode = LineBreakMode.TailTruncation
}
.Binding(Label.TextProperty, "Name")
.DynamicResource(Label.TextColorProperty, BaseTheme.TextColor)
}
})
}
.DynamicResource(BackgroundColorProperty, BaseTheme.WindowBackgroundColor);
}
}
public class SelectItem<T>
{
public string Icon { get; set; }
public T Value { get; set; }
public string Name { get; set; }
}
}

View File

@ -4,7 +4,6 @@ using Xamarin.Forms;
namespace Billing.UI
{
public class OptionEntry : Entry { }
public class OptionEditor : Editor { }

View File

@ -13,7 +13,7 @@
<ContentPage.Resources>
<ui:MoneyConverter x:Key="moneyConverter"/>
<ui:MoneyConverter x:Key="money2Converter"/>
<ui:MoneyConverter x:Key="money2Converter" MarkVisible="False"/>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
@ -42,8 +42,17 @@
Text="{Binding Liability, Converter={StaticResource moneyConverter}}"/>
</Grid>
</Grid>
<CollectionView VerticalScrollBarVisibility="Never" ItemsSource="{Binding Accounts}">
<CollectionView.ItemTemplate>
<ui:GroupStackLayout x:Name="groupLayout" ItemsSource="{Binding Accounts}">
<ui:GroupStackLayout.GroupHeaderTemplate>
<DataTemplate x:DataType="v:AccountGrouping">
<StackLayout Orientation="Horizontal" Padding="10, 0">
<Label Text="{Binding Key}" TextColor="{DynamicResource SecondaryTextColor}"/>
<Label Text="{Binding Balance, Converter={StaticResource money2Converter}}"
Margin="10, 0" TextColor="{DynamicResource SecondaryTextColor}"/>
</StackLayout>
</DataTemplate>
</ui:GroupStackLayout.GroupHeaderTemplate>
<ui:GroupStackLayout.ItemTemplate>
<DataTemplate x:DataType="m:Account">
<StackLayout Orientation="Horizontal" Padding="20, 0" HeightRequest="44" Spacing="10">
<Image Source="{Binding Icon}" HeightRequest="20" VerticalOptions="Center"/>
@ -56,8 +65,8 @@
<ui:TintImage Source="right.png" HeightRequest="20"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ui:GroupStackLayout.ItemTemplate>
</ui:GroupStackLayout>
</StackLayout>
</ScrollView>
</ui:BillingPage>

View File

@ -1,4 +1,6 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Billing.Models;
using Billing.UI;
using Xamarin.Forms;
@ -10,21 +12,21 @@ namespace Billing.Views
private static readonly BindableProperty BalanceProperty = BindableProperty.Create(nameof(Balance), typeof(decimal), typeof(AccountPage));
private static readonly BindableProperty AssetProperty = BindableProperty.Create(nameof(Asset), typeof(decimal), typeof(AccountPage));
private static readonly BindableProperty LiabilityProperty = BindableProperty.Create(nameof(Liability), typeof(decimal), typeof(AccountPage));
private static readonly BindableProperty AccountsProperty = BindableProperty.Create(nameof(Accounts), typeof(ObservableCollection<Account>), typeof(AccountPage));
private static readonly BindableProperty AccountsProperty = BindableProperty.Create(nameof(Accounts), typeof(List<AccountGrouping>), typeof(AccountPage));
public decimal Balance => (decimal)GetValue(BalanceProperty);
public decimal Asset => (decimal)GetValue(AssetProperty);
public decimal Liability => (decimal)GetValue(LiabilityProperty);
public ObservableCollection<Account> Accounts => (ObservableCollection<Account>)GetValue(AccountsProperty);
public List<AccountGrouping> Accounts => (List<AccountGrouping>)GetValue(AccountsProperty);
public Command AddAccount { get; }
private readonly ObservableCollection<Account> accounts;
private readonly List<AccountGrouping> accounts;
public AccountPage()
{
AddAccount = new Command(OnAddAccount);
accounts = new ObservableCollection<Account>();
accounts = new List<AccountGrouping>();
SetValue(AccountsProperty, accounts);
InitializeComponent();
@ -47,7 +49,27 @@ namespace Billing.Views
private void AccountChecked(object sender, AccountEventArgs e)
{
Helper.Debug(e.Account.ToString());
accounts.Add(e.Account);
var group = accounts.FirstOrDefault(g => g.Key == e.Account.Category);
if (group == null)
{
group = new AccountGrouping(e.Account.Category)
{
e.Account
};
accounts.Add(group);
}
else
{
group.Add(e.Account);
}
groupLayout.Refresh(accounts);
}
}
public class AccountGrouping : List<Account>, IGrouping<AccountCategory, Account>
{
public AccountGrouping(AccountCategory key) : base() => Key = key;
public AccountCategory Key { get; }
public decimal Balance { get; set; }
}
}

View File

@ -13,7 +13,6 @@
<ContentPage.Resources>
<ui:AccountCategoryConverter x:Key="categoryConverter"/>
<ui:MoneyConverter x:Key="moneyConverter" MarkVisible="False"/>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
@ -23,14 +22,16 @@
<ContentPage.Content>
<TableView Intent="Settings" HasUnevenRows="True">
<TableSection Title=" ">
<ui:OptionEditorCell Title="{r:Text AccountName}" Height="120" FontSize="20"
Icon="pencil.png"
<ui:OptionEditorCell Height="120" Icon="pencil.png" FontSize="20" Keyboard="Text"
Title="{r:Text AccountName}"
Text="{Binding AccountName, Mode=TwoWay}"
Keyboard="Text" Placeholder="{r:Text AccountNamePlaceholder}"/>
<ui:OptionImageCell Title="{r:Text Icon}" Height="44" Icon="face.png"
Placeholder="{r:Text AccountNamePlaceholder}"/>
<ui:OptionImageCell Height="44" Icon="face.png"
Title="{r:Text Icon}"
ImageSource="{Binding AccountIcon}"
Command="{Binding SelectIcon}"/>
<ui:OptionSelectCell Title="{r:Text Category}" Height="44" Icon="project.png"
<ui:OptionSelectCell Height="44" Icon="project.png"
Title="{r:Text Category}"
Detail="{Binding Category, Converter={StaticResource categoryConverter}}"
Command="{Binding SelectCategory}"/>
</TableSection>
@ -38,20 +39,22 @@
<TableSection.Title>
<OnPlatform x:TypeArguments="x:String" Android=" "/>
</TableSection.Title>
<ui:OptionEntryCell Title="{r:Text Balance}" Height="44" Icon="sackdollar.png"
Text="{Binding Balance, Converter={StaticResource moneyConverter}}"
Keyboard="Numeric" Placeholder="{r:Text BalancePlaceholder}"
Unfocused="Balance_Unfocused"/>
<ui:OptionTextCell Title="{r:Text Currency}" Height="44" Icon="dollar.png"
<ui:OptionEntryCell Height="44" Icon="sackdollar.png" Keyboard="Numeric"
Title="{r:Text Balance}"
Text="{Binding Balance, Mode=TwoWay}"
Placeholder="{r:Text BalancePlaceholder}"/>
<ui:OptionTextCell Height="44" Icon="dollar.png"
Title="{r:Text Currency}"
Detail="{r:Text CNY}"/>
</TableSection>
<TableSection>
<TableSection.Title>
<OnPlatform x:TypeArguments="x:String" Android=" "/>
</TableSection.Title>
<ui:OptionEditorCell Title="{r:Text Memo}" Height="120" Icon="note.png"
<ui:OptionEditorCell Height="120" Icon="note.png" Keyboard="Plain"
Title="{r:Text Memo}"
Text="{Binding Memo, Mode=TwoWay}"
Keyboard="Plain" Placeholder="{r:Text MemoPlaceholder}"/>
Placeholder="{r:Text MemoPlaceholder}"/>
</TableSection>
</TableView>
</ContentPage.Content>

View File

@ -1,6 +1,8 @@
using Billing.Models;
using Billing.Languages;
using Billing.Models;
using Billing.UI;
using System;
using System.Collections.Generic;
using Xamarin.Forms;
namespace Billing.Views
@ -69,16 +71,6 @@ namespace Billing.Views
InitializeComponent();
}
private void Balance_Unfocused(object sender, FocusEventArgs e)
{
if (sender is OptionEntry entry && decimal.TryParse(entry.Text, out decimal d))
{
var converter = (MoneyConverter)Resources["moneyConverter"];
entry.Text = converter.Convert(d, null, null, null)?.ToString();
//Balance = d;
}
}
private async void OnCheckAccount()
{
if (Tap.IsBusy)
@ -108,9 +100,22 @@ namespace Billing.Views
}
private void OnSelectCategory()
private async void OnSelectCategory()
{
if (Tap.IsBusy)
{
return;
}
using (Tap.Start())
{
await Navigation.PushAsync(new ItemSelectPage(new List<SelectItem<AccountCategory>>
{
new() { Icon = "sackdollar", Value = AccountCategory.Cash, Name = Resource.Cash },
new() { Icon = "creditcard", Value = AccountCategory.CreditCard, Name = Resource.CreditCard },
new() { Icon = "", Value = AccountCategory.DebitCard, Name = Resource.DebitCard },
new() { Icon = "", Value = AccountCategory.ElecAccount, Name = Resource.ElecAccount }
}));
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B