first test-flight version

This commit is contained in:
2022-03-03 15:10:36 +08:00
parent 9929eee056
commit 25191127f3
116 changed files with 1124 additions and 173 deletions

View File

@ -13,7 +13,6 @@
<ContentPage.Resources>
<ui:MoneyConverter x:Key="moneyConverter"/>
<ui:MoneyConverter x:Key="money2Converter" MarkVisible="False"/>
<ui:AccountCategoryConverter x:Key="categoryConverter"/>
<ui:IconConverter x:Key="iconConverter"/>
</ContentPage.Resources>
@ -34,7 +33,7 @@
Text="{Binding Balance, Converter={StaticResource moneyConverter}}"/>
</StackLayout>
<Grid Grid.Column="1" Margin="20, 0" VerticalOptions="Center" HorizontalOptions="End"
ColumnDefinitions="Auto, Auto" RowDefinitions="Auto, Auto">
ColumnDefinitions="Auto, Auto" RowDefinitions="Auto, Auto" IsVisible="False">
<Label FontSize="Small" HorizontalOptions="End" Text="{r:Text Assets}"/>
<Label Grid.Column="1" FontSize="Small" Margin="10, 0, 0, 0" HorizontalOptions="End"
Text="{Binding Asset, Converter={StaticResource moneyConverter}}"/>
@ -44,20 +43,22 @@
Text="{Binding Liability, Converter={StaticResource moneyConverter}}"/>
</Grid>
</Grid>
<ui:GroupStackLayout x:Name="groupLayout" ItemsSource="{Binding Accounts}" Margin="0, 10, 0, 0">
<ui:GroupStackLayout x:Name="groupLayout" ItemsSource="{Binding Accounts}" Margin="0, 10, 0, 0" GroupHeight="40">
<ui:GroupStackLayout.GroupHeaderTemplate>
<DataTemplate x:DataType="v:AccountGrouping">
<StackLayout Orientation="Horizontal" Padding="10, 0">
<StackLayout Orientation="Horizontal" Padding="10, 0" VerticalOptions="End">
<Label Text="{Binding Key, Converter={StaticResource categoryConverter}}"
TextColor="{DynamicResource SecondaryTextColor}"/>
<Label Text="{Binding Balance, Converter={StaticResource money2Converter}}"
<Label Text="{Binding Balance, Converter={StaticResource moneyConverter}}"
Margin="10, 0" TextColor="{DynamicResource SecondaryTextColor}"/>
</StackLayout>
</DataTemplate>
</ui:GroupStackLayout.GroupHeaderTemplate>
<ui:GroupStackLayout.ItemTemplate>
<DataTemplate x:DataType="m:Account">
<Grid Padding="20, 0, 10, 0" ColumnSpacing="10" ColumnDefinitions="Auto, *, Auto, Auto">
<ui:LongPressGrid Padding="20, 0, 10, 0" ColumnSpacing="10" ColumnDefinitions="Auto, *, Auto, Auto"
LongCommand="{Binding DeleteAccount, Source={x:Reference accountPage}}"
LongCommandParameter="{Binding .}">
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding EditAccount, Source={x:Reference accountPage}}"
CommandParameter="{Binding .}"/>
@ -67,11 +68,11 @@
<Label Grid.Column="1" Text="{Binding Name}" TextColor="{DynamicResource TextColor}"
HorizontalOptions="FillAndExpand" VerticalOptions="Center"
FontSize="Default" FontAttributes="Bold"/>
<Label Grid.Column="2" Text="{Binding Balance, Converter={StaticResource money2Converter}}"
<Label Grid.Column="2" Text="{Binding Balance, Converter={StaticResource moneyConverter}}"
TextColor="{DynamicResource SecondaryTextColor}"
VerticalOptions="Center"/>
<ui:TintImage Grid.Column="3" Source="right.png" HeightRequest="20"/>
</Grid>
</ui:LongPressGrid>
</DataTemplate>
</ui:GroupStackLayout.ItemTemplate>
</ui:GroupStackLayout>

