first step

This commit is contained in:
gaoyuan 2022-03-02 22:29:13 +08:00
parent 51f8e4f2e8
commit 9929eee056
17 changed files with 343 additions and 60 deletions

View File

@ -9,6 +9,7 @@ namespace Billing.Languages
{ {
internal class Resource internal class Resource
{ {
public static string Ok => Text(nameof(Ok));
public static string TitleDateFormat => Text(nameof(TitleDateFormat)); public static string TitleDateFormat => Text(nameof(TitleDateFormat));
public static string Cash => Text(nameof(Cash)); public static string Cash => Text(nameof(Cash));
public static string CreditCard => Text(nameof(CreditCard)); public static string CreditCard => Text(nameof(CreditCard));
@ -16,6 +17,11 @@ namespace Billing.Languages
public static string ElecAccount => Text(nameof(ElecAccount)); public static string ElecAccount => Text(nameof(ElecAccount));
public static string AddBill => Text(nameof(AddBill)); public static string AddBill => Text(nameof(AddBill));
public static string EditBill => Text(nameof(EditBill)); public static string EditBill => Text(nameof(EditBill));
public static string AddAccount => Text(nameof(AddAccount));
public static string EditAccount => Text(nameof(EditAccount));
public static string AccountRequired => Text(nameof(AccountRequired));
public static string NeedAccount => Text(nameof(NeedAccount));
public static string AmountRequired => Text(nameof(AmountRequired));
static readonly Dictionary<string, LanguageResource> dict = new(); static readonly Dictionary<string, LanguageResource> dict = new();

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<Ok>OK</Ok>
<Accounts>Accounts</Accounts> <Accounts>Accounts</Accounts>
<Bills>Bills</Bills> <Bills>Bills</Bills>
<Settings>Settings</Settings> <Settings>Settings</Settings>
@ -17,8 +18,10 @@
<Assets>Assets</Assets> <Assets>Assets</Assets>
<Liability>Liability</Liability> <Liability>Liability</Liability>
<AddAccount>Add Account</AddAccount> <AddAccount>Add Account</AddAccount>
<EditAccount>Edit Account</EditAccount>
<AccountName>Account Name</AccountName> <AccountName>Account Name</AccountName>
<AccountNamePlaceholder>Please enter account name</AccountNamePlaceholder> <AccountNamePlaceholder>Please enter account name</AccountNamePlaceholder>
<AccountRequired>The account name is required.</AccountRequired>
<Icon>Icon</Icon> <Icon>Icon</Icon>
<Category>Category</Category> <Category>Category</Category>
<Balance>Balance</Balance> <Balance>Balance</Balance>
@ -34,6 +37,11 @@
<IconSelector>Icon Selection</IconSelector> <IconSelector>Icon Selection</IconSelector>
<AddBill>Add Billing</AddBill> <AddBill>Add Billing</AddBill>
<EditBill>Edit Billing</EditBill> <EditBill>Edit Billing</EditBill>
<Name>Name</Name>
<NamePlaceholder>Please enter the name</NamePlaceholder>
<Account>Account</Account> <Account>Account</Account>
<CreatedTime>Created Time</CreatedTime> <CreatedTime>Created Time</CreatedTime>
<Store>Store</Store>
<NeedAccount>Please create an account first.</NeedAccount>
<AmountRequired>Please enter the amount.</AmountRequired>
</root> </root>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<Ok>确定</Ok>
<Accounts>账户</Accounts> <Accounts>账户</Accounts>
<Bills>账单</Bills> <Bills>账单</Bills>
<Settings>设置</Settings> <Settings>设置</Settings>
@ -17,8 +18,10 @@
<Assets>资产</Assets> <Assets>资产</Assets>
<Liability>负债</Liability> <Liability>负债</Liability>
<AddAccount>新建账户</AddAccount> <AddAccount>新建账户</AddAccount>
<EditAccount>编辑账户</EditAccount>
<AccountName>账户名称</AccountName> <AccountName>账户名称</AccountName>
<AccountNamePlaceholder>请输入账户名称</AccountNamePlaceholder> <AccountNamePlaceholder>请输入账户名称</AccountNamePlaceholder>
<AccountRequired>账户名称不可为空。</AccountRequired>
<Icon>图标</Icon> <Icon>图标</Icon>
<Category>种类</Category> <Category>种类</Category>
<Balance>余额</Balance> <Balance>余额</Balance>
@ -34,6 +37,11 @@
<IconSelector>图标选择</IconSelector> <IconSelector>图标选择</IconSelector>
<AddBill>增加账单</AddBill> <AddBill>增加账单</AddBill>
<EditBill>编辑账单</EditBill> <EditBill>编辑账单</EditBill>
<Name>名称</Name>
<NamePlaceholder>请输入名称</NamePlaceholder>
<Account>账户</Account> <Account>账户</Account>
<CreatedTime>创建时间</CreatedTime> <CreatedTime>创建时间</CreatedTime>
<Store>店铺</Store>
<NeedAccount>请先创建一个资金账户。</NeedAccount>
<AmountRequired>请输入金额。</AmountRequired>
</root> </root>

View File

@ -5,31 +5,37 @@ namespace Billing.Models
{ {
public class Bill : BaseModel public class Bill : BaseModel
{ {
public int Id { get; set; }
public decimal Amount { get; set; } public decimal Amount { get; set; }
public string Name { get; set; } public string Name { get; set; }
public int CategoryId { get; set; } public int CategoryId { get; set; }
public int WalletId { get; set; } public int WalletId { get; set; }
public string Store { get; set; } public string Store { get; set; }
public DateTime CreateTime { get; set; } public DateTime CreateTime { get; set; }
public string Note { get; set; }
public override void OnXmlDeserialize(XElement node) public override void OnXmlDeserialize(XElement node)
{ {
Id = Read(node, nameof(Id), 0);
Amount = Read(node, nameof(Amount), 0m); Amount = Read(node, nameof(Amount), 0m);
Name = Read(node, nameof(Name), string.Empty); Name = Read(node, nameof(Name), string.Empty);
CategoryId = Read(node, nameof(CategoryId), -1); CategoryId = Read(node, nameof(CategoryId), -1);
WalletId = Read(node, nameof(WalletId), -1); WalletId = Read(node, nameof(WalletId), -1);
Store = Read(node, nameof(Store), string.Empty); Store = Read(node, nameof(Store), string.Empty);
CreateTime = Read(node, nameof(CreateTime), default(DateTime)); CreateTime = Read(node, nameof(CreateTime), default(DateTime));
Note = Read(node, nameof(Note), string.Empty);
} }
public override void OnXmlSerialize(XElement node) public override void OnXmlSerialize(XElement node)
{ {
Write(node, nameof(Id), Id);
Write(node, nameof(Amount), Amount); Write(node, nameof(Amount), Amount);
Write(node, nameof(Name), Name); Write(node, nameof(Name), Name);
Write(node, nameof(CategoryId), CategoryId); Write(node, nameof(CategoryId), CategoryId);
Write(node, nameof(WalletId), WalletId); Write(node, nameof(WalletId), WalletId);
Write(node, nameof(Store), Store); Write(node, nameof(Store), Store);
Write(node, nameof(CreateTime), CreateTime); Write(node, nameof(CreateTime), CreateTime);
Write(node, nameof(Note), Note);
} }
} }
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks;
using Billing.Models; using Billing.Models;
using Billing.UI; using Billing.UI;
using Xamarin.Essentials; using Xamarin.Essentials;
@ -52,7 +53,21 @@ namespace Billing.Store
private List<Category> GetCategoriesInternal() private List<Category> GetCategoriesInternal()
{ {
return GetList<Category>(Path.Combine(PersonalFolder, categoryFile)); var list = GetList<Category>(Path.Combine(PersonalFolder, categoryFile));
if (list == null || list.Count == 0)
{
list = new List<Category>
{
// TODO: sample categories
new() { Id = 0, Name = "早餐", Icon = "face" },
new() { Id = 1, Name = "轻轨", Icon = "" },
new() { Id = 2, Name = "公交车", Icon = "" },
new() { Id = 3, Name = "出租车", Icon = "" },
new() { Id = 4, Type = CategoryType.Income, Name = "投资", Icon = "#brand#btc" }
};
Task.Run(() => WriteCategoriesInternal(list));
}
return list;
} }
private void WriteCategoriesInternal(IEnumerable<Category> categories) private void WriteCategoriesInternal(IEnumerable<Category> categories)
@ -71,7 +86,7 @@ namespace Billing.Store
} }
try try
{ {
using var stream = File.OpenWrite(filename); using var stream = File.Open(filename, FileMode.Create);
list.ToStream(stream); list.ToStream(stream);
} }
catch (Exception ex) catch (Exception ex)

View File

@ -28,7 +28,7 @@ namespace Billing.Themes
Add(TextColor, Color.FromRgb(0x33, 0x33, 0x33)); Add(TextColor, Color.FromRgb(0x33, 0x33, 0x33));
Add(SecondaryTextColor, Color.DimGray); Add(SecondaryTextColor, Color.DimGray);
Add(RedColor, Color.FromRgb(211, 64, 85)); Add(RedColor, Color.FromRgb(211, 64, 85));
Add(RedColor, Color.FromRgb(64, 211, 85)); Add(GreenColor, Color.FromRgb(64, 211, 85));
Add(new Style(typeof(TabBar)) Add(new Style(typeof(TabBar))
{ {

View File

@ -1,7 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks;
using System.Xml.Linq; using System.Xml.Linq;
using Billing.Languages;
using Billing.Models; using Billing.Models;
using Xamarin.Forms; using Xamarin.Forms;
@ -77,6 +79,11 @@ namespace Billing.UI
Grid.SetRowSpan(view, rowSpan); Grid.SetRowSpan(view, rowSpan);
return view; return view;
} }
public static async Task ShowMessage(this Page page, string message, string title = null)
{
await page.DisplayAlert(title ?? page.Title, message, Resource.Ok);
}
} }
public static class ModelExtensionHelper public static class ModelExtensionHelper

View File

@ -155,7 +155,8 @@ namespace Billing.UI
itemHeight = rowHeight; itemHeight = rowHeight;
} }
var rect = new Rectangle(0, lastHeight, width, itemHeight); var rect = new Rectangle(0, lastHeight, width, itemHeight);
item.Layout(rect); //item.Layout(rect);
LayoutChildIntoBoundingRegion(item, rect);
lastHeight += itemHeight + spacing; lastHeight += itemHeight + spacing;
} }
} }

