diff --git a/Billing.Shared/Billing.Shared.projitems b/Billing.Shared/Billing.Shared.projitems index 7d6eba9..5566fd7 100644 --- a/Billing.Shared/Billing.Shared.projitems +++ b/Billing.Shared/Billing.Shared.projitems @@ -77,6 +77,7 @@ + diff --git a/Billing.Shared/UI/CustomControl.cs b/Billing.Shared/UI/CustomControl.cs index d32550f..17baaab 100644 --- a/Billing.Shared/UI/CustomControl.cs +++ b/Billing.Shared/UI/CustomControl.cs @@ -62,4 +62,6 @@ namespace Billing.UI } } } + + public class BlurryPanel : ContentView { } } \ No newline at end of file diff --git a/Billing.Shared/UI/SegmentedControl.cs b/Billing.Shared/UI/SegmentedControl.cs new file mode 100644 index 0000000..b4e310d --- /dev/null +++ b/Billing.Shared/UI/SegmentedControl.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using Xamarin.Forms; + +namespace Billing.UI +{ + public class SegmentedControl : View, IViewContainer + { + public IList Children { get; set; } + + public SegmentedControl() + { + Children = new List(); + } + + public static readonly BindableProperty TintColorProperty = Helper.Create(nameof(TintColor)); + public static readonly BindableProperty DisabledColorProperty = Helper.Create(nameof(DisabledColor)); + public static readonly BindableProperty SelectedTextColorProperty = Helper.Create(nameof(SelectedTextColor)); + public static readonly BindableProperty SelectedSegmentIndexProperty = Helper.Create(nameof(SelectedSegmentIndex)); + + public Color TintColor + { + get => (Color)GetValue(TintColorProperty); + set => SetValue(TintColorProperty, value); + } + public Color DisabledColor + { + get => (Color)GetValue(DisabledColorProperty); + set => SetValue(DisabledColorProperty, value); + } + public Color SelectedTextColor + { + get => (Color)GetValue(SelectedTextColorProperty); + set => SetValue(SelectedTextColorProperty, value); + } + public int SelectedSegmentIndex + { + get => (int)GetValue(SelectedSegmentIndexProperty); + set => SetValue(SelectedSegmentIndexProperty, value); + } + + public SegmentedControlOption SelectedSegment => Children[SelectedSegmentIndex]; + + public event EventHandler ValueChanged; + + //[EditorBrowsable(EditorBrowsableState.Never)] + public void SendValueChanged() + { + ValueChanged?.Invoke(this, new ValueChangedEventArgs { NewValue = SelectedSegmentIndex }); + } + } + + public class SegmentedControlOption : View + { + public static readonly BindableProperty TextProperty = Helper.Create(nameof(Text)); + + public string Text + { + get => (string)GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + public object Value { get; set; } + } + + public class ValueChangedEventArgs : EventArgs + { + public int NewValue { get; set; } + } +} diff --git a/Billing.Shared/Views/RankPage.xaml b/Billing.Shared/Views/RankPage.xaml index 4806653..ebf656b 100644 --- a/Billing.Shared/Views/RankPage.xaml +++ b/Billing.Shared/Views/RankPage.xaml @@ -41,6 +41,7 @@ + @@ -59,52 +60,89 @@ - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/Billing.Shared/Views/RankPage.xaml.cs b/Billing.Shared/Views/RankPage.xaml.cs index 20fba3a..edd6bca 100644 --- a/Billing.Shared/Views/RankPage.xaml.cs +++ b/Billing.Shared/Views/RankPage.xaml.cs @@ -16,13 +16,33 @@ namespace Billing.Views { public partial class RankPage : BillingPage { + private static readonly BindableProperty SegmentTypeProperty = Helper.Create(nameof(SegmentType), defaultValue: 0, propertyChanged: OnSegmentTypeChanged); 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)); + private static readonly BindableProperty IncomeProperty = Helper.Create(nameof(Income)); + private static readonly BindableProperty SpendingProperty = Helper.Create(nameof(Spending)); + private static readonly BindableProperty BalanceProperty = Helper.Create(nameof(Balance)); + private static void OnSegmentTypeChanged(RankPage page, int old, int @new) + { + page.type = @new switch + { + 1 => CategoryType.Income, + _ => CategoryType.Spending + }; + page.OnFilterCommand(false); + page.SetMonth(page.current); + } + + public int SegmentType + { + get => (int)GetValue(SegmentTypeProperty); + set => SetValue(SegmentTypeProperty, value); + } public Chart Chart { get => (Chart)GetValue(ChartProperty); @@ -53,6 +73,9 @@ namespace Billing.Views get => (bool)GetValue(NoResultTopBillsProperty); set => SetValue(NoResultTopBillsProperty, value); } + public decimal Income => (decimal)GetValue(IncomeProperty); + public decimal Spending => (decimal)GetValue(SpendingProperty); + public decimal Balance => (decimal)GetValue(BalanceProperty); public Command LeftCommand { get; } public Command RightCommand { get; } @@ -63,6 +86,7 @@ namespace Billing.Views private DateTime end; private IEnumerable bills; private CategoryType type = CategoryType.Spending; + private bool isFilterToggled; private readonly SKTypeface font; @@ -70,6 +94,7 @@ namespace Billing.Views { LeftCommand = new Command(OnLeftCommand); RightCommand = new Command(OnRightCommand); + FilterCommand = new Command(OnFilterCommand); EditBilling = new Command(OnEditBilling); var style = SKFontManager.Default.GetFontStyles("PingFang SC"); @@ -79,6 +104,9 @@ namespace Billing.Views } InitializeComponent(); + + gridFilter.TranslationY = -60; + panelFilter.TranslationY = -60; } public override void OnLoaded() @@ -104,6 +132,36 @@ namespace Billing.Views SetMonth(current.AddMonths(1)); } + private async void OnFilterCommand(object o) + { + if (o is bool flag) + { + isFilterToggled = flag; + } + else + { + isFilterToggled = !isFilterToggled; + } + ViewExtensions.CancelAnimations(gridFilter); + ViewExtensions.CancelAnimations(panelFilter); + if (isFilterToggled) + { + await Task.WhenAll( + gridFilter.TranslateTo(0, 0, easing: Easing.CubicOut), + gridFilter.FadeTo(1, easing: Easing.CubicOut), + panelFilter.TranslateTo(0, 0, easing: Easing.CubicOut), + panelFilter.FadeTo(1, easing: Easing.CubicOut)); + } + else + { + await Task.WhenAll( + gridFilter.TranslateTo(0, -60, easing: Easing.CubicIn), + gridFilter.FadeTo(0, easing: Easing.CubicIn), + panelFilter.TranslateTo(0, -60, easing: Easing.CubicIn), + panelFilter.FadeTo(0, easing: Easing.CubicIn)); + } + } + private async void OnEditBilling(object o) { if (Tap.IsBusy) @@ -130,6 +188,16 @@ namespace Billing.Views bill.Wallet = App.Accounts.FirstOrDefault(a => a.Id == bill.Bill.WalletId)?.Name; } + private async void RefreshBalance() + { + var bills = await Task.Run(() => App.Bills.Where(b => b.CreateTime >= current && b.CreateTime <= end)); + 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 void OnBillChecked(object sender, Bill e) { var bill = TopBills.FirstOrDefault(b => b.Bill == e); @@ -137,6 +205,7 @@ namespace Billing.Views { UpdateBill(bill); } + RefreshBalance(); Task.Run(App.WriteBills); } @@ -158,6 +227,8 @@ namespace Billing.Views _ = Task.Run(() => LoadReportChart(primaryColor, textColor)); _ = Task.Run(() => LoadCategoryChart(primaryColor, textColor)); _ = Task.Run(LoadTopBills); + + RefreshBalance(); } private void LoadReportChart(SKColor primaryColor, SKColor textColor) diff --git a/Billing/Billing.Android/Billing.Android.csproj b/Billing/Billing.Android/Billing.Android.csproj index c30730e..70a2386 100644 --- a/Billing/Billing.Android/Billing.Android.csproj +++ b/Billing/Billing.Android/Billing.Android.csproj @@ -89,6 +89,7 @@ + diff --git a/Billing/Billing.Android/Properties/AndroidManifest.xml b/Billing/Billing.Android/Properties/AndroidManifest.xml index 8c5af44..59f68b9 100644 --- a/Billing/Billing.Android/Properties/AndroidManifest.xml +++ b/Billing/Billing.Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@  - + diff --git a/Billing/Billing.Android/Renderers/BlurryPanelRenderer.cs b/Billing/Billing.Android/Renderers/BlurryPanelRenderer.cs new file mode 100644 index 0000000..259da23 --- /dev/null +++ b/Billing/Billing.Android/Renderers/BlurryPanelRenderer.cs @@ -0,0 +1,30 @@ +using Android.Content; +using Billing.Droid.Renderers; +using Billing.UI; +using Xamarin.Forms; +using Xamarin.Forms.Platform.Android; + +[assembly: ExportRenderer(typeof(BlurryPanel), typeof(BlurryPanelRenderer))] +namespace Billing.Droid.Renderers +{ + public class BlurryPanelRenderer : ViewRenderer + { + public BlurryPanelRenderer(Context context) : base(context) + { + } + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + if (e.NewElement != null) + { + var color = e.NewElement.BackgroundColor; + if (!color.IsDefault) + { + SetBackgroundColor(color.MultiplyAlpha(.94).ToAndroid()); + } + } + } + } +} diff --git a/Billing/Billing.iOS/Billing.iOS.csproj b/Billing/Billing.iOS/Billing.iOS.csproj index 16d4a37..a65f362 100644 --- a/Billing/Billing.iOS/Billing.iOS.csproj +++ b/Billing/Billing.iOS/Billing.iOS.csproj @@ -89,6 +89,8 @@ + + diff --git a/Billing/Billing.iOS/Info.plist b/Billing/Billing.iOS/Info.plist index 684fd37..e2f0733 100644 --- a/Billing/Billing.iOS/Info.plist +++ b/Billing/Billing.iOS/Info.plist @@ -44,8 +44,8 @@ UIFileSharingEnabled CFBundleVersion - 7 + 8 CFBundleShortVersionString - 0.7.308 + 0.8.309 diff --git a/Billing/Billing.iOS/Renderers/BlurryPanelRenderer.cs b/Billing/Billing.iOS/Renderers/BlurryPanelRenderer.cs new file mode 100644 index 0000000..5e5d7a9 --- /dev/null +++ b/Billing/Billing.iOS/Renderers/BlurryPanelRenderer.cs @@ -0,0 +1,87 @@ +using Billing.iOS.Renderers; +using Billing.UI; +using CoreAnimation; +using UIKit; +using Xamarin.Forms; +using Xamarin.Forms.Platform.iOS; + +[assembly: ExportRenderer(typeof(BlurryPanel), typeof(BlurryPanelRenderer))] +namespace Billing.iOS.Renderers +{ + public class BlurryPanelRenderer : ViewRenderer + { + private UIVisualEffectView nativeControl; + private CALayer bottom; + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + if (e.OldElement != null) + { + if (bottom != null) + { + if (bottom.SuperLayer != null) + { + bottom.RemoveFromSuperLayer(); + } + bottom.Dispose(); + bottom = null; + } + } + + if (e.NewElement != null) + { + e.NewElement.BackgroundColor = Color.Default; + if (Control == null) + { + var blur = UIBlurEffect.FromStyle(UIBlurEffectStyle.SystemMaterial); + nativeControl = new UIVisualEffectView(blur) + { + Frame = Frame + }; + SetNativeControl(nativeControl); + } + } + } + + public override void LayoutSubviews() + { + base.LayoutSubviews(); + + if (nativeControl != null) + { + if (bottom == null) + { + bottom = new CALayer + { + BackgroundColor = UIColor.White.CGColor, + ShadowColor = UIColor.Black.CGColor, + ShadowOpacity = 1.0f + }; + } + if (bottom.SuperLayer == null) + { + nativeControl.Layer.InsertSublayer(bottom, 0); + } + bottom.Frame = new CoreGraphics.CGRect(0, Frame.Height - 5, Frame.Width, 5); + nativeControl.Frame = Frame; + } + } + + protected override void Dispose(bool disposing) + { + if (bottom != null) + { + if (bottom.SuperLayer != null) + { + bottom.RemoveFromSuperLayer(); + } + bottom.Dispose(); + bottom = null; + } + + base.Dispose(disposing); + } + } +} diff --git a/Billing/Billing.iOS/Renderers/SegmentedControlRenderer.cs b/Billing/Billing.iOS/Renderers/SegmentedControlRenderer.cs new file mode 100644 index 0000000..6ff1208 --- /dev/null +++ b/Billing/Billing.iOS/Renderers/SegmentedControlRenderer.cs @@ -0,0 +1,153 @@ +using System; +using System.ComponentModel; +using Billing.iOS.Renderers; +using Billing.UI; +using UIKit; +using Xamarin.Forms; +using Xamarin.Forms.Platform.iOS; + +[assembly: ExportRenderer(typeof(SegmentedControl), typeof(SegmentedControlRenderer))] +namespace Billing.iOS.Renderers +{ + public class SegmentedControlRenderer : ViewRenderer + { + private UISegmentedControl nativeControl; + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + var element = Element; + if (Control == null && element != null) + { + nativeControl = new UISegmentedControl(); + + for (var i = 0; i < element.Children.Count; i++) + { + nativeControl.InsertSegment(element.Children[i].Text, i, false); + } + + nativeControl.Enabled = element.IsEnabled; + //nativeControl.BackgroundColor = element.BackgroundColor.ToUIColor(); + nativeControl.SelectedSegmentTintColor = GetTintColor(element); + SetTextColor(); + nativeControl.SelectedSegment = element.SelectedSegmentIndex; + + SetNativeControl(nativeControl); + } + + if (e.OldElement != null) + { + if (nativeControl != null) + { + nativeControl.ValueChanged -= NativeControl_ValueChanged; + } + } + + if (e.NewElement != null) + { + nativeControl.ValueChanged += NativeControl_ValueChanged; + } + } + + protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(sender, e); + + var element = Element; + if (nativeControl == null || element == null) + { + return; + } + + switch (e.PropertyName) + { + case "Renderer": + element.SendValueChanged(); + break; + + //case nameof(element.BackgroundColor): + // nativeControl.BackgroundColor = element.BackgroundColor.ToUIColor(); + // break; + case nameof(element.SelectedSegmentIndex): + nativeControl.SelectedSegment = element.SelectedSegmentIndex; + break; + + case nameof(element.TintColor): + nativeControl.SelectedSegmentTintColor = GetTintColor(element); + break; + + case nameof(element.IsEnabled): + nativeControl.Enabled = element.IsEnabled; + nativeControl.SelectedSegmentTintColor = GetTintColor(element); + break; + + case nameof(element.SelectedTextColor): + SetTextColor(); + break; + } + } + + private void SetTextColor() + { + //var color = Element.SelectedTextColor; + //UIColor c = color == default ? UIColor.LabelColor : color.ToUIColor(); + UIColor c = UIColor.LabelColor; + var attribute = new UITextAttributes + { + TextColor = c + }; + nativeControl.SetTitleTextAttributes(attribute, UIControlState.Selected); + attribute = new UITextAttributes + { + TextColor = c.ColorWithAlpha(.6f) + }; + nativeControl.SetTitleTextAttributes(attribute, UIControlState.Normal); + } + + private UIColor GetTintColor(SegmentedControl element) + { + if (element.IsEnabled) + { + //var tintColor = element.TintColor; + //if (tintColor == default) + //{ + return UIColor.SystemGray6Color; + //} + //else + //{ + // return tintColor.ToUIColor().ColorWithAlpha(.3f); + //} + } + else + { + //var disabledColor = element.DisabledColor; + //if (disabledColor == default) + //{ + return UIColor.SecondaryLabelColor; + //} + //else + //{ + // return disabledColor.ToUIColor(); + //} + } + } + + private void NativeControl_ValueChanged(object sender, EventArgs e) + { + Element.SelectedSegmentIndex = (int)nativeControl.SelectedSegment; + } + + protected override void Dispose(bool disposing) + { + if (nativeControl != null) + { + nativeControl.ValueChanged -= NativeControl_ValueChanged; + nativeControl.Dispose(); + nativeControl = null; + } + + base.Dispose(disposing); + } + } +}