View File

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Billing.Languages;
using Billing.Models;
using Billing.UI;
using Xamarin.Forms;
@ -20,6 +21,7 @@ namespace Billing.Views
public List<AccountGrouping> Accounts => (List<AccountGrouping>)GetValue(AccountsProperty);
public Command EditAccount { get; }
public Command DeleteAccount { get; }
private readonly List<AccountGrouping> accounts = new();
private bool initialized;
@ -27,6 +29,7 @@ namespace Billing.Views
public AccountPage()
{
EditAccount = new Command(OnEditAccount);
DeleteAccount = new Command(OnDeleteAccount);
SetValue(AccountsProperty, accounts);
InitializeComponent();
@ -34,7 +37,7 @@ namespace Billing.Views
protected override void OnAppearing()
{
if (!initialized)
if (!initialized || accounts.Count == 0)
{
initialized = true;
accounts.Clear();
@ -52,6 +55,12 @@ namespace Billing.Views
}
}
groupLayout.Refresh(accounts);
RefreshBalance();
}
private void RefreshBalance()
{
SetValue(BalanceProperty, App.Accounts.Sum(a => a.Balance));
}
private void AddToAccountGroup(Account account)
@ -94,6 +103,35 @@ namespace Billing.Views
}
}
private async void OnDeleteAccount(object o)
{
if (Tap.IsBusy)
{
return;
}
using (Tap.Start())
{
if (o is Account account)
{
var result = await this.ShowConfirm(Resource.ConfirmDeleteAccount);
if (result)
{
var group = accounts.FirstOrDefault(a => a.Key == account.Category);
if (group == null)
{
Helper.Error("account.delete", "unexpected deleting account, cannot find the current category");
return;
}
group.Remove(account);
groupLayout.Refresh(accounts);
RefreshBalance();
_ = Task.Run(App.WriteAccounts);
}
}
}
}
private void AccountChecked(object sender, AccountEventArgs e)
{
if (e.Account.Id < 0)
@ -102,6 +140,7 @@ namespace Billing.Views
AddToAccountGroup(e.Account);
}
groupLayout.Refresh(accounts);
RefreshBalance();
Task.Run(App.WriteAccounts);
}

View File

@ -152,10 +152,9 @@ namespace Billing.Views
}
}
private async void Category_ItemTapped(object sender, SelectItem<AccountCategory> e)
private void Category_ItemTapped(object sender, SelectItem<AccountCategory> e)
{
Category = e.Value;
await Navigation.PopAsync();
}
}

View File

@ -39,9 +39,11 @@
<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}"
Detail="{Binding CreatedTime}"/>
<ui:OptionDatePickerCell Height="44" Icon="bars.png"
Title="{r:Text CreatedTime}"
Date="{Binding CreatedDate, Mode=TwoWay}"/>
<ui:OptionTimePickerCell Height="44"
Time="{Binding CreatedTime, Mode=TwoWay}"/>
</TableSection>
<TableSection>
<TableSection.Title>

View File