View File

@ -245,7 +245,7 @@ namespace Billing.UI
HorizontalOptions = LayoutOptions.End, HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.Center VerticalOptions = LayoutOptions.Center
} }
.Binding(Switch.IsToggledProperty, nameof(IsToggled), BindingMode.TwoWay); .Binding(Switch.IsToggledProperty, nameof(IsToggled), mode: BindingMode.TwoWay);
} }
public class OptionEntryCell : OptionCell public class OptionEntryCell : OptionCell
@ -283,7 +283,7 @@ namespace Billing.UI
VerticalOptions = LayoutOptions.Center, VerticalOptions = LayoutOptions.Center,
ReturnType = ReturnType.Next ReturnType = ReturnType.Next
} }
.Binding(Entry.TextProperty, nameof(Text), BindingMode.TwoWay) .Binding(Entry.TextProperty, nameof(Text), mode: BindingMode.TwoWay)
.Binding(InputView.KeyboardProperty, nameof(Keyboard)) .Binding(InputView.KeyboardProperty, nameof(Keyboard))
.Binding(Entry.PlaceholderProperty, nameof(Placeholder)) .Binding(Entry.PlaceholderProperty, nameof(Placeholder))
.DynamicResource(Entry.TextColorProperty, BaseTheme.TextColor) .DynamicResource(Entry.TextColorProperty, BaseTheme.TextColor)
@ -334,7 +334,7 @@ namespace Billing.UI
HorizontalOptions = LayoutOptions.Fill, HorizontalOptions = LayoutOptions.Fill,
VerticalOptions = LayoutOptions.Fill VerticalOptions = LayoutOptions.Fill
} }
.Binding(Editor.TextProperty, nameof(Text), BindingMode.TwoWay) .Binding(Editor.TextProperty, nameof(Text), mode: BindingMode.TwoWay)
.Binding(Editor.FontSizeProperty, nameof(FontSize)) .Binding(Editor.FontSizeProperty, nameof(FontSize))
.Binding(InputView.KeyboardProperty, nameof(Keyboard)) .Binding(InputView.KeyboardProperty, nameof(Keyboard))
.Binding(Editor.PlaceholderProperty, nameof(Placeholder)) .Binding(Editor.PlaceholderProperty, nameof(Placeholder))

