using System; using Xamarin.Forms; namespace Billing.UI { public partial class BillingDate : ContentView { #region UI Properties public static readonly BindableProperty SundayProperty = Helper.Create(nameof(Sunday)); public static readonly BindableProperty MondayProperty = Helper.Create(nameof(Monday)); public static readonly BindableProperty TuesdayProperty = Helper.Create(nameof(Tuesday)); public static readonly BindableProperty WednesdayProperty = Helper.Create(nameof(Wednesday)); public static readonly BindableProperty ThursdayProperty = Helper.Create(nameof(Thursday)); public static readonly BindableProperty FridayProperty = Helper.Create(nameof(Friday)); public static readonly BindableProperty SaturdayProperty = Helper.Create(nameof(Saturday)); 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 = Helper.Create(nameof(LocatedDate), propertyChanged: OnLocatedDatePropertyChanged); public static readonly BindableProperty SelectedDateProperty = Helper.Create(nameof(SelectedDate), propertyChanged: OnSelectedDatePropertyChanged); private static void OnLocatedDatePropertyChanged(BillingDate billingDate, DateTime old, DateTime date) { var week = (int)date.DayOfWeek; var tmpDate = date.AddDays(-week); for (var i = 0; i < 7; i++) { var day = new BillingDay(tmpDate); billingDate[i] = day; tmpDate = tmpDate.AddDays(1); } } private static void OnSelectedDatePropertyChanged(BillingDate billingDate, DateTime old, DateTime selected) { for (var i = 0; i < 7; i++) { var day = billingDate[i]; day.Refresh(selected); } } public BillingDay this[int index] { get => (BillingDay)GetValue(GetWeekProperty(index)); set => SetValue(GetWeekProperty(index), value); } 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 (Helper.IsSameDay(selectedDate, SelectedDate)) { return; } DateTime located; if (locatedDate != null) { located = Helper.FirstDay(locatedDate.Value); } else { located = Helper.FirstDay(selectedDate); } if (LocatedDate != located) { LocatedDate = located; } else { RestoreState(this[(int)selectedDate.DayOfWeek]); } SelectedDate = selectedDate; DateSelected?.Invoke(this, new DateEventArgs(selectedDate)); } private void OnDayChanged(object o) { var selected = SelectedDate; if (o is BillingDay day && !Helper.IsSameDay(selected, day.Date)) { RestoreState(day); SelectedDate = day.Date; DateSelected?.Invoke(this, new DateEventArgs(day.Date)); } } private void RestoreState(BillingDay day) { 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; }); } } this.AbortAnimation("selected"); this.Animate("selected", v => { day.Opacity = v; }, start: 0, end: 1, easing: Easing.CubicOut, finished: (v, b) => { day.Opacity = 1; }); } 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; if (speed < -0.7) { LocatedDate = LocatedDate.AddDays(7); } else if (speed > 0.7) { LocatedDate = LocatedDate.AddDays(-7); } OnSelectedDatePropertyChanged(this, default, SelectedDate); } } } public class DateEventArgs : EventArgs { public DateTime Date { get; } public DateEventArgs(DateTime date) { Date = date; } } public class BillingDayView : ContentView { public static readonly BindableProperty BillingDayProperty = Helper.Create(nameof(BillingDay)); public BillingDay BillingDay { get => (BillingDay)GetValue(BillingDayProperty); set => SetValue(BillingDayProperty, value); } } public class BillingDay : BindableObject { public static readonly BindableProperty DateProperty = Helper.Create(nameof(Date), propertyChanged: OnDatePropertyChanged); public static readonly BindableProperty TextProperty = Helper.Create(nameof(Text)); public static readonly BindableProperty FontFamilyProperty = Helper.Create(nameof(FontFamily), defaultValue: Definition.RegularFontFamily); public static readonly BindableProperty IsSelectedProperty = Helper.Create(nameof(IsSelected), defaultValue: false); public static readonly BindableProperty OpacityProperty = Helper.Create(nameof(Opacity), defaultValue: 1.0); public static readonly BindableProperty TextOpacityProperty = Helper.Create(nameof(TextOpacity), defaultValue: 1.0); private static void OnDatePropertyChanged(BillingDay day, DateTime old, DateTime date) { if (date.Day == 1) { day.SetValue(TextProperty, date.ToString("MMM")); } else { day.SetValue(TextProperty, date.Day.ToString()); } } public DateTime Date => (DateTime)GetValue(DateProperty); 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) { SetValue(DateProperty, date); } public void Refresh(DateTime selected) { var date = Date; if (Helper.IsSameDay(date, selected)) { IsSelected = true; SetValue(FontFamilyProperty, Definition.BoldFontFamily); } else { SetValue(FontFamilyProperty, Definition.RegularFontFamily); } if (date.Year == selected.Year && date.Month == selected.Month) { SetValue(TextOpacityProperty, 1.0); } else { SetValue(TextOpacityProperty, .4); } } } }