@ -15,7 +15,8 @@ namespace Billing.Views
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 CreatedDateProperty = BindableProperty.Create(nameof(CreatedDate), typeof(DateTime), typeof(AddBillPage));
private static readonly BindableProperty CreatedTimeProperty = BindableProperty.Create(nameof(CreatedTime), typeof(TimeSpan), typeof(AddBillPage));
private static readonly BindableProperty NoteProperty = BindableProperty.Create(nameof(Note), typeof(string), typeof(AddBillPage));
public string Amount
@ -35,9 +36,14 @@ namespace Billing.Views
get => (string)GetValue(StoreProperty);
set => SetValue(StoreProperty, value);
}
public DateTime CreatedTime
public DateTime CreatedDate
{
get => (DateTime)GetValue(CreatedTimeProperty);
get => (DateTime)GetValue(CreatedDateProperty);
set => SetValue(CreatedDateProperty, value);
}
public TimeSpan CreatedTime
{
get => (TimeSpan)GetValue(CreatedTimeProperty);
set => SetValue(CreatedTimeProperty, value);
}
public string Note
@ -86,14 +92,15 @@ namespace Billing.Views
{
if (bill != null)
{
Amount = bill.Amount.ToString(CultureInfo.InvariantCulture);
Amount = Math.Abs(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;
CreatedDate = bill.CreateTime.Date;
CreatedTime = bill.CreateTime.TimeOfDay;
Note = bill.Note;
}
else
@ -104,7 +111,8 @@ namespace Billing.Views
var firstCategory = App.Categories.First();
categoryId = firstCategory.Id;
SetValue(CategoryNameProperty, firstCategory.Name);
CreatedTime = createDate.Date.Add(DateTime.Now.TimeOfDay);
CreatedDate = createDate.Date;
CreatedTime = DateTime.Now.TimeOfDay;
}
}
@ -132,13 +140,18 @@ namespace Billing.Views
amount *= -1;
}
await Navigation.PopAsync();
var name = Name;
if (string.IsNullOrWhiteSpace(name))
{
name = category.Name;
}
if (bill != null)
{
bill.Amount = amount;
bill.Name = Name;
bill.Name = name;
bill.CategoryId = categoryId;
bill.WalletId = walletId;
bill.CreateTime = CreatedTime;
bill.CreateTime = CreatedDate.Date.Add(CreatedTime);
bill.Store = Store;
bill.Note = Note;
}
@ -146,24 +159,66 @@ namespace Billing.Views
{
Id = -1,
Amount = amount,
Name = Name,
Name = name,
CategoryId = categoryId,
WalletId = walletId,
CreateTime = CreatedTime,
CreateTime = CreatedDate.Date.Add(CreatedTime),
Store = Store,
Note = Note
});
}
}
private void OnSelectCategory()
private async void OnSelectCategory()
{
}
private void OnSelectWallet()
if (Tap.IsBusy)
{
return;
}
using (Tap.Start())
{
var source = App.Categories.Select(c => new SelectItem<int>
{
Value = c.Id,
Name = c.Name,
Icon = c.Icon
});
var page = new ItemSelectPage<SelectItem<int>>(source);
page.ItemTapped += Category_ItemTapped;
await Navigation.PushAsync(page);
}
}
private void Category_ItemTapped(object sender, SelectItem<int> category)
{
categoryId = category.Value;
SetValue(CategoryNameProperty, category.Name);
}
private async void OnSelectWallet()
{
}
if (Tap.IsBusy)
{
return;
}
using (Tap.Start())
{
var source = App.Accounts.Select(a => new SelectItem<int>
{
Value = a.Id,
Name = a.Name,
Icon = a.Icon
});
var page = new ItemSelectPage<SelectItem<int>>(source);
page.ItemTapped += Wallet_ItemTapped;
await Navigation.PushAsync(page);
}
}
private void Wallet_ItemTapped(object sender, SelectItem<int> account)
{
walletId = account.Value;
SetValue(WalletNameProperty, account.Name);
}
}
}

View File

