using Billing.Models; using Billing.Themes; using Billing.UI; using Microcharts; using SkiaSharp; using SkiaSharp.Views.Forms; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Xamarin.Essentials; using Xamarin.Forms; using Resource = Billing.Languages.Resource; namespace Billing.Views { public partial class RankPage : BillingPage { private static readonly BindableProperty ChartProperty = Helper.Create(nameof(Chart)); private static readonly BindableProperty CategoryChartProperty = Helper.Create(nameof(CategoryChart)); private static readonly BindableProperty TopBillsProperty = Helper.Create, RankPage>(nameof(TopBills)); private static readonly BindableProperty NoResultChartProperty = Helper.Create(nameof(NoResultChart)); private static readonly BindableProperty NoResultCategoryChartProperty = Helper.Create(nameof(NoResultCategoryChart)); private static readonly BindableProperty NoResultTopBillsProperty = Helper.Create(nameof(NoResultTopBills)); public Chart Chart { get => (Chart)GetValue(ChartProperty); set => SetValue(ChartProperty, value); } public Chart CategoryChart { get => (Chart)GetValue(CategoryChartProperty); set => SetValue(CategoryChartProperty, value); } public IList TopBills { get => (IList)GetValue(TopBillsProperty); set => SetValue(TopBillsProperty, value); } public bool NoResultChart { get => (bool)GetValue(NoResultChartProperty); 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 RightCommand { get; } public Command FilterCommand { get; } public Command EditBilling { get; } private DateTime current; private DateTime end; private IEnumerable bills; private CategoryType type = CategoryType.Spending; private readonly SKTypeface font; public RankPage() { LeftCommand = new Command(OnLeftCommand); RightCommand = new Command(OnRightCommand); EditBilling = new Command(OnEditBilling); var style = SKFontManager.Default.GetFontStyles("PingFang SC"); if (style != null) { font = style.CreateTypeface(SKFontStyle.Normal); } InitializeComponent(); } public override void OnLoaded() { SetMonth(DateTime.Today); } private void OnLeftCommand() { if (scroller.ScrollY > 0) { scroller.ScrollToAsync(0, 0, true); } SetMonth(current.AddMonths(-1)); } private void OnRightCommand() { if (scroller.ScrollY > 0) { scroller.ScrollToAsync(0, 0, true); } SetMonth(current.AddMonths(1)); } 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); end = current.AddDays(DateTime.DaysInMonth(current.Year, current.Month)); var format = Resource.DateRangeFormat; Title = current.ToString(format) + " ~ " + end.AddDays(-1).ToString(format); var spending = type == CategoryType.Spending; bills = await Task.Run(() => App.Bills.Where(b => (b.Amount > 0 ^ spending) && b.CreateTime >= current && b.CreateTime <= end)); var primaryColor = BaseTheme.CurrentPrimaryColor.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(); for (var day = current; day <= end; day = day.AddDays(1)) { var daybills = bills.Where(b => Helper.IsSameDay(b.CreateTime, day)); decimal amount = Math.Abs(daybills.Sum(b => b.Amount)); if (amount > 0) { entries.Add(new((float)amount) { Label = day.ToString("MM-dd"), ValueLabel = amount.ToString("#,##0.##"), Color = primaryColor, TextColor = textColor, ValueLabelColor = textColor }); } } MainThread.BeginInvokeOnMainThread(() => { if (entries.Count > 0) { NoResultChart = false; Chart = new LineChart { BackgroundColor = SKColors.Transparent, LabelTextSize = 24, Entries = entries }; } else { Chart = null; NoResultChart = true; } }); } private void LoadCategoryChart(SKColor primaryColor, SKColor textColor) { var entries = new List(); var groups = bills.GroupBy(b => b.CategoryId); var dict = new Dictionary(); 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); } } } foreach (var kv in dict) { entries.Add(new((float)kv.Value) { 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 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; } }); } } }