Hello everybody!
_I realized the crutch! It works, but I would like to know better approaches to this task. _
I have Months that I want to display in CarouselPage. Here are the problems I faced:
1. At the start, display the second page, the first page on the left, and the third page on the right;
2. Assign a unique identifier to each page (in my case it was the date of the first day of each month);
3. Add pages if user swipe left or right;
4. Add ActivityIndicator to the entire CarouselPage (I added it in templates for each page);
5. Translate CarouselPage events into commands, I get an exception if I use Convac.Behaviors or my own EventToCommandBehavior class.
Month Carousel Page Xaml:
<?xml version="1.0" encoding="utf-8" ?>
<base:CarouselBasePage
x:Class="MDOSchedule.UI.Pages.AllJobs.CarouselAllJobsWeekPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:base="clr-namespace:MDOSchedule.UI.Pages.Base;assembly=MDOSchedule"
xmlns:templates="clr-namespace:MDOSchedule.UI.Templates;assembly=MDOSchedule"
xmlns:week="clr-namespace:MDOSchedule.UI.Views.Week;assembly=MDOSchedule"
x:Name="This"
Title="{Binding CurrentDate, StringFormat='{0:MMMM yyyy}'}"
BindingContextChanged="CarouselAllJobsWeekPage_OnBindingContextChanged"
CurrentPageChanged="CarouselAllJobsWeekPage_OnCurrentPageChanged"
ItemsSource="{Binding Weeks}"
PagesChanged="CarouselAllJobsWeekPage_OnPagesChanged">
<CarouselPage.ToolbarItems>
<ToolbarItem
Command="{Binding RefreshItemsCommand}"
Icon="ic_refresh.png"
Order="Primary" />
</CarouselPage.ToolbarItems>
<CarouselPage.ItemTemplate>
<DataTemplate>
<ContentPage Title="{Binding DateOfFirstDayOfWeek}">
<AbsoluteLayout>
<week:ScheduleWeekView
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All"
ItemTappedCommand="{Binding ItemTappedCommand}"
WeekItems="{Binding Days}" />
<templates:ActivityIndicatorTemplate BindingContext="{Binding Source={x:Reference This}, Path=BindingContext}" />
</AbsoluteLayout>
</ContentPage>
</DataTemplate>
</CarouselPage.ItemTemplate>
</base:CarouselBasePage>
Month Carousel Page Xaml.CS:
public partial class CarouselAllJobsMonthPage : CarouselBasePage
{
#region Private Fields
private CarouselAllJobsMonthViewModel _viewModel;
private bool _isInitialized;
#endregion
#region Init
public CarouselAllJobsMonthPage()
{
InitializeComponent();
}
#endregion
#region Events
private void CarouselAllJobsMonthPage_OnBindingContextChanged(object sender, EventArgs e)
{
_viewModel = BindingContext as CarouselAllJobsMonthViewModel;
}
private void CarouselAllJobsMonthPage_OnPagesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (!_isInitialized
&& Children.Count > 2)
{
CurrentPage = this.Children[1];
_isInitialized = true;
}
}
private void CarouselAllJobsMonthPage_OnCurrentPageChanged(object sender, EventArgs e)
{
_viewModel.CurrentPageChangedCommand.Execute(this.CurrentPage);
}
#endregion
}
My Carousel View Model:
public class CarouselAllJobsMonthViewModel : BaseViewModel
{
// Fields
#region Public Fields
public ObservableCollection<Month> Months
{
get => Get<ObservableCollection<Month>>();
set => Set(value);
}
public DateTime CurrentDate
{
get => Get<DateTime>();
set => Set(value);
}
#endregion
#region Private Fields
private DateTime _previousDate;
private int _pagesCount = 3;
private int _pageIndex = 1;
#endregion
// Methods
#region Commands
public ICommand RefreshItemsCommand =>
new Command(async () => await ShowLoading(RefreshItems));
public ICommand ItemTappedCommand =>
new Command<DayInfo>(DayTapped);
public ICommand CurrentPageChangedCommand =>
new Command<ContentPage>(async page => await LoadNextPage(page));
private async Task RefreshItems()
{
Months[_pageIndex].Weeks = GetMonth(CurrentDate).Weeks;
await Task.Delay(1500);
}
private void DayTapped(DayInfo obj)
{
}
private async Task LoadNextPage(ContentPage page)
{
if (!DateTime.TryParse(page?.Title,
DateTimeFormatInfo.InvariantInfo,
DateTimeStyles.None,
out DateTime newDate))
return;
if (newDate.DateEqualsByDay(CurrentDate))
return;
await ShowLoading(async () =>
{
// Calculate current position and Time
_previousDate = this.CurrentDate;
this.CurrentDate = newDate;
_pageIndex += newDate < _previousDate ? -1 : 1;
// Add to the head
if (newDate.DateEqualsByDay(Months[0].FirstDayOfMonth))
{
Months.Insert(0, GetMonth(CurrentDate.AddMonths(-1)));
_pagesCount++;
await Task.Delay(1500);
}
// Add to the tail
else if (newDate.DateEqualsByDay(Months[_pagesCount - 1].FirstDayOfMonth))
{
Months.Add(GetMonth(CurrentDate.AddMonths(1)));
_pagesCount++;
await Task.Delay(1500);
}
});
}
#endregion
#region Init
#endregion
#region Override Methods
public override async Task OnPageAppearing()
{
await ShowLoading(async () =>
{
Months = new ObservableCollection<Month>
{
GetMonth(DateTime.Now.AddMonths(-1)),
GetMonth(DateTime.Now),
GetMonth(DateTime.Now.AddMonths(1)),
};
CurrentDate = Months[0].FirstDayOfMonth;
});
}
#endregion
#region Private Methods
#endregion
#region Test
private Month GetMonth(DateTime monthTime)
{
var weeks = new List<Week>();
DateTime firstDayOfMonth = new DateTime(monthTime.Year, monthTime.Month, 1);
var lastMondayOfPreviousMonth = firstDayOfMonth;
while (lastMondayOfPreviousMonth.DayOfWeek != DayOfWeek.Monday)
lastMondayOfPreviousMonth = lastMondayOfPreviousMonth.AddDays(-1);
var currentDate = lastMondayOfPreviousMonth;
for (int i = 0; i < 6; i++)
{
var week = new Week { Days = GetDays(currentDate) };
weeks.Add(week);
currentDate = currentDate.AddDays(7);
}
return new Month
{
FirstDayOfMonth = firstDayOfMonth,
Weeks = weeks,
ItemTappedCommand = this.ItemTappedCommand
};
}
private readonly Random _random = new Random();
private List<DayInfo> GetDays(DateTime firstDay)
{
var days = new List<DayInfo>();
for (int i = 0; i < 7; i++)
{
var jobs = new List<JobObject>();
if (i != 2)
for (int j = 0; j < 10; j++)
{
jobs.Add(new JobObject()
{
Color = (j & _random.Next(3)) == 1 ? "#42f47d" : "#ff6677",
JobId = _random.Next(70),
Monteurs = new List<MonteurObject>()
{
new MonteurObject()
{
TeamName = "Tax"
}
}
});
}
days.Add(new DayInfo(firstDay.AddDays(i), jobs));
}
return days;
}
#endregion
}