@ -12,9 +12,11 @@
<ContentPage.Resources>
<ui:TitleDateConverter x:Key="titleDateConverter"/>
<ui:MoneyConverter x:Key="moneyConverter" MarkVisible="False" Absolute="True"/>
<ui:NegativeConverter x:Key="negativeConverter"/>
<ui:MoneyConverter x:Key="moneyConverter" Absolute="True"/>
<ui:MoneyConverter x:Key="moneyRawConverter"/>
<ui:BalanceColorConverter x:Key="colorConverter"/>
<ui:UIBillConverter x:Key="billConverter"/>
<ui:TimeConverter x:Key="timeConverter"/>
<ui:IconConverter x:Key="iconConverter"/>
</ContentPage.Resources>
@ -36,28 +38,51 @@
<Grid RowDefinitions="Auto, Auto, *">
<ui:BillingDate x:Name="billingDate" SelectedDate="{Binding SelectedDate}" DateSelected="OnDateSelected"/>
<Grid Grid.Row="1" Padding="8" ColumnSpacing="8" ColumnDefinitions="Auto, *, Auto"
BackgroundColor="{DynamicResource PromptBackgroundColor}">
<ui:TintImage Source="bars.png" WidthRequest="23" HeightRequest="23"/>
<Label Grid.Column="1" Text="{r:Text NoRecords}" TextColor="{DynamicResource TextColor}"
VerticalOptions="Center"/>
<StackLayout Grid.Column="2" Orientation="Horizontal" Spacing="6">
<Grid Grid.Row="1" Padding="8" ColumnSpacing="8" ColumnDefinitions="*, Auto"
BackgroundColor="{DynamicResource PromptBackgroundColor}"
IsVisible="{Binding NoBills}">
<Label Text="{r:Text NoRecords}" TextColor="{DynamicResource TextColor}"
VerticalOptions="Center" FontSize="12"/>
<StackLayout Grid.Column="1" Orientation="Horizontal" Spacing="6">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding EditBilling}"/>
</StackLayout.GestureRecognizers>
<Label Text="{r:Text TapToMemo}" TextColor="{DynamicResource PrimaryColor}"
VerticalOptions="Center"/>
VerticalOptions="Center" FontSize="12"/>
<ui:TintImage Source="right.png" WidthRequest="24" HeightRequest="24"/>
</StackLayout>
</Grid>
<Grid Grid.Row="1" Padding="8" ColumnSpacing="8" ColumnDefinitions="*, Auto" HeightRequest="24"
BackgroundColor="{DynamicResource PromptBackgroundColor}"
IsVisible="{Binding NoBills, Converter={StaticResource negativeConverter}}">
<StackLayout Grid.Column="1" Orientation="Horizontal" Spacing="6">
<Label Text="{r:Text Income}" TextColor="{DynamicResource GreenColor}"
VerticalOptions="Center" FontSize="12"/>
<Label Text="{Binding Income, Converter={StaticResource moneyConverter}}"
TextColor="{DynamicResource TextColor}"
VerticalOptions="Center" FontSize="12"/>
<Label Text="{r:Text Spending}" TextColor="{DynamicResource RedColor}"
VerticalOptions="Center" FontSize="12" Margin="10, 0, 0, 0"/>
<Label Text="{Binding Spending, Converter={StaticResource moneyConverter}}"
TextColor="{DynamicResource TextColor}"
VerticalOptions="Center" FontSize="12"/>
<Label Text="{r:Text Balance}"
VerticalOptions="Center" FontSize="12" Margin="10, 0, 0, 0"/>
<Label Text="{Binding Balance, Converter={StaticResource moneyRawConverter}}"
TextColor="{DynamicResource TextColor}"
VerticalOptions="Center" FontSize="12"/>
</StackLayout>
</Grid>
<!-- bill list -->
<ScrollView Grid.Row="2">
<ui:GroupStackLayout x:Name="billsLayout" ItemsSource="{Binding Bills}"
Margin="0, 10, 0, 0" RowHeight="50">
<ui:GroupStackLayout.ItemTemplate>
<DataTemplate x:DataType="v:UIBill">
<Grid Padding="20, 0, 10, 0" ColumnSpacing="10"
ColumnDefinitions="Auto, *, Auto" RowDefinitions="Auto, Auto">
<ui:LongPressGrid Padding="20, 0" ColumnSpacing="10"
ColumnDefinitions="Auto, *, Auto" RowDefinitions="Auto, Auto"
LongCommand="{Binding DeleteBilling, Source={x:Reference billPage}}"
LongCommandParameter="{Binding .}">
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding EditBilling, Source={x:Reference billPage}}"
CommandParameter="{Binding .}"/>
@ -70,10 +95,13 @@
<Label Grid.Column="2" Text="{Binding Amount, Converter={StaticResource moneyConverter}}"
TextColor="{Binding Amount, Converter={StaticResource colorConverter}}"
VerticalOptions="Center"/>
<Label Grid.Row="1" Grid.Column="1" Text="{Binding ., Converter={StaticResource billConverter}}"
FontSize="10"
TextColor="{DynamicResource SecondaryTextColor}"/>
</Grid>
<StackLayout Grid.Row="1" Grid.Column="1" Spacing="6" Orientation="Horizontal">
<Label Text="{Binding DateCreation, Converter={StaticResource timeConverter}}"
FontSize="10" TextColor="{DynamicResource SecondaryTextColor}"/>
<Label Text="{Binding Wallet}"
FontSize="10" TextColor="{DynamicResource SecondaryTextColor}"/>
</StackLayout>
</ui:LongPressGrid>
</DataTemplate>
</ui:GroupStackLayout.ItemTemplate>
</ui:GroupStackLayout>

