diff --git a/Billing.Shared/Billing.Shared.projitems b/Billing.Shared/Billing.Shared.projitems
index a25db1d..7572722 100644
--- a/Billing.Shared/Billing.Shared.projitems
+++ b/Billing.Shared/Billing.Shared.projitems
@@ -38,6 +38,7 @@
+
AccountPage.xaml
diff --git a/Billing.Shared/UI/WrapLayout.cs b/Billing.Shared/UI/WrapLayout.cs
new file mode 100644
index 0000000..20ef995
--- /dev/null
+++ b/Billing.Shared/UI/WrapLayout.cs
@@ -0,0 +1,216 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Xamarin.Forms;
+
+namespace Billing.UI
+{
+ public class WrapLayout : Layout
+ {
+ public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(WrapLayout), propertyChanged: OnItemsSourcePropertyChanged);
+ public static readonly BindableProperty ColumnSpacingProperty = BindableProperty.Create(nameof(ColumnSpacing), typeof(double), typeof(WrapLayout), defaultValue: 5.0, propertyChanged: (obj, _, _) => ((WrapLayout)obj).InvalidateLayout());
+ public static readonly BindableProperty RowSpacingProperty = BindableProperty.Create(nameof(RowSpacing), typeof(double), typeof(WrapLayout), defaultValue: 5.0, propertyChanged: (obj, _, _) => ((WrapLayout)obj).InvalidateLayout());
+
+ private static void OnItemsSourcePropertyChanged(BindableObject obj, object old, object @new)
+ {
+ var itemTemplate = BindableLayout.GetItemTemplate(obj);
+ if (itemTemplate == null)
+ {
+ return;
+ }
+ var layout = (WrapLayout)obj;
+ if (@new == null)
+ {
+ layout.Children.Clear();
+ layout.InvalidateLayout();
+ }
+ else if (@new is IList list)
+ {
+ layout.freezed = true;
+ layout.Children.Clear();
+ foreach (var item in list)
+ {
+ var child = itemTemplate.CreateContent();
+ if (child is View view)
+ {
+ view.BindingContext = item;
+ layout.Children.Add(view);
+ }
+ }
+ layout.freezed = false;
+ layout.InvalidateLayout();
+ }
+ }
+
+ public IList ItemsSource
+ {
+ get => (IList)GetValue(ItemsSourceProperty);
+ set => SetValue(ItemsSourceProperty, value);
+ }
+ public double ColumnSpacing
+ {
+ get => (double)GetValue(ColumnSpacingProperty);
+ set => SetValue(ColumnSpacingProperty, value);
+ }
+ public double RowSpacing
+ {
+ get => (double)GetValue(RowSpacingProperty);
+ set => SetValue(RowSpacingProperty, value);
+ }
+
+ private readonly Dictionary layoutDataCache = new();
+
+ private bool freezed;
+
+ protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
+ {
+ var data = GetLayoutData(widthConstraint, heightConstraint);
+ if (data.VisibleChildrenCount == 0)
+ {
+ return new SizeRequest();
+ }
+ var totalSize = new Size(
+ data.CellSize.Width * data.Columns + ColumnSpacing * (data.Columns - 1),
+ data.CellSize.Height * data.Rows + RowSpacing * (data.Rows - 1));
+ return new SizeRequest(totalSize);
+ }
+
+ protected override void LayoutChildren(double x, double y, double width, double height)
+ {
+ if (freezed)
+ {
+ return;
+ }
+ var data = GetLayoutData(width, height);
+ if (data.VisibleChildrenCount == 0)
+ {
+ return;
+ }
+
+ double xChild = x;
+ double yChild = y;
+ int row = 0;
+ int column = 0;
+
+ var columnSpacing = ColumnSpacing;
+ var rowSpacing = RowSpacing;
+
+ foreach (View child in Children)
+ {
+ if (!child.IsVisible)
+ {
+ continue;
+ }
+ LayoutChildIntoBoundingRegion(child, new Rectangle(new Point(xChild, yChild), data.CellSize));
+ if (++column == data.Columns)
+ {
+ column = 0;
+ row++;
+ xChild = x;
+ yChild += rowSpacing + data.CellSize.Height;
+ }
+ else
+ {
+ xChild += columnSpacing + data.CellSize.Width;
+ }
+ }
+ }
+
+ protected override void InvalidateLayout()
+ {
+ base.InvalidateLayout();
+ layoutDataCache.Clear();
+ }
+
+ protected override void OnChildMeasureInvalidated()
+ {
+ base.OnChildMeasureInvalidated();
+ layoutDataCache.Clear();
+ }
+
+ private LayoutData GetLayoutData(double width, double height)
+ {
+ Size size = new(width, height);
+ if (layoutDataCache.ContainsKey(size))
+ {
+ return layoutDataCache[size];
+ }
+
+ int visibleChildrenCount = 0;
+ Size maxChildSize = new();
+ LayoutData layoutData;
+
+ foreach (View child in Children)
+ {
+ if (!child.IsVisible)
+ {
+ continue;
+ }
+ visibleChildrenCount++;
+ SizeRequest childSizeRequest = child.Measure(double.PositiveInfinity, double.PositiveInfinity);
+ maxChildSize.Width = Math.Max(maxChildSize.Width, childSizeRequest.Request.Width);
+ maxChildSize.Height = Math.Max(maxChildSize.Height, childSizeRequest.Request.Height);
+ }
+
+ if (visibleChildrenCount > 0)
+ {
+ var columnSpacing = ColumnSpacing;
+ int rows;
+ int columns;
+ if (double.IsPositiveInfinity(width))
+ {
+ columns = visibleChildrenCount;
+ rows = 1;
+ }
+ else
+ {
+ columns = (int)((width + columnSpacing) / (maxChildSize.Width + columnSpacing));
+ columns = Math.Max(1, columns);
+ rows = (visibleChildrenCount + columns - 1) / columns;
+ }
+ Size cellSize = new();
+ if (double.IsPositiveInfinity(width))
+ {
+ cellSize.Width = maxChildSize.Width;
+ }
+ else
+ {
+ cellSize.Width = (width - columnSpacing * (columns - 1)) / columns;
+ }
+ if (double.IsPositiveInfinity(height))
+ {
+ cellSize.Height = maxChildSize.Height;
+ }
+ else
+ {
+ cellSize.Height = (height - RowSpacing * (rows - 1)) / rows;
+ }
+ layoutData = new LayoutData(visibleChildrenCount, cellSize, rows, columns);
+ }
+ else
+ {
+ layoutData = new LayoutData();
+ }
+
+ layoutDataCache.Add(size, layoutData);
+ return layoutData;
+ }
+ }
+
+ public class LayoutData
+ {
+ public int VisibleChildrenCount { get; set; }
+ public Size CellSize { get; set; }
+ public int Rows { get; set; }
+ public int Columns { get; set; }
+
+ public LayoutData() { }
+ public LayoutData(int count, Size size, int rows, int columns)
+ {
+ VisibleChildrenCount = count;
+ CellSize = size;
+ Rows = rows;
+ Columns = columns;
+ }
+ }
+}