complete ranking page
This commit is contained in:
parent
b5c531d128
commit
abffc0627e
@ -92,4 +92,5 @@
|
|||||||
<TrackingChart>Tracking Chart</TrackingChart>
|
<TrackingChart>Tracking Chart</TrackingChart>
|
||||||
<NoResult>(no results)</NoResult>
|
<NoResult>(no results)</NoResult>
|
||||||
<Top10>Top 10</Top10>
|
<Top10>Top 10</Top10>
|
||||||
|
<CategoryRank>Category Ranking</CategoryRank>
|
||||||
</root>
|
</root>
|
@ -92,4 +92,5 @@
|
|||||||
<TrackingChart>跟踪图表</TrackingChart>
|
<TrackingChart>跟踪图表</TrackingChart>
|
||||||
<NoResult>(无记录)</NoResult>
|
<NoResult>(无记录)</NoResult>
|
||||||
<Top10>Top 10</Top10>
|
<Top10>Top 10</Top10>
|
||||||
|
<CategoryRank>分类排行</CategoryRank>
|
||||||
</root>
|
</root>
|
@ -111,10 +111,16 @@ namespace Billing.UI
|
|||||||
|
|
||||||
public class TimeConverter : IValueConverter
|
public class TimeConverter : IValueConverter
|
||||||
{
|
{
|
||||||
|
public bool IncludeDate { get; set; }
|
||||||
|
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
if (value is DateTime time)
|
if (value is DateTime time)
|
||||||
{
|
{
|
||||||
|
if (IncludeDate)
|
||||||
|
{
|
||||||
|
return time.ToString("MM-dd HH:mm");
|
||||||
|
}
|
||||||
return time.ToString("HH:mm");
|
return time.ToString("HH:mm");
|
||||||
}
|
}
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
<ui:NegativeConverter x:Key="negativeConverter"/>
|
<ui:NegativeConverter x:Key="negativeConverter"/>
|
||||||
<ui:MoneyConverter x:Key="moneyConverter" Absolute="True"/>
|
<ui:MoneyConverter x:Key="moneyConverter" Absolute="True"/>
|
||||||
<ui:BalanceColorConverter x:Key="colorConverter"/>
|
<ui:BalanceColorConverter x:Key="colorConverter"/>
|
||||||
<ui:TimeConverter x:Key="timeConverter"/>
|
<ui:TimeConverter x:Key="timeConverter" IncludeDate="True"/>
|
||||||
<ui:IconConverter x:Key="iconConverter"/>
|
<ui:IconConverter x:Key="iconConverter"/>
|
||||||
<Style x:Key="titleLabel" TargetType="Label">
|
<Style x:Key="titleLabel" TargetType="Label">
|
||||||
<Setter Property="FontSize" Value="16"/>
|
<Setter Property="FontSize" Value="16"/>
|
||||||
@ -63,18 +63,28 @@
|
|||||||
<StackLayout>
|
<StackLayout>
|
||||||
<Label Text="{r:Text TrackingChart}" Style="{StaticResource titleLabel}"/>
|
<Label Text="{r:Text TrackingChart}" Style="{StaticResource titleLabel}"/>
|
||||||
<chart:ChartView HeightRequest="240" Chart="{Binding Chart}"
|
<chart:ChartView HeightRequest="240" Chart="{Binding Chart}"
|
||||||
IsVisible="{Binding NoResult, Converter={StaticResource negativeConverter}}"/>
|
IsVisible="{Binding NoResultChart, Converter={StaticResource negativeConverter}}"/>
|
||||||
<Label Text="{r:Text NoResult}" Style="{StaticResource promptLabel}"
|
<Label Text="{r:Text NoResult}" Style="{StaticResource promptLabel}"
|
||||||
IsVisible="{Binding NoResult}"/>
|
IsVisible="{Binding NoResultChart}"/>
|
||||||
|
|
||||||
|
<Label Text="{r:Text CategoryRank}" Style="{StaticResource titleLabel}"/>
|
||||||
|
<chart:ChartView HeightRequest="240" Chart="{Binding CategoryChart}"
|
||||||
|
IsVisible="{Binding NoResultCategoryChart, Converter={StaticResource negativeConverter}}"/>
|
||||||
|
<Label Text="{r:Text NoResult}" Style="{StaticResource promptLabel}"
|
||||||
|
IsVisible="{Binding NoResultCategoryChart}"/>
|
||||||
|
|
||||||
<Label Text="{r:Text Top10}" Style="{StaticResource titleLabel}"/>
|
<Label Text="{r:Text Top10}" Style="{StaticResource titleLabel}"/>
|
||||||
<ui:GroupStackLayout IsVisible="{Binding NoResult, Converter={StaticResource negativeConverter}}"
|
<ui:GroupStackLayout IsVisible="{Binding NoResultTopBills, Converter={StaticResource negativeConverter}}"
|
||||||
ItemsSource="{Binding TopBills}" RowHeight="50">
|
ItemsSource="{Binding TopBills}" RowHeight="50">
|
||||||
|
|
||||||
<ui:GroupStackLayout.ItemTemplate>
|
<ui:GroupStackLayout.ItemTemplate>
|
||||||
<DataTemplate x:DataType="v:UIBill">
|
<DataTemplate x:DataType="v:UIBill">
|
||||||
<Grid Padding="20, 0" ColumnSpacing="10"
|
<Grid Padding="20, 0" ColumnSpacing="10"
|
||||||
ColumnDefinitions="Auto, *, Auto" RowDefinitions="Auto, Auto">
|
ColumnDefinitions="Auto, *, Auto" RowDefinitions="Auto, Auto">
|
||||||
|
<Grid.GestureRecognizers>
|
||||||
|
<TapGestureRecognizer Command="{Binding EditBilling, Source={x:Reference rankPage}}"
|
||||||
|
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}"
|
||||||
@ -94,7 +104,7 @@
|
|||||||
</ui:GroupStackLayout.ItemTemplate>
|
</ui:GroupStackLayout.ItemTemplate>
|
||||||
</ui:GroupStackLayout>
|
</ui:GroupStackLayout>
|
||||||
<Label Text="{r:Text NoResult}" Style="{StaticResource promptLabel}"
|
<Label Text="{r:Text NoResult}" Style="{StaticResource promptLabel}"
|
||||||
IsVisible="{Binding NoResult}"/>
|
IsVisible="{Binding NoResultTopBills}"/>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</ui:BillingPage>
|
</ui:BillingPage>
|
@ -17,88 +17,156 @@ namespace Billing.Views
|
|||||||
public partial class RankPage : BillingPage
|
public partial class RankPage : BillingPage
|
||||||
{
|
{
|
||||||
private static readonly BindableProperty ChartProperty = Helper.Create<Chart, RankPage>(nameof(Chart));
|
private static readonly BindableProperty ChartProperty = Helper.Create<Chart, RankPage>(nameof(Chart));
|
||||||
|
private static readonly BindableProperty CategoryChartProperty = Helper.Create<Chart, RankPage>(nameof(CategoryChart));
|
||||||
private static readonly BindableProperty TopBillsProperty = Helper.Create<IList<UIBill>, RankPage>(nameof(TopBills));
|
private static readonly BindableProperty TopBillsProperty = Helper.Create<IList<UIBill>, RankPage>(nameof(TopBills));
|
||||||
private static readonly BindableProperty NoResultProperty = Helper.Create<bool, RankPage>(nameof(NoResult));
|
private static readonly BindableProperty NoResultChartProperty = Helper.Create<bool, RankPage>(nameof(NoResultChart));
|
||||||
|
private static readonly BindableProperty NoResultCategoryChartProperty = Helper.Create<bool, RankPage>(nameof(NoResultCategoryChart));
|
||||||
|
private static readonly BindableProperty NoResultTopBillsProperty = Helper.Create<bool, RankPage>(nameof(NoResultTopBills));
|
||||||
|
|
||||||
public Chart Chart
|
public Chart Chart
|
||||||
{
|
{
|
||||||
get => (Chart)GetValue(ChartProperty);
|
get => (Chart)GetValue(ChartProperty);
|
||||||
set => SetValue(ChartProperty, value);
|
set => SetValue(ChartProperty, value);
|
||||||
}
|
}
|
||||||
|
public Chart CategoryChart
|
||||||
|
{
|
||||||
|
get => (Chart)GetValue(CategoryChartProperty);
|
||||||
|
set => SetValue(CategoryChartProperty, value);
|
||||||
|
}
|
||||||
public IList<UIBill> TopBills
|
public IList<UIBill> TopBills
|
||||||
{
|
{
|
||||||
get => (IList<UIBill>)GetValue(TopBillsProperty);
|
get => (IList<UIBill>)GetValue(TopBillsProperty);
|
||||||
set => SetValue(TopBillsProperty, value);
|
set => SetValue(TopBillsProperty, value);
|
||||||
}
|
}
|
||||||
public bool NoResult
|
public bool NoResultChart
|
||||||
{
|
{
|
||||||
get => (bool)GetValue(NoResultProperty);
|
get => (bool)GetValue(NoResultChartProperty);
|
||||||
set => SetValue(NoResultProperty, value);
|
set => SetValue(NoResultChartProperty, value);
|
||||||
|
}
|
||||||
|
public bool NoResultCategoryChart
|
||||||
|
{
|
||||||
|
get => (bool)GetValue(NoResultCategoryChartProperty);
|
||||||
|
set => SetValue(NoResultCategoryChartProperty, value);
|
||||||
|
}
|
||||||
|
public bool NoResultTopBills
|
||||||
|
{
|
||||||
|
get => (bool)GetValue(NoResultTopBillsProperty);
|
||||||
|
set => SetValue(NoResultTopBillsProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Command LeftCommand { get; }
|
public Command LeftCommand { get; }
|
||||||
public Command RightCommand { get; }
|
public Command RightCommand { get; }
|
||||||
public Command FilterCommand { get; }
|
public Command FilterCommand { get; }
|
||||||
|
public Command EditBilling { get; }
|
||||||
|
|
||||||
private DateTime current;
|
private DateTime current;
|
||||||
|
private DateTime end;
|
||||||
private IEnumerable<Bill> bills;
|
private IEnumerable<Bill> bills;
|
||||||
private CategoryType type = CategoryType.Spending;
|
private CategoryType type = CategoryType.Spending;
|
||||||
|
|
||||||
|
private readonly SKTypeface font;
|
||||||
|
|
||||||
public RankPage()
|
public RankPage()
|
||||||
{
|
{
|
||||||
LeftCommand = new Command(OnLeftCommand);
|
LeftCommand = new Command(OnLeftCommand);
|
||||||
RightCommand = new Command(OnRightCommand);
|
RightCommand = new Command(OnRightCommand);
|
||||||
|
EditBilling = new Command(OnEditBilling);
|
||||||
|
|
||||||
|
var style = SKFontManager.Default.GetFontStyles("PingFang SC");
|
||||||
|
if (style != null)
|
||||||
|
{
|
||||||
|
font = style.CreateTypeface(SKFontStyle.Normal);
|
||||||
|
}
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnLoaded()
|
public override void OnLoaded()
|
||||||
{
|
{
|
||||||
Task.Run(() => SetMonth(DateTime.Today));
|
SetMonth(DateTime.Today);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLeftCommand()
|
private void OnLeftCommand()
|
||||||
{
|
{
|
||||||
Task.Run(() =>
|
if (scroller.ScrollY > 0)
|
||||||
{
|
{
|
||||||
MainThread.BeginInvokeOnMainThread(async () => await scroller.ScrollToAsync(0, 0, true));
|
scroller.ScrollToAsync(0, 0, true);
|
||||||
SetMonth(current.AddMonths(-1));
|
}
|
||||||
});
|
SetMonth(current.AddMonths(-1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRightCommand()
|
private void OnRightCommand()
|
||||||
{
|
{
|
||||||
Task.Run(() =>
|
if (scroller.ScrollY > 0)
|
||||||
{
|
{
|
||||||
MainThread.BeginInvokeOnMainThread(async () => await scroller.ScrollToAsync(0, 0, true));
|
scroller.ScrollToAsync(0, 0, true);
|
||||||
SetMonth(current.AddMonths(1));
|
}
|
||||||
});
|
SetMonth(current.AddMonths(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetMonth(DateTime date)
|
private async void OnEditBilling(object o)
|
||||||
|
{
|
||||||
|
if (Tap.IsBusy)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
using (Tap.Start())
|
||||||
|
{
|
||||||
|
if (o is UIBill bill)
|
||||||
|
{
|
||||||
|
var page = new AddBillPage(bill.Bill);
|
||||||
|
page.BillChecked += OnBillChecked;
|
||||||
|
await Navigation.PushAsync(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 OnBillChecked(object sender, Bill e)
|
||||||
|
{
|
||||||
|
var bill = TopBills.FirstOrDefault(b => b.Bill == e);
|
||||||
|
if (bill != null)
|
||||||
|
{
|
||||||
|
UpdateBill(bill);
|
||||||
|
}
|
||||||
|
|
||||||
|
Task.Run(App.WriteBills);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void SetMonth(DateTime date)
|
||||||
{
|
{
|
||||||
current = date.AddDays(1 - date.Day);
|
current = date.AddDays(1 - date.Day);
|
||||||
var end = current.AddDays(DateTime.DaysInMonth(current.Year, current.Month));
|
end = current.AddDays(DateTime.DaysInMonth(current.Year, current.Month));
|
||||||
|
|
||||||
var format = Resource.DateRangeFormat;
|
var format = Resource.DateRangeFormat;
|
||||||
Title = current.ToString(format) + " ~ " + end.AddDays(-1).ToString(format);
|
Title = current.ToString(format) + " ~ " + end.AddDays(-1).ToString(format);
|
||||||
|
|
||||||
bills = App.Bills.Where(b => b.CreateTime >= current && b.CreateTime <= end);
|
var spending = type == CategoryType.Spending;
|
||||||
|
bills = await Task.Run(() => App.Bills.Where(b => (b.Amount > 0 ^ spending) && b.CreateTime >= current && b.CreateTime <= end));
|
||||||
|
|
||||||
var entries = new List<ChartEntry>();
|
|
||||||
var primaryColor = BaseTheme.CurrentPrimaryColor.ToSKColor();
|
var primaryColor = BaseTheme.CurrentPrimaryColor.ToSKColor();
|
||||||
var textColor = BaseTheme.CurrentSecondaryTextColor.ToSKColor();
|
var textColor = BaseTheme.CurrentSecondaryTextColor.ToSKColor();
|
||||||
|
|
||||||
|
_ = Task.Run(() => LoadReportChart(primaryColor, textColor));
|
||||||
|
_ = Task.Run(() => LoadCategoryChart(primaryColor, textColor));
|
||||||
|
_ = Task.Run(LoadTopBills);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadReportChart(SKColor primaryColor, SKColor textColor)
|
||||||
|
{
|
||||||
|
var entries = new List<ChartEntry>();
|
||||||
for (var day = current; day <= end; day = day.AddDays(1))
|
for (var day = current; day <= end; day = day.AddDays(1))
|
||||||
{
|
{
|
||||||
var daybills = bills.Where(b => Helper.IsSameDay(b.CreateTime, day));
|
var daybills = bills.Where(b => Helper.IsSameDay(b.CreateTime, day));
|
||||||
decimal amount;
|
decimal amount = Math.Abs(daybills.Sum(b => b.Amount));
|
||||||
if (type == CategoryType.Income)
|
|
||||||
{
|
|
||||||
amount = daybills.Where(b => b.Amount > 0).Sum(b => b.Amount);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
amount = daybills.Where(b => b.Amount < 0).Sum(b => -b.Amount);
|
|
||||||
}
|
|
||||||
if (amount > 0)
|
if (amount > 0)
|
||||||
{
|
{
|
||||||
entries.Add(new((float)amount)
|
entries.Add(new((float)amount)
|
||||||
@ -112,30 +180,104 @@ namespace Billing.Views
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entries.Count > 0)
|
MainThread.BeginInvokeOnMainThread(() =>
|
||||||
{
|
{
|
||||||
NoResult = false;
|
if (entries.Count > 0)
|
||||||
Chart = new LineChart
|
|
||||||
{
|
{
|
||||||
BackgroundColor = SKColors.Transparent,
|
NoResultChart = false;
|
||||||
LabelTextSize = 24,
|
Chart = new LineChart
|
||||||
Entries = entries
|
{
|
||||||
};
|
BackgroundColor = SKColors.Transparent,
|
||||||
if (type == CategoryType.Income)
|
LabelTextSize = 24,
|
||||||
{
|
Entries = entries
|
||||||
TopBills = bills.Where(b => b.Amount > 0).OrderByDescending(b => b.Amount).Take(10).Select(b => Helper.WrapBill(b)).ToList();
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TopBills = bills.Where(b => b.Amount < 0).OrderBy(b => b.Amount).Take(10).Select(b => Helper.WrapBill(b)).ToList();
|
Chart = null;
|
||||||
|
NoResultChart = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadCategoryChart(SKColor primaryColor, SKColor textColor)
|
||||||
|
{
|
||||||
|
var entries = new List<ChartEntry>();
|
||||||
|
var groups = bills.GroupBy(b => b.CategoryId);
|
||||||
|
var dict = new Dictionary<string, decimal>();
|
||||||
|
decimal all = 0m;
|
||||||
|
foreach (var g in groups)
|
||||||
|
{
|
||||||
|
var categoryId = g.Key;
|
||||||
|
var category = App.Categories.FirstOrDefault(c => c.Id == categoryId);
|
||||||
|
if (category?.ParentId != null)
|
||||||
|
{
|
||||||
|
category = App.Categories.FirstOrDefault(c => c.Id == category.ParentId) ?? category;
|
||||||
|
}
|
||||||
|
if (category != null)
|
||||||
|
{
|
||||||
|
var total = Math.Abs(g.Sum(g => g.Amount));
|
||||||
|
all += total;
|
||||||
|
if (dict.ContainsKey(category.Name))
|
||||||
|
{
|
||||||
|
dict[category.Name] += total;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dict.Add(category.Name, total);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
foreach (var kv in dict)
|
||||||
{
|
{
|
||||||
NoResult = true;
|
entries.Add(new((float)kv.Value)
|
||||||
Chart = null;
|
{
|
||||||
TopBills = null;
|
Label = kv.Key,
|
||||||
|
ValueLabel = (kv.Value * 100 / all).ToString("0.#") + "%",
|
||||||
|
Color = primaryColor,
|
||||||
|
TextColor = textColor,
|
||||||
|
ValueLabelColor = textColor
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MainThread.BeginInvokeOnMainThread(() =>
|
||||||
|
{
|
||||||
|
if (entries.Count > 0)
|
||||||
|
{
|
||||||
|
NoResultCategoryChart = false;
|
||||||
|
CategoryChart = new RadarChart
|
||||||
|
{
|
||||||
|
BackgroundColor = SKColors.Transparent,
|
||||||
|
LabelTextSize = 30,
|
||||||
|
Typeface = font,
|
||||||
|
Entries = entries
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CategoryChart = null;
|
||||||
|
NoResultCategoryChart = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadTopBills()
|
||||||
|
{
|
||||||
|
List<UIBill> topBills = bills.OrderByDescending(b => Math.Abs(b.Amount)).Take(10).Select(b => Helper.WrapBill(b)).ToList();
|
||||||
|
|
||||||
|
MainThread.BeginInvokeOnMainThread(() =>
|
||||||
|
{
|
||||||
|
if (topBills.Count > 0)
|
||||||
|
{
|
||||||
|
NoResultTopBills = false;
|
||||||
|
TopBills = topBills;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TopBills = null;
|
||||||
|
NoResultTopBills = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user