View File

@ -19,7 +19,7 @@
</ContentPage.Resources> </ContentPage.Resources>
<ContentPage.ToolbarItems> <ContentPage.ToolbarItems>
<ToolbarItem Order="Primary" IconImageSource="plus.png" Command="{Binding AddAccount}"/> <ToolbarItem Order="Primary" IconImageSource="plus.png" Command="{Binding EditAccount}"/>
</ContentPage.ToolbarItems> </ContentPage.ToolbarItems>
<ScrollView> <ScrollView>
@ -57,17 +57,21 @@
</ui:GroupStackLayout.GroupHeaderTemplate> </ui:GroupStackLayout.GroupHeaderTemplate>
<ui:GroupStackLayout.ItemTemplate> <ui:GroupStackLayout.ItemTemplate>
<DataTemplate x:DataType="m:Account"> <DataTemplate x:DataType="m:Account">
<StackLayout Orientation="Horizontal" Padding="20, 0, 10, 0" Spacing="10"> <Grid Padding="20, 0, 10, 0" ColumnSpacing="10" ColumnDefinitions="Auto, *, Auto, Auto">
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding EditAccount, Source={x:Reference accountPage}}"
CommandParameter="{Binding .}"/>
</Grid.GestureRecognizers>
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}" <ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/> WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
<Label Text="{Binding Name}" TextColor="{DynamicResource TextColor}" <Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
HorizontalOptions="FillAndExpand" VerticalOptions="Center" HorizontalOptions="FillAndExpand" VerticalOptions="Center"
FontSize="Default" FontAttributes="Bold"/> FontSize="Default" FontAttributes="Bold"/>
<Label Text="{Binding Balance, Converter={StaticResource money2Converter}}" <Label Grid.Column="2" Text="{Binding Balance, Converter={StaticResource money2Converter}}"
TextColor="{DynamicResource SecondaryTextColor}" TextColor="{DynamicResource SecondaryTextColor}"
VerticalOptions="Center"/> VerticalOptions="Center"/>
<ui:TintImage Source="right.png" HeightRequest="20"/> <ui:TintImage Grid.Column="3" Source="right.png" HeightRequest="20"/>
</StackLayout> </Grid>
</DataTemplate> </DataTemplate>
</ui:GroupStackLayout.ItemTemplate> </ui:GroupStackLayout.ItemTemplate>
</ui:GroupStackLayout> </ui:GroupStackLayout>

