using Billing.Themes; using System; using Xamarin.Forms; namespace Billing.UI; public partial class BillingDate : ContentView { #region UI Properties private static readonly BindableProperty SundayProperty = BindableProperty.Create(nameof(Sunday), typeof(BillingDay), typeof(BillingDate)); private static readonly BindableProperty MondayProperty = BindableProperty.Create(nameof(Monday), typeof(BillingDay), typeof(BillingDate)); private static readonly BindableProperty TuesdayProperty = BindableProperty.Create(nameof(Tuesday), typeof(BillingDay), typeof(BillingDate)); private static readonly BindableProperty WednesdayProperty = BindableProperty.Create(nameof(Wednesday), typeof(BillingDay), typeof(BillingDate)); private static readonly BindableProperty ThursdayProperty = BindableProperty.Create(nameof(Thursday), typeof(BillingDay), typeof(BillingDate)); private static readonly BindableProperty FridayProperty = BindableProperty.Create(nameof(Friday), typeof(BillingDay), typeof(BillingDate)); private static readonly BindableProperty SaturdayProperty = BindableProperty.Create(nameof(Saturday), typeof(BillingDay), typeof(BillingDate)); public BillingDay Sunday => (BillingDay)GetValue(SundayProperty); public BillingDay Monday => (BillingDay)GetValue(MondayProperty); public BillingDay Tuesday => (BillingDay)GetValue(TuesdayProperty); public BillingDay Wednesday => (BillingDay)GetValue(WednesdayProperty); public BillingDay Thursday => (BillingDay)GetValue(ThursdayProperty); public BillingDay Friday => (BillingDay)GetValue(FridayProperty); public BillingDay Saturday => (BillingDay)GetValue(SaturdayProperty); #endregion private static BindableProperty GetWeekProperty(int week) { return (DayOfWeek)week switch { DayOfWeek.Monday => MondayProperty, DayOfWeek.Tuesday => TuesdayProperty, DayOfWeek.Wednesday => WednesdayProperty, DayOfWeek.Thursday => ThursdayProperty, DayOfWeek.Friday => FridayProperty, DayOfWeek.Saturday => SaturdayProperty, _ => SundayProperty }; } public static readonly BindableProperty LocatedDateProperty = BindableProperty.Create(nameof(LocatedDate), typeof(DateTime), typeof(BillingDate), propertyChanged: OnLocatedDatePropertyChanged); public static readonly BindableProperty SelectedDateProperty = BindableProperty.Create(nameof(SelectedDate), typeof(DateTime), typeof(BillingDate), propertyChanged: OnSelectedDatePropertyChanged); private static void OnLocatedDatePropertyChanged(BindableObject obj, object old, object @new) { if (obj is BillingDate billingDate && @new is DateTime date) { var week = (int)date.DayOfWeek; var tmpDate = date.AddDays(-week); for (var i = 0; i < 7; i++) { var prop = GetWeekProperty(i); var day = new BillingDay(tmpDate); billingDate.SetValue(prop, day); tmpDate = tmpDate.AddDays(1); } } } private static void OnSelectedDatePropertyChanged(BindableObject obj, object old, object @new) { if (obj is BillingDate billingDate && @new is DateTime selected) { for (var i = 0; i < 7; i++) { var prop = GetWeekProperty(i); var day = (BillingDay)billingDate.GetValue(prop); day.Refresh(selected); } } } public DateTime LocatedDate { get => (DateTime)GetValue(LocatedDateProperty); set => SetValue(LocatedDateProperty, value); } public DateTime SelectedDate { get => (DateTime)GetValue(SelectedDateProperty); set => SetValue(SelectedDateProperty, value); } public Command OnDayTapped { get; } public event EventHandler DateSelected; private DateTime lastDate; private double? x1; private double x2; public BillingDate() { OnDayTapped = new Command(OnDayChanged); InitializeComponent(); } public void SetDateTime(DateTime selectedDate, DateTime? locatedDate = null) { if (locatedDate != null) { LocatedDate = locatedDate.Value; } else { LocatedDate = selectedDate; } SelectedDate = selectedDate; DateSelected?.Invoke(this, new DateEventArgs(selectedDate)); } private void OnDayChanged(object o) { var selected = SelectedDate; if (o is BillingDay day && (selected.Year != day.Date.Year || selected.DayOfYear != day.Date.DayOfYear)) { for (var i = 0; i < 7; i++) { var d = (BillingDay)GetValue(GetWeekProperty(i)); if (d.IsSelected) { this.AbortAnimation("unselected"); this.Animate("unselected", v => { d.Opacity = v; }, start: 1, end: 0, easing: Easing.CubicOut, finished: (v, b) => { d.Opacity = 0; d.IsSelected = false; }); } } SelectedDate = day.Date; this.AbortAnimation("selected"); this.Animate("selected", v => { day.Opacity = v; }, start: 0, end: 1, easing: Easing.CubicOut, finished: (v, b) => { day.Opacity = 1; }); DateSelected?.Invoke(this, new DateEventArgs(day.Date)); } } private void OnPanUpdated(object sender, PanUpdatedEventArgs e) { if (e.StatusType == GestureStatus.Started) { lastDate = DateTime.Now; x1 = null; } else if (e.StatusType == GestureStatus.Running) { if (x1 == null) { x1 = e.TotalX; } x2 = e.TotalX; } else if (e.StatusType == GestureStatus.Completed) { if (x1 == null) { return; } var totalX = x2 - x1.Value; x1 = null; var ms = (DateTime.Now - lastDate).TotalMilliseconds; var speed = totalX / ms; Helper.Debug($"completed, speed: {speed}"); if (speed < -0.7) { LocatedDate = LocatedDate.AddDays(7); } else if (speed > 0.7) { LocatedDate = LocatedDate.AddDays(-7); } OnSelectedDatePropertyChanged(this, null, SelectedDate); } } } public class DateEventArgs : EventArgs { public DateTime Date { get; } public DateEventArgs(DateTime date) { Date = date; } } public class BillingDayView : ContentView { public static readonly BindableProperty BillingDayProperty = BindableProperty.Create(nameof(BillingDay), typeof(BillingDay), typeof(BillingDayView)); public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(Command), typeof(BillingDayView)); public BillingDay BillingDay { get => (BillingDay)GetValue(BillingDayProperty); set => SetValue(BillingDayProperty, value); } public Command Command { get => (Command)GetValue(CommandProperty); set => SetValue(CommandProperty, value); } } public class BillingDay : BindableObject { private static readonly BindableProperty DateProperty = BindableProperty.Create(nameof(Date), typeof(DateTime), typeof(BillingDay), propertyChanged: OnDatePropertyChanged); private static readonly BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(BillingDay)); private static readonly BindableProperty FontFamilyProperty = BindableProperty.Create(nameof(FontFamily), typeof(string), typeof(BillingDay), defaultValue: Definition.GetCascadiaRegularFontFamily()); private static readonly BindableProperty IsSelectedProperty = BindableProperty.Create(nameof(IsSelected), typeof(bool), typeof(BillingDay)); private static readonly BindableProperty OpacityProperty = BindableProperty.Create(nameof(Opacity), typeof(double), typeof(BillingDay), defaultValue: 1.0); private static readonly BindableProperty TextOpacityProperty = BindableProperty.Create(nameof(TextOpacity), typeof(double), typeof(BillingDay), defaultValue: 1.0); private static void OnDatePropertyChanged(BindableObject obj, object old, object @new) { if (obj is BillingDay day && @new is DateTime date) { if (date.Day == 1) { day.SetValue(TextProperty, date.ToString("MMM")); } else { day.SetValue(TextProperty, date.Day.ToString()); } } } public DateTime Date { get => (DateTime)GetValue(DateProperty); set => SetValue(DateProperty, value); } public string Text => (string)GetValue(TextProperty); public string FontFamily => (string)GetValue(FontFamilyProperty); public bool IsSelected { get => (bool)GetValue(IsSelectedProperty); set => SetValue(IsSelectedProperty, value); } public double Opacity { get => (double)GetValue(OpacityProperty); set => SetValue(OpacityProperty, value); } public double TextOpacity => (double)GetValue(TextOpacityProperty); public BillingDay(DateTime date) { Date = date; } public void Refresh(DateTime selected) { var date = Date; if (date.Year == selected.Year && date.DayOfYear == selected.DayOfYear) { SetValue(IsSelectedProperty, true); SetValue(FontFamilyProperty, Definition.GetCascadiaBoldFontFamily()); } else { SetValue(FontFamilyProperty, Definition.GetCascadiaRegularFontFamily()); } if (date.Year == selected.Year && date.Month == selected.Month) { SetValue(TextOpacityProperty, 1.0); } else { SetValue(TextOpacityProperty, .4); } } }