View File

@ -13,6 +13,10 @@ namespace Billing.Views
{
private static readonly BindableProperty SelectedDateProperty = BindableProperty.Create(nameof(SelectedDate), typeof(DateTime), typeof(BillPage));
private static readonly BindableProperty BillsProperty = BindableProperty.Create(nameof(Bills), typeof(List<UIBill>), typeof(BillPage));
private static readonly BindableProperty NoBillsProperty = BindableProperty.Create(nameof(NoBills), typeof(bool), typeof(BillPage));
private static readonly BindableProperty IncomeProperty = BindableProperty.Create(nameof(Income), typeof(decimal), typeof(BillPage));
private static readonly BindableProperty SpendingProperty = BindableProperty.Create(nameof(Spending), typeof(decimal), typeof(BillPage));
private static readonly BindableProperty BalanceProperty = BindableProperty.Create(nameof(Balance), typeof(decimal), typeof(BillPage));
public DateTime SelectedDate
{
@ -24,12 +28,18 @@ namespace Billing.Views
get => (List<UIBill>)GetValue(BillsProperty);
set => SetValue(BillsProperty, value);
}
public bool NoBills => (bool)GetValue(NoBillsProperty);
public decimal Income => (decimal)GetValue(IncomeProperty);
public decimal Spending => (decimal)GetValue(SpendingProperty);
public decimal Balance => (decimal)GetValue(BalanceProperty);
public Command EditBilling { get; }
public Command DeleteBilling { get; }
public BillPage()
{
EditBilling = new Command(OnEditBilling);
DeleteBilling = new Command(OnDeleteBilling);
InitializeComponent();
@ -45,6 +55,17 @@ namespace Billing.Views
b.CreateTime.Month == e.Date.Month &&
b.CreateTime.Day == e.Date.Day);
Bills = new List<UIBill>(bills.Select(b => WrapBill(b)));
RefreshBalance(Bills);
}
private void RefreshBalance(List<UIBill> bills)
{
SetValue(NoBillsProperty, bills.Count == 0);
var income = bills.Where(b => b.Amount > 0).Sum(b => b.Amount);
var spending = -bills.Where(b => b.Amount < 0).Sum(b => b.Amount);
SetValue(IncomeProperty, income);
SetValue(SpendingProperty, spending);
SetValue(BalanceProperty, income - spending);
}
private UIBill WrapBill(Bill b)
@ -101,6 +122,31 @@ namespace Billing.Views
}
}
private async void OnDeleteBilling(object o)
{
if (Tap.IsBusy)
{
return;
}
using (Tap.Start())
{
if (o is UIBill bill)
{
var result = await this.ShowConfirm(Resource.ConfirmDeleteBill);
if (result)
{
var bills = Bills;
bills.Remove(bill);
App.Bills.Remove(bill.Bill);
billsLayout.Refresh(bills);
RefreshBalance(bills);
_ = Task.Run(App.WriteBills);
}
}
}
}
private void OnBillChecked(object sender, Bill e)
{
if (e.Id < 0)
@ -127,6 +173,7 @@ namespace Billing.Views
UpdateBill(bill);
}
}
RefreshBalance(Bills);
Task.Run(App.WriteBills);
}