View File

@ -19,14 +19,14 @@ namespace Billing.Views
public decimal Liability => (decimal)GetValue(LiabilityProperty); public decimal Liability => (decimal)GetValue(LiabilityProperty);
public List<AccountGrouping> Accounts => (List<AccountGrouping>)GetValue(AccountsProperty); public List<AccountGrouping> Accounts => (List<AccountGrouping>)GetValue(AccountsProperty);
public Command AddAccount { get; } public Command EditAccount { get; }
private readonly List<AccountGrouping> accounts = new(); private readonly List<AccountGrouping> accounts = new();
private bool initialized; private bool initialized;
public AccountPage() public AccountPage()
{ {
AddAccount = new Command(OnAddAccount); EditAccount = new Command(OnEditAccount);
SetValue(AccountsProperty, accounts); SetValue(AccountsProperty, accounts);
InitializeComponent(); InitializeComponent();
@ -40,9 +40,17 @@ namespace Billing.Views
accounts.Clear(); accounts.Clear();
foreach (var account in App.Accounts) foreach (var account in App.Accounts)
{ {
account.Balance = account.Initial + App.Bills.Where(b => b.WalletId == account.Id).Sum(b => b.Amount);
AddToAccountGroup(account); AddToAccountGroup(account);
} }
} }
else
{
foreach (var account in App.Accounts)
{
account.Balance = account.Initial + App.Bills.Where(b => b.WalletId == account.Id).Sum(b => b.Amount);
}
}
groupLayout.Refresh(accounts); groupLayout.Refresh(accounts);
} }
@ -72,7 +80,7 @@ namespace Billing.Views
} }
} }
private async void OnAddAccount() private async void OnEditAccount(object o)
{ {
if (Tap.IsBusy) if (Tap.IsBusy)
{ {
@ -80,7 +88,7 @@ namespace Billing.Views
} }
using (Tap.Start()) using (Tap.Start())
{ {
var page = new AddAccountPage(); var page = new AddAccountPage(o as Account);
page.AccountChecked += AccountChecked; page.AccountChecked += AccountChecked;
await Navigation.PushAsync(page); await Navigation.PushAsync(page);
} }
@ -88,8 +96,11 @@ namespace Billing.Views
private void AccountChecked(object sender, AccountEventArgs e) private void AccountChecked(object sender, AccountEventArgs e)
{ {
App.Accounts.Add(e.Account); if (e.Account.Id < 0)
AddToAccountGroup(e.Account); {
App.Accounts.Add(e.Account);
AddToAccountGroup(e.Account);
}
groupLayout.Refresh(accounts); groupLayout.Refresh(accounts);
Task.Run(App.WriteAccounts); Task.Run(App.WriteAccounts);

View File

@ -7,7 +7,6 @@
x:Class="Billing.Views.AddAccountPage" x:Class="Billing.Views.AddAccountPage"
x:Name="addAccountPage" x:Name="addAccountPage"
x:DataType="v:AddAccountPage" x:DataType="v:AddAccountPage"
Title="{r:Text AddAccount}"
BindingContext="{x:Reference addAccountPage}"> BindingContext="{x:Reference addAccountPage}">
<ContentPage.Resources> <ContentPage.Resources>

View File

@ -49,29 +49,27 @@ namespace Billing.Views
public event EventHandler<AccountEventArgs> AccountChecked; public event EventHandler<AccountEventArgs> AccountChecked;
public AddAccountPage() public AddAccountPage(Account account = null)
{
CheckAccount = new Command(OnCheckAccount);
SelectIcon = new Command(OnSelectIcon);
SelectCategory = new Command(OnSelectCategory);
AccountIcon = BaseModel.ICON_DEFAULT;
Category = AccountCategory.Cash;
InitializeComponent();
}
public AddAccountPage(Account account)
{ {
CheckAccount = new Command(OnCheckAccount); CheckAccount = new Command(OnCheckAccount);
SelectIcon = new Command(OnSelectIcon); SelectIcon = new Command(OnSelectIcon);
SelectCategory = new Command(OnSelectCategory); SelectCategory = new Command(OnSelectCategory);
Title = Resource.EditAccount;
this.account = account; this.account = account;
AccountName = account.Name; if (account == null)
AccountIcon = account.Icon; {
Category = account.Category; AccountIcon = BaseModel.ICON_DEFAULT;
Initial = account.Initial.ToString(); Category = AccountCategory.Cash;
Memo = account.Memo; }
else
{
AccountName = account.Name;
AccountIcon = account.Icon;
Category = account.Category;
Initial = account.Initial.ToString();
Memo = account.Memo;
}
InitializeComponent(); InitializeComponent();
} }
@ -83,13 +81,26 @@ namespace Billing.Views
} }
using (Tap.Start()) using (Tap.Start())
{ {
if (string.IsNullOrWhiteSpace(AccountName))
{
await this.ShowMessage(Resource.AccountRequired);
return;
}
await Navigation.PopAsync(); await Navigation.PopAsync();
_ = decimal.TryParse(Initial, out decimal initial); _ = decimal.TryParse(Initial, out decimal initial);
if (account != null)
{
account.Name = AccountName;
account.Icon = AccountIcon;
account.Category = Category;
account.Initial = initial;
account.Memo = Memo;
}
AccountChecked?.Invoke(this, new AccountEventArgs AccountChecked?.Invoke(this, new AccountEventArgs
{ {
Account = new Account Account = account ?? new Account
{ {
Id = account?.Id ?? -1, Id = -1,
Name = AccountName, Name = AccountName,
Icon = AccountIcon, Icon = AccountIcon,
Category = Category, Category = Category,

View File

@ -16,27 +16,32 @@
<ContentPage.Content> <ContentPage.Content>
<TableView Intent="Settings" HasUnevenRows="True"> <TableView Intent="Settings" HasUnevenRows="True">
<TableSection Title=" "> <TableSection Title=" ">
<ui:OptionEditorCell Height="120" Icon="pencil.png" FontSize="20" Keyboard="Text" <ui:OptionEditorCell Height="120" Icon="yuan.png" FontSize="20" Keyboard="Numeric"
Title="{r:Text Account}" Text="{Binding Amount, Mode=TwoWay}"
Text="{Binding AccountName, Mode=TwoWay}"
Placeholder="0.00"/> Placeholder="0.00"/>
</TableSection> </TableSection>
<TableSection> <TableSection>
<TableSection.Title> <TableSection.Title>
<OnPlatform x:TypeArguments="x:String" Android=" "/> <OnPlatform x:TypeArguments="x:String" Android=" "/>
</TableSection.Title> </TableSection.Title>
<ui:OptionEntryCell Height="44" Icon="pencil.png"
Title="{r:Text Name}"
Text="{Binding Name, Mode=TwoWay}"
Placeholder="{r:Text NamePlaceholder}"/>
<ui:OptionSelectCell Height="44" Icon="project.png" <ui:OptionSelectCell Height="44" Icon="project.png"
Title="{r:Text Category}" Title="{r:Text Category}"
Detail="{Binding Category, Converter={StaticResource categoryConverter}}" Detail="{Binding CategoryName}"
Command="{Binding SelectCategory}"/> Command="{Binding SelectCategory}"/>
<ui:OptionSelectCell Height="44" Icon="project.png" <ui:OptionSelectCell Height="44" Icon="wallet.png"
Title="{r:Text Account}" Title="{r:Text Account}"
Detail="{Binding Category, Converter={StaticResource categoryConverter}}" Detail="{Binding WalletName}"
Command="{Binding SelectCategory}"/> Command="{Binding SelectWallet}"/>
<ui:OptionSelectCell Height="44" Icon="project.png" <ui:OptionEntryCell Height="44" Icon="online.png"
Title="{r:Text Store}"
Text="{Binding Store, Mode=TwoWay}"/>
<ui:OptionSelectCell Height="44" Icon="bars.png"
Title="{r:Text CreatedTime}" Title="{r:Text CreatedTime}"
Detail="{Binding Category, Converter={StaticResource categoryConverter}}" Detail="{Binding CreatedTime}"/>
Command="{Binding SelectCategory}"/>
</TableSection> </TableSection>
<TableSection> <TableSection>
<TableSection.Title> <TableSection.Title>
@ -44,7 +49,7 @@
</TableSection.Title> </TableSection.Title>
<ui:OptionEditorCell Height="120" Icon="note.png" Keyboard="Plain" <ui:OptionEditorCell Height="120" Icon="note.png" Keyboard="Plain"
Title="{r:Text Memo}" Title="{r:Text Memo}"
Text="{Binding Memo, Mode=TwoWay}" Text="{Binding Note, Mode=TwoWay}"
Placeholder="{r:Text MemoPlaceholder}"/> Placeholder="{r:Text MemoPlaceholder}"/>
</TableSection> </TableSection>
</TableView> </TableView>

View File

@ -1,4 +1,6 @@
using System; using System;
using System.Globalization;
using System.Linq;
using Billing.Languages; using Billing.Languages;
using Billing.Models; using Billing.Models;
using Billing.UI; using Billing.UI;
@ -8,28 +10,158 @@ namespace Billing.Views
{ {
public partial class AddBillPage : BillingPage public partial class AddBillPage : BillingPage
{ {
private static readonly BindableProperty AmountProperty = BindableProperty.Create(nameof(Amount), typeof(string), typeof(AddBillPage));
private static readonly BindableProperty NameProperty = BindableProperty.Create(nameof(Name), typeof(string), typeof(AddBillPage));
private static readonly BindableProperty CategoryNameProperty = BindableProperty.Create(nameof(CategoryName), typeof(string), typeof(AddBillPage));
private static readonly BindableProperty WalletNameProperty = BindableProperty.Create(nameof(WalletName), typeof(string), typeof(AddBillPage));
private static readonly BindableProperty StoreProperty = BindableProperty.Create(nameof(Store), typeof(string), typeof(AddBillPage));
private static readonly BindableProperty CreatedTimeProperty = BindableProperty.Create(nameof(CreatedTime), typeof(DateTime), typeof(AddBillPage));
private static readonly BindableProperty NoteProperty = BindableProperty.Create(nameof(Note), typeof(string), typeof(AddBillPage));
public string Amount
{
get => (string)GetValue(AmountProperty);
set => SetValue(AmountProperty, value);
}
public string Name
{
get => (string)GetValue(NameProperty);
set => SetValue(NameProperty, value);
}
public string CategoryName => (string)GetValue(CategoryNameProperty);
public string WalletName => (string)GetValue(WalletNameProperty);
public string Store
{
get => (string)GetValue(StoreProperty);
set => SetValue(StoreProperty, value);
}
public DateTime CreatedTime
{
get => (DateTime)GetValue(CreatedTimeProperty);
set => SetValue(CreatedTimeProperty, value);
}
public string Note
{
get => (string)GetValue(NoteProperty);
set => SetValue(NoteProperty, value);
}
public Command CheckBill { get; } public Command CheckBill { get; }
public Command SelectCategory { get; }
public Command SelectWallet { get; }
public event EventHandler<Bill> BillChecked;
private readonly Bill bill; private readonly Bill bill;
private readonly DateTime createDate; private readonly DateTime createDate;
private int walletId;
private int categoryId;
public AddBillPage(DateTime date) public AddBillPage(DateTime date)
{ {
createDate = date; createDate = date;
CheckBill = new Command(OnCheckBill); CheckBill = new Command(OnCheckBill);
SelectCategory = new Command(OnSelectCategory);
SelectWallet = new Command(OnSelectWallet);
InitializeComponent(); InitializeComponent();
Title = Resource.AddBill; Title = Resource.AddBill;
Initial();
} }
public AddBillPage(Bill bill) public AddBillPage(Bill bill)
{ {
this.bill = bill; this.bill = bill;
CheckBill = new Command(OnCheckBill); CheckBill = new Command(OnCheckBill);
SelectCategory = new Command(OnSelectCategory);
SelectWallet = new Command(OnSelectWallet);
InitializeComponent(); InitializeComponent();
Title = Resource.EditBill; Title = Resource.EditBill;
Initial();
} }
private void OnCheckBill() private void Initial()
{
if (bill != null)
{
Amount = bill.Amount.ToString(CultureInfo.InvariantCulture);
Name = bill.Name;
walletId = bill.WalletId;
categoryId = bill.CategoryId;
SetValue(WalletNameProperty, App.Accounts.FirstOrDefault(a => a.Id == walletId)?.Name);
SetValue(CategoryNameProperty, App.Categories.FirstOrDefault(c => c.Id == categoryId)?.Name);
Store = bill.Store;
CreatedTime = bill.CreateTime;
Note = bill.Note;
}
else
{
var first = App.Accounts.First();
walletId = first.Id;
SetValue(WalletNameProperty, first.Name);
var firstCategory = App.Categories.First();
categoryId = firstCategory.Id;
SetValue(CategoryNameProperty, firstCategory.Name);
CreatedTime = createDate.Date.Add(DateTime.Now.TimeOfDay);
}
}
private async void OnCheckBill()
{
if (Tap.IsBusy)
{
return;
}
using (Tap.Start())
{
if (!decimal.TryParse(Amount, out decimal amount))
{
return;
}
if (amount == 0)
{
await this.ShowMessage(Resource.AmountRequired);
return;
}
amount = Math.Abs(amount);
var category = App.Categories.FirstOrDefault(c => c.Id == categoryId);
if (category.Type == CategoryType.Spending)
{
amount *= -1;
}
await Navigation.PopAsync();
if (bill != null)
{
bill.Amount = amount;
bill.Name = Name;
bill.CategoryId = categoryId;
bill.WalletId = walletId;
bill.CreateTime = CreatedTime;
bill.Store = Store;
bill.Note = Note;
}
BillChecked?.Invoke(this, bill ?? new Bill
{
Id = -1,
Amount = amount,
Name = Name,
CategoryId = categoryId,
WalletId = walletId,
CreateTime = CreatedTime,
Store = Store,
Note = Note
});
}
}
private void OnSelectCategory()
{
}
private void OnSelectWallet()
{ {
} }

View File

@ -30,6 +30,10 @@
</Grid> </Grid>
</Shell.TitleView> </Shell.TitleView>
<ContentPage.ToolbarItems>
<ToolbarItem Order="Primary" IconImageSource="plus.png" Command="{Binding EditBilling}"/>
</ContentPage.ToolbarItems>
<Grid RowDefinitions="Auto, Auto, *"> <Grid RowDefinitions="Auto, Auto, *">
<ui:BillingDate x:Name="billingDate" SelectedDate="{Binding SelectedDate}" DateSelected="OnDateSelected"/> <ui:BillingDate x:Name="billingDate" SelectedDate="{Binding SelectedDate}" DateSelected="OnDateSelected"/>
<Grid Grid.Row="1" Padding="8" ColumnSpacing="8" ColumnDefinitions="Auto, *, Auto" <Grid Grid.Row="1" Padding="8" ColumnSpacing="8" ColumnDefinitions="Auto, *, Auto"
@ -39,7 +43,7 @@
VerticalOptions="Center"/> VerticalOptions="Center"/>
<StackLayout Grid.Column="2" Orientation="Horizontal" Spacing="6"> <StackLayout Grid.Column="2" Orientation="Horizontal" Spacing="6">
<StackLayout.GestureRecognizers> <StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding AddBilling}"/> <TapGestureRecognizer Command="{Binding EditBilling}"/>
</StackLayout.GestureRecognizers> </StackLayout.GestureRecognizers>
<Label Text="{r:Text TapToMemo}" TextColor="{DynamicResource PrimaryColor}" <Label Text="{r:Text TapToMemo}" TextColor="{DynamicResource PrimaryColor}"
VerticalOptions="Center"/> VerticalOptions="Center"/>
@ -48,11 +52,16 @@
</Grid> </Grid>
<!-- bill list --> <!-- bill list -->
<ScrollView Grid.Row="2"> <ScrollView Grid.Row="2">
<ui:GroupStackLayout x:Name="billsLayout" ItemsSource="{Binding Bills}" Margin="0, 10, 0, 0"> <ui:GroupStackLayout x:Name="billsLayout" ItemsSource="{Binding Bills}"
Margin="0, 10, 0, 0" RowHeight="50">
<ui:GroupStackLayout.ItemTemplate> <ui:GroupStackLayout.ItemTemplate>
<DataTemplate x:DataType="v:UIBill"> <DataTemplate x:DataType="v:UIBill">
<Grid Padding="20, 0, 10, 0" ColumnSpacing="10" <Grid Padding="20, 0, 10, 0" ColumnSpacing="10"
ColumnDefinitions="Auto, *, Auto" RowDefinitions="Auto, Auto"> ColumnDefinitions="Auto, *, Auto" RowDefinitions="Auto, Auto">
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding EditBilling, Source={x:Reference billPage}}"
CommandParameter="{Binding .}"/>
</Grid.GestureRecognizers>
<ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}" <ui:TintImage Source="{Binding Icon, Converter={StaticResource iconConverter}}"
WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/> WidthRequest="26" HeightRequest="20" VerticalOptions="Center"/>
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}" <Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
@ -62,7 +71,7 @@
TextColor="{Binding Amount, Converter={StaticResource colorConverter}}" TextColor="{Binding Amount, Converter={StaticResource colorConverter}}"
VerticalOptions="Center"/> VerticalOptions="Center"/>
<Label Grid.Row="1" Grid.Column="1" Text="{Binding ., Converter={StaticResource billConverter}}" <Label Grid.Row="1" Grid.Column="1" Text="{Binding ., Converter={StaticResource billConverter}}"
FontSize="Small" FontSize="10"
TextColor="{DynamicResource SecondaryTextColor}"/> TextColor="{DynamicResource SecondaryTextColor}"/>
</Grid> </Grid>
</DataTemplate> </DataTemplate>

View File

@ -1,8 +1,10 @@
using Billing.Languages;
using Billing.Models; using Billing.Models;
using Billing.UI; using Billing.UI;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Xamarin.Forms; using Xamarin.Forms;
namespace Billing.Views namespace Billing.Views
@ -23,11 +25,11 @@ namespace Billing.Views
set => SetValue(BillsProperty, value); set => SetValue(BillsProperty, value);
} }
public Command AddBilling { get; } public Command EditBilling { get; }
public BillPage() public BillPage()
{ {
AddBilling = new Command(OnAddBilling); EditBilling = new Command(OnEditBilling);
InitializeComponent(); InitializeComponent();
@ -42,14 +44,28 @@ namespace Billing.Views
b.CreateTime.Year == e.Date.Year && b.CreateTime.Year == e.Date.Year &&
b.CreateTime.Month == e.Date.Month && b.CreateTime.Month == e.Date.Month &&
b.CreateTime.Day == e.Date.Day); b.CreateTime.Day == e.Date.Day);
Bills = new List<UIBill>(bills.Select(b => new UIBill(b) Bills = new List<UIBill>(bills.Select(b => WrapBill(b)));
}
private UIBill WrapBill(Bill b)
{
return new UIBill(b)
{ {
Icon = App.Categories.FirstOrDefault(c => c.Id == b.CategoryId)?.Icon ?? BaseModel.ICON_DEFAULT, Icon = App.Categories.FirstOrDefault(c => c.Id == b.CategoryId)?.Icon ?? BaseModel.ICON_DEFAULT,
Name = b.Name, Name = b.Name,
DateCreation = b.CreateTime, DateCreation = b.CreateTime,
Amount = b.Amount, Amount = b.Amount,
Wallet = App.Accounts.FirstOrDefault(a => a.Id == b.WalletId)?.Name Wallet = App.Accounts.FirstOrDefault(a => a.Id == b.WalletId)?.Name
})); };
}
private void UpdateBill(UIBill bill)
{
bill.Icon = App.Categories.FirstOrDefault(c => c.Id == bill.Bill.CategoryId)?.Icon ?? BaseModel.ICON_DEFAULT;
bill.Name = bill.Bill.Name;
bill.DateCreation = bill.Bill.CreateTime;
bill.Amount = bill.Bill.Amount;
bill.Wallet = App.Accounts.FirstOrDefault(a => a.Id == bill.Bill.WalletId)?.Name;
} }
private void OnTitleDateLongPressed(object sender, EventArgs e) private void OnTitleDateLongPressed(object sender, EventArgs e)
@ -57,7 +73,7 @@ namespace Billing.Views
billingDate.SetDateTime(DateTime.Now); billingDate.SetDateTime(DateTime.Now);
} }
private async void OnAddBilling() private async void OnEditBilling(object o)
{ {
if (Tap.IsBusy) if (Tap.IsBusy)
{ {
@ -65,10 +81,55 @@ namespace Billing.Views
} }
using (Tap.Start()) using (Tap.Start())
{ {
var page = new AddBillPage(SelectedDate); AddBillPage page;
if (o is UIBill bill)
{
page = new AddBillPage(bill.Bill);
}
else
{
if (App.Accounts.Count == 0)
{
await this.ShowMessage(Resource.NeedAccount);
await Shell.Current.GoToAsync("//Accounts");
return;
}
page = new AddBillPage(SelectedDate);
}
page.BillChecked += OnBillChecked;
await Navigation.PushAsync(page); await Navigation.PushAsync(page);
} }
} }
private void OnBillChecked(object sender, Bill e)
{
if (e.Id < 0)
{
int maxId;
if (App.Bills.Count > 0)
{
maxId = App.Bills.Max(b => b.Id);
}
else
{
maxId = -1;
}
e.Id = maxId + 1;
App.Bills.Add(e);
Bills.Add(WrapBill(e));
billsLayout.Refresh(Bills);
}
else
{
var bill = Bills.FirstOrDefault(b => b.Bill == e);
if (bill != null)
{
UpdateBill(bill);
}
}
Task.Run(App.WriteBills);
}
} }
public class UIBill : BindableObject public class UIBill : BindableObject