View File

@ -40,7 +40,11 @@ namespace Billing.Views
new() { Icon = BaseModel.ICON_DEFAULT },
new() { Icon = "wallet" },
new() { Icon = "creditcard" },
new() { Icon = "debitcard" }
new() { Icon = "debitcard" },
new() { Icon = "cmb" },
new() { Icon = "rcb" },
new() { Icon = "yuebao" },
new() { Icon = "zhaozhaoying" }
};
source.AddRange(IconConverter.IconPreset.Select(icon => new BillingIcon { Icon = $"#brand#{icon.Key}" }));
foreach (var icon in source)

View File

@ -2,12 +2,27 @@
<ui:BillingPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:r="clr-namespace:Billing.Languages"
xmlns:ui="clr-namespace:Billing.UI"
xmlns:v="clr-namespace:Billing.Views"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Billing.Views.SettingPage"
Title="{r:Text Settings}">
<StackLayout>
<Label Text="Welcome to Settings Page!"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
</StackLayout>
x:Name="settingPage"
x:DataType="v:SettingPage"
Title="{r:Text Settings}"
BindingContext="{x:Reference settingPage}">
<TableView Intent="Settings" RowHeight="44">
<TableSection Title="{r:Text About}">
<ui:OptionTextCell Title="{r:Text Version}"
Detail="{Binding Version}"/>
</TableSection>
<TableSection Title="{r:Text Preference}">
<ui:OptionEntryCell Title="{r:Text PrimaryColor}"
Text="{Binding Red, Mode=TwoWay}"
Keyboard="Numeric"/>
<ui:OptionEntryCell Text="{Binding Green, Mode=TwoWay}"
Keyboard="Numeric"/>
<ui:OptionEntryCell Text="{Binding Blue, Mode=TwoWay}"
Keyboard="Numeric"/>
</TableSection>
</TableView>
</ui:BillingPage>

View File

@ -1,12 +1,61 @@
using Billing.Themes;
using Billing.UI;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Billing.Views
{
public partial class SettingPage : BillingPage
{
private static readonly BindableProperty VersionProperty = BindableProperty.Create(nameof(Version), typeof(string), typeof(SettingPage));
private static readonly BindableProperty RedProperty = BindableProperty.Create(nameof(Red), typeof(byte), typeof(SettingPage));
private static readonly BindableProperty GreenProperty = BindableProperty.Create(nameof(Green), typeof(byte), typeof(SettingPage));
private static readonly BindableProperty BlueProperty = BindableProperty.Create(nameof(Blue), typeof(byte), typeof(SettingPage));
public string Version => (string)GetValue(VersionProperty);
public byte Red
{
get => (byte)GetValue(RedProperty);
set => SetValue(RedProperty, value);
}
public byte Green
{
get => (byte)GetValue(GreenProperty);
set => SetValue(GreenProperty, value);
}
public byte Blue
{
get => (byte)GetValue(BlueProperty);
set => SetValue(BlueProperty, value);
}
public SettingPage()
{
InitializeComponent();
var (main, build) = Definition.GetVersion();
SetValue(VersionProperty, $"{main} ({build})");
}
protected override void OnAppearing()
{
base.OnAppearing();
//SetValue(VersionProperty, $"{AppInfo.VersionString} ({AppInfo.BuildString})");
var colorString = Preferences.Get(Definition.PrimaryColorKey, "#183153");
var color = Color.FromHex(colorString);
Red = (byte)(color.R * 255);
Green = (byte)(color.G * 255);
Blue = (byte)(color.B * 255);
}
protected override void OnDisappearing()
{
base.OnDisappearing();
var color = Color.FromRgb(Red, Green, Blue);
Preferences.Set(Definition.PrimaryColorKey, color.ToHex());
Light.Instance.RefreshColor(color);
}
}
}