diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj
index 43800d681..f883a241e 100644
--- a/src/Android/Android.csproj
+++ b/src/Android/Android.csproj
@@ -63,8 +63,15 @@
+
+
+
+
+
+
+
@@ -194,5 +201,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Android/Renderers/BoxedView/BoxedViewRecyclerAdapter.cs b/src/Android/Renderers/BoxedView/BoxedViewRecyclerAdapter.cs
new file mode 100644
index 000000000..ca91f39f6
--- /dev/null
+++ b/src/Android/Renderers/BoxedView/BoxedViewRecyclerAdapter.cs
@@ -0,0 +1,532 @@
+using Android.Content;
+using Android.Runtime;
+using Android.Support.V7.Widget;
+using Android.Text;
+using Android.Views;
+using Android.Widget;
+using Bit.App.Controls.BoxedView;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Android;
+using AView = Android.Views.View;
+
+namespace Bit.Droid.Renderers
+{
+ [Preserve(AllMembers = true)]
+ public class BoxedViewRecyclerAdapter : RecyclerView.Adapter, AView.IOnClickListener
+ {
+ private const int ViewTypeHeader = 0;
+ private const int ViewTypeFooter = 1;
+
+ private float MinRowHeight => _context.ToPixels(44);
+
+ private Dictionary _viewTypes;
+ private List _cellCaches;
+
+ internal List CellCaches
+ {
+ get
+ {
+ if(_cellCaches == null)
+ {
+ FillCache();
+ }
+ return _cellCaches;
+ }
+ }
+
+ // Item click. correspond to AdapterView.IOnItemClickListener
+ private int _selectedIndex = -1;
+ private AView _preSelectedCell = null;
+
+ Context _context;
+ BoxedView _boxedView;
+ RecyclerView _recyclerView;
+
+ List _viewHolders = new List();
+
+ public BoxedViewRecyclerAdapter(Context context, BoxedView boxedView, RecyclerView recyclerView)
+ {
+ _context = context;
+ _boxedView = boxedView;
+ _recyclerView = recyclerView;
+
+ _boxedView.ModelChanged += _boxedView_ModelChanged;
+ }
+
+ void _boxedView_ModelChanged(object sender, EventArgs e)
+ {
+ if(_recyclerView != null)
+ {
+ _cellCaches = null;
+ NotifyDataSetChanged();
+ }
+ }
+
+ public override int ItemCount => CellCaches.Count;
+
+ public override long GetItemId(int position)
+ {
+ return position;
+ }
+
+ public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
+ {
+ ViewHolder viewHolder;
+ switch(viewType)
+ {
+ case ViewTypeHeader:
+ viewHolder = new HeaderViewHolder(
+ LayoutInflater.FromContext(_context).Inflate(Resource.Layout.HeaderCell, parent, false),
+ _boxedView);
+ break;
+ case ViewTypeFooter:
+ viewHolder = new FooterViewHolder(
+ LayoutInflater.FromContext(_context).Inflate(Resource.Layout.FooterCell, parent, false),
+ _boxedView);
+ break;
+ default:
+ viewHolder = new ContentViewHolder(
+ LayoutInflater.FromContext(_context).Inflate(Resource.Layout.ContentCell, parent, false));
+ viewHolder.ItemView.SetOnClickListener(this);
+ break;
+ }
+ _viewHolders.Add(viewHolder);
+ return viewHolder;
+ }
+
+ public void OnClick(AView view)
+ {
+ var position = _recyclerView.GetChildAdapterPosition(view);
+
+ //TODO: It is desirable that the forms side has Selected property and reflects it.
+ // But do it at a later as iOS side doesn't have that process.
+ DeselectRow();
+
+ var cell = view.FindViewById(Resource.Id.ContentCellBody).GetChildAt(0) as BaseCellView;
+
+
+ if(cell == null || !CellCaches[position].Cell.IsEnabled)
+ {
+ //if FormsCell IsEnable is false, does nothing.
+ return;
+ }
+
+ _boxedView.Model.RowSelected(CellCaches[position].Cell);
+
+ cell.RowSelected(this, position);
+ }
+
+ public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
+ {
+ var cellInfo = CellCaches[position];
+
+ switch(holder.ItemViewType)
+ {
+ case ViewTypeHeader:
+ BindHeaderView((HeaderViewHolder)holder, (TextCell)cellInfo.Cell);
+ break;
+ case ViewTypeFooter:
+ BindFooterView((FooterViewHolder)holder, (TextCell)cellInfo.Cell);
+ break;
+ default:
+ BindContentView((ContentViewHolder)holder, cellInfo.Cell, position);
+ break;
+ }
+ }
+
+ public override int GetItemViewType(int position)
+ {
+ var cellInfo = CellCaches[position];
+ if(cellInfo.IsHeader)
+ {
+ return ViewTypeHeader;
+ }
+ else if(cellInfo.IsFooter)
+ {
+ return ViewTypeFooter;
+ }
+ else
+ {
+ return _viewTypes[cellInfo.Cell.GetType()];
+ }
+ }
+
+ public void DeselectRow()
+ {
+ if(_preSelectedCell != null)
+ {
+ _preSelectedCell.Selected = false;
+ _preSelectedCell = null;
+ }
+ _selectedIndex = -1;
+ }
+
+ public void SelectedRow(AView cell, int position)
+ {
+ _preSelectedCell = cell;
+ _selectedIndex = position;
+ cell.Selected = true;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if(disposing)
+ {
+ _boxedView.ModelChanged -= _boxedView_ModelChanged;
+ _cellCaches?.Clear();
+ _cellCaches = null;
+ _boxedView = null;
+ _viewTypes = null;
+ foreach(var holder in _viewHolders)
+ {
+ holder.Dispose();
+ }
+ _viewHolders.Clear();
+ _viewHolders = null;
+ }
+ base.Dispose(disposing);
+ }
+
+
+ private void BindHeaderView(HeaderViewHolder holder, TextCell formsCell)
+ {
+ var view = holder.ItemView;
+
+ //judging cell height
+ int cellHeight = (int)_context.ToPixels(44);
+ var individualHeight = formsCell.Height;
+
+ if(individualHeight > 0d)
+ {
+ cellHeight = (int)_context.ToPixels(individualHeight);
+ }
+ else if(_boxedView.HeaderHeight > -1)
+ {
+ cellHeight = (int)_context.ToPixels(_boxedView.HeaderHeight);
+ }
+
+ view.SetMinimumHeight(cellHeight);
+ view.LayoutParameters.Height = cellHeight;
+
+ //textview setting
+ holder.TextView.SetPadding(
+ (int)view.Context.ToPixels(_boxedView.HeaderPadding.Left),
+ (int)view.Context.ToPixels(_boxedView.HeaderPadding.Top),
+ (int)view.Context.ToPixels(_boxedView.HeaderPadding.Right),
+ (int)view.Context.ToPixels(_boxedView.HeaderPadding.Bottom));
+
+ holder.TextView.Gravity = _boxedView.HeaderTextVerticalAlign.ToAndroidVertical() | GravityFlags.Left;
+ holder.TextView.TextAlignment = Android.Views.TextAlignment.Gravity;
+ holder.TextView.SetTextSize(Android.Util.ComplexUnitType.Sp, (float)_boxedView.HeaderFontSize);
+ holder.TextView.SetBackgroundColor(_boxedView.HeaderBackgroundColor.ToAndroid());
+ holder.TextView.SetMaxLines(1);
+ holder.TextView.SetMinLines(1);
+ holder.TextView.Ellipsize = TextUtils.TruncateAt.End;
+
+ if(_boxedView.HeaderTextColor != Color.Default)
+ {
+ holder.TextView.SetTextColor(_boxedView.HeaderTextColor.ToAndroid());
+ }
+
+ //border setting
+ if(_boxedView.ShowSectionTopBottomBorder)
+ {
+ holder.Border.SetBackgroundColor(_boxedView.SeparatorColor.ToAndroid());
+ }
+ else
+ {
+ holder.Border.SetBackgroundColor(Android.Graphics.Color.Transparent);
+ }
+
+ //update text
+ holder.TextView.Text = formsCell.Text;
+ }
+
+ private void BindFooterView(FooterViewHolder holder, TextCell formsCell)
+ {
+ var view = holder.ItemView;
+
+ //footer visible setting
+ if(string.IsNullOrEmpty(formsCell.Text))
+ {
+ //if text is empty, hidden (height 0)
+ holder.TextView.Visibility = ViewStates.Gone;
+ view.Visibility = ViewStates.Gone;
+ }
+ else
+ {
+ holder.TextView.Visibility = ViewStates.Visible;
+ view.Visibility = ViewStates.Visible;
+ }
+
+ //textview setting
+ holder.TextView.SetPadding(
+ (int)view.Context.ToPixels(_boxedView.FooterPadding.Left),
+ (int)view.Context.ToPixels(_boxedView.FooterPadding.Top),
+ (int)view.Context.ToPixels(_boxedView.FooterPadding.Right),
+ (int)view.Context.ToPixels(_boxedView.FooterPadding.Bottom));
+
+ holder.TextView.SetTextSize(Android.Util.ComplexUnitType.Sp, (float)_boxedView.FooterFontSize);
+ holder.TextView.SetBackgroundColor(_boxedView.FooterBackgroundColor.ToAndroid());
+ if(_boxedView.FooterTextColor != Color.Default)
+ {
+ holder.TextView.SetTextColor(_boxedView.FooterTextColor.ToAndroid());
+ }
+
+ //update text
+ holder.TextView.Text = formsCell.Text;
+ }
+
+ private void BindContentView(ContentViewHolder holder, Cell formsCell, int position)
+ {
+ AView nativeCell = null;
+ AView layout = holder.ItemView;
+
+ holder.SectionIndex = CellCaches[position].SectionIndex;
+ holder.RowIndex = CellCaches[position].RowIndex;
+
+ nativeCell = holder.Body.GetChildAt(0);
+ if(nativeCell != null)
+ {
+ holder.Body.RemoveViewAt(0);
+ }
+
+ nativeCell = CellFactory.GetCell(formsCell, nativeCell, _recyclerView, _context, _boxedView);
+
+ if(position == _selectedIndex)
+ {
+ DeselectRow();
+ nativeCell.Selected = true;
+ _preSelectedCell = nativeCell;
+ }
+
+ var minHeight = (int)Math.Max(_context.ToPixels(_boxedView.RowHeight), MinRowHeight);
+
+ //it is neccesary to set both
+ layout.SetMinimumHeight(minHeight);
+ nativeCell.SetMinimumHeight(minHeight);
+
+ if(!_boxedView.HasUnevenRows)
+ {
+ // if not Uneven, set the larger one of RowHeight and MinRowHeight.
+ layout.LayoutParameters.Height = minHeight;
+ }
+ else if(formsCell.Height > -1)
+ {
+ // if the cell itself was specified height, set it.
+ layout.SetMinimumHeight((int)_context.ToPixels(formsCell.Height));
+ layout.LayoutParameters.Height = (int)_context.ToPixels(formsCell.Height);
+ }
+ else if(formsCell is ViewCell viewCell)
+ {
+ // if used a viewcell, calculate the size and layout it.
+ var size = viewCell.View.Measure(_boxedView.Width, double.PositiveInfinity);
+ viewCell.View.Layout(new Rectangle(0, 0, size.Request.Width, size.Request.Height));
+ layout.LayoutParameters.Height = (int)_context.ToPixels(size.Request.Height);
+ }
+ else
+ {
+ layout.LayoutParameters.Height = -2; //wrap_content
+ }
+
+ if(!CellCaches[position].IsLastCell || _boxedView.ShowSectionTopBottomBorder)
+ {
+ holder.Border.SetBackgroundColor(_boxedView.SeparatorColor.ToAndroid());
+ }
+ else
+ {
+ holder.Border.SetBackgroundColor(Android.Graphics.Color.Transparent);
+ }
+
+ holder.Body.AddView(nativeCell, 0);
+ }
+
+ private void FillCache()
+ {
+ var model = _boxedView.Model;
+ int sectionCount = model.GetSectionCount();
+
+ var newCellCaches = new List();
+ for(var sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++)
+ {
+ var sectionTitle = model.GetSectionTitle(sectionIndex);
+ var sectionRowCount = model.GetRowCount(sectionIndex);
+
+ Cell headerCell = new TextCell { Text = sectionTitle, Height = model.GetHeaderHeight(sectionIndex) };
+ headerCell.Parent = _boxedView;
+
+ newCellCaches.Add(new CellCache
+ {
+ Cell = headerCell,
+ IsHeader = true,
+ SectionIndex = sectionIndex,
+ });
+
+ for(int i = 0; i < sectionRowCount; i++)
+ {
+ newCellCaches.Add(new CellCache
+ {
+ Cell = model.GetCell(sectionIndex, i),
+ IsLastCell = i == sectionRowCount - 1,
+ SectionIndex = sectionIndex,
+ RowIndex = i
+ });
+ }
+
+ var footerCell = new TextCell { Text = model.GetFooterText(sectionIndex) };
+ footerCell.Parent = _boxedView;
+
+ newCellCaches.Add(new CellCache
+ {
+ Cell = footerCell,
+ IsFooter = true,
+ SectionIndex = sectionIndex,
+ });
+ }
+
+ _cellCaches = newCellCaches;
+
+ if(_viewTypes == null)
+ {
+ _viewTypes = _cellCaches
+ .Select(x => x.Cell.GetType())
+ .Distinct()
+ .Select((x, idx) => new { x, index = idx })
+ .ToDictionary(key => key.x, val => val.index + 2);
+ }
+ else
+ {
+ var idx = _viewTypes.Values.Max() + 1;
+ foreach(var t in _cellCaches.Select(x => x.Cell.GetType()).Distinct().Except(_viewTypes.Keys).ToList())
+ {
+ _viewTypes.Add(t, idx++);
+ }
+ }
+ }
+
+ public void CellMoved(int fromPos, int toPos)
+ {
+ var tmp = CellCaches[fromPos];
+ CellCaches.RemoveAt(fromPos);
+ CellCaches.Insert(toPos, tmp);
+ }
+
+ [Preserve(AllMembers = true)]
+ internal class CellCache
+ {
+ public Cell Cell { get; set; }
+ public bool IsHeader { get; set; } = false;
+ public bool IsFooter { get; set; } = false;
+ public bool IsLastCell { get; set; } = false;
+ public int SectionIndex { get; set; }
+ public int RowIndex { get; set; }
+ }
+ }
+
+ [Preserve(AllMembers = true)]
+ internal class ViewHolder : RecyclerView.ViewHolder
+ {
+ public ViewHolder(AView view)
+ : base(view) { }
+
+ protected override void Dispose(bool disposing)
+ {
+ if(disposing)
+ {
+ ItemView?.Dispose();
+ ItemView = null;
+ }
+ base.Dispose(disposing);
+ }
+ }
+
+ [Preserve(AllMembers = true)]
+ internal class HeaderViewHolder : ViewHolder
+ {
+ public HeaderViewHolder(AView view, BoxedView boxedView)
+ : base(view)
+ {
+ TextView = view.FindViewById(Resource.Id.HeaderCellText);
+ Border = view.FindViewById(Resource.Id.HeaderCellBorder);
+ }
+
+ public TextView TextView { get; private set; }
+ public LinearLayout Border { get; private set; }
+
+ protected override void Dispose(bool disposing)
+ {
+ if(disposing)
+ {
+ TextView?.Dispose();
+ TextView = null;
+ Border?.Dispose();
+ Border = null;
+ }
+ base.Dispose(disposing);
+ }
+ }
+
+ [Preserve(AllMembers = true)]
+ internal class FooterViewHolder : ViewHolder
+ {
+ public TextView TextView { get; private set; }
+
+ public FooterViewHolder(AView view, BoxedView boxedView)
+ : base(view)
+ {
+ TextView = view.FindViewById(Resource.Id.FooterCellText);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if(disposing)
+ {
+ TextView?.Dispose();
+ TextView = null;
+ }
+ base.Dispose(disposing);
+ }
+ }
+
+ [Preserve(AllMembers = true)]
+ internal class ContentViewHolder : ViewHolder
+ {
+ public LinearLayout Body { get; private set; }
+ public AView Border { get; private set; }
+ public int SectionIndex { get; set; }
+ public int RowIndex { get; set; }
+
+ public ContentViewHolder(AView view)
+ : base(view)
+ {
+ Body = view.FindViewById(Resource.Id.ContentCellBody);
+ Border = view.FindViewById(Resource.Id.ContentCellBorder);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if(disposing)
+ {
+ var nativeCell = Body.GetChildAt(0);
+ if(nativeCell is INativeElementView nativeElementView)
+ {
+ // If a ViewCell is used, it stops the ViewCellContainer from executing the dispose method.
+ // Because if the AiForms.Effects is used and a ViewCellContainer is disposed, it crashes.
+ if(!(nativeElementView.Element is ViewCell))
+ {
+ nativeCell?.Dispose();
+ }
+ }
+ Border?.Dispose();
+ Border = null;
+ Body?.Dispose();
+ Body = null;
+ ItemView.SetOnClickListener(null);
+ }
+ base.Dispose(disposing);
+ }
+ }
+}
diff --git a/src/Android/Renderers/BoxedView/BoxedViewRenderer.cs b/src/Android/Renderers/BoxedView/BoxedViewRenderer.cs
new file mode 100644
index 000000000..6842682ac
--- /dev/null
+++ b/src/Android/Renderers/BoxedView/BoxedViewRenderer.cs
@@ -0,0 +1,195 @@
+using Android.Content;
+using Android.Runtime;
+using Android.Support.V7.Widget;
+using Android.Support.V7.Widget.Helper;
+using Android.Views;
+using Bit.App.Controls.BoxedView;
+using Bit.Droid.Renderers;
+using System;
+using System.ComponentModel;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Android;
+
+[assembly: ExportRenderer(typeof(BoxedView), typeof(BoxedViewRenderer))]
+namespace Bit.Droid.Renderers
+{
+ [Preserve(AllMembers = true)]
+ public class BoxedViewRenderer : ViewRenderer
+ {
+ private Page _parentPage;
+ private LinearLayoutManager _layoutManager;
+ private ItemTouchHelper _itemTouchhelper;
+ private BoxedViewRecyclerAdapter _adapter;
+ private BoxedViewSimpleCallback _simpleCallback;
+
+ public BoxedViewRenderer(Context context)
+ : base(context)
+ {
+ AutoPackage = false;
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs e)
+ {
+ base.OnElementChanged(e);
+ if(e.NewElement != null)
+ {
+ var recyclerView = new RecyclerView(Context);
+ _layoutManager = new LinearLayoutManager(Context);
+ recyclerView.SetLayoutManager(_layoutManager);
+
+ SetNativeControl(recyclerView);
+
+ Control.Focusable = false;
+ Control.DescendantFocusability = DescendantFocusability.AfterDescendants;
+
+ UpdateBackgroundColor();
+ UpdateRowHeight();
+
+ _adapter = new BoxedViewRecyclerAdapter(Context, e.NewElement, recyclerView);
+ Control.SetAdapter(_adapter);
+
+ _simpleCallback = new BoxedViewSimpleCallback(e.NewElement, ItemTouchHelper.Up | ItemTouchHelper.Down, 0);
+ _itemTouchhelper = new ItemTouchHelper(_simpleCallback);
+ _itemTouchhelper.AttachToRecyclerView(Control);
+
+ Element elm = Element;
+ while(elm != null)
+ {
+ elm = elm.Parent;
+ if(elm is Page)
+ {
+ break;
+ }
+ }
+
+ _parentPage = elm as Page;
+ _parentPage.Appearing += ParentPageAppearing;
+ }
+ }
+
+ private void ParentPageAppearing(object sender, EventArgs e)
+ {
+ Device.BeginInvokeOnMainThread(() => _adapter.DeselectRow());
+ }
+
+ protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
+ {
+ base.OnLayout(changed, left, top, right, bottom);
+ if(!changed)
+ {
+ return;
+ }
+
+ var startPos = _layoutManager.FindFirstCompletelyVisibleItemPosition();
+ var endPos = _layoutManager.FindLastCompletelyVisibleItemPosition();
+
+ var totalH = 0;
+ for(var i = startPos; i <= endPos; i++)
+ {
+ var child = _layoutManager.GetChildAt(i);
+ if(child == null)
+ {
+ return;
+ }
+
+ totalH += _layoutManager.GetChildAt(i).Height;
+ }
+ Element.VisibleContentHeight = Context.FromPixels(Math.Min(totalH, Control.Height));
+ }
+
+ protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.OnElementPropertyChanged(sender, e);
+ if(e.PropertyName == BoxedView.SeparatorColorProperty.PropertyName)
+ {
+ _adapter.NotifyDataSetChanged();
+ }
+ else if(e.PropertyName == BoxedView.BackgroundColorProperty.PropertyName)
+ {
+ UpdateBackgroundColor();
+ }
+ else if(e.PropertyName == TableView.RowHeightProperty.PropertyName)
+ {
+ UpdateRowHeight();
+ }
+ else if(e.PropertyName == BoxedView.SelectedColorProperty.PropertyName)
+ {
+ //_adapter.NotifyDataSetChanged();
+ }
+ else if(e.PropertyName == BoxedView.ShowSectionTopBottomBorderProperty.PropertyName)
+ {
+ _adapter.NotifyDataSetChanged();
+ }
+ else if(e.PropertyName == TableView.HasUnevenRowsProperty.PropertyName)
+ {
+ _adapter.NotifyDataSetChanged();
+ }
+ else if(e.PropertyName == BoxedView.ScrollToTopProperty.PropertyName)
+ {
+ UpdateScrollToTop();
+ }
+ else if(e.PropertyName == BoxedView.ScrollToBottomProperty.PropertyName)
+ {
+ UpdateScrollToBottom();
+ }
+ }
+
+ private void UpdateRowHeight()
+ {
+ if(Element.RowHeight == -1)
+ {
+ Element.RowHeight = 60;
+ }
+ else
+ {
+ _adapter?.NotifyDataSetChanged();
+ }
+ }
+
+ private void UpdateScrollToTop()
+ {
+ if(Element.ScrollToTop)
+ {
+ _layoutManager.ScrollToPosition(0);
+ Element.ScrollToTop = false;
+ }
+ }
+
+ private void UpdateScrollToBottom()
+ {
+ if(Element.ScrollToBottom)
+ {
+ if(_adapter != null)
+ {
+ _layoutManager.ScrollToPosition(_adapter.ItemCount - 1);
+ }
+ Element.ScrollToBottom = false;
+ }
+ }
+
+ protected new void UpdateBackgroundColor()
+ {
+ if(Element.BackgroundColor != Color.Default)
+ {
+ Control.SetBackgroundColor(Element.BackgroundColor.ToAndroid());
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if(disposing)
+ {
+ _parentPage.Appearing -= ParentPageAppearing;
+ _adapter?.Dispose();
+ _adapter = null;
+ _layoutManager?.Dispose();
+ _layoutManager = null;
+ _simpleCallback?.Dispose();
+ _simpleCallback = null;
+ _itemTouchhelper?.Dispose();
+ _itemTouchhelper = null;
+ }
+ base.Dispose(disposing);
+ }
+ }
+}
diff --git a/src/Android/Renderers/BoxedView/BoxedViewSimpleCallback.cs b/src/Android/Renderers/BoxedView/BoxedViewSimpleCallback.cs
new file mode 100644
index 000000000..d505e18d9
--- /dev/null
+++ b/src/Android/Renderers/BoxedView/BoxedViewSimpleCallback.cs
@@ -0,0 +1,104 @@
+using Android.Runtime;
+using Android.Support.V7.Widget;
+using Android.Support.V7.Widget.Helper;
+using Bit.App.Controls.BoxedView;
+using System;
+
+namespace Bit.Droid.Renderers
+{
+ [Preserve(AllMembers = true)]
+ public class BoxedViewSimpleCallback : ItemTouchHelper.SimpleCallback
+ {
+ private BoxedView _boxedView;
+ private int _offset = 0;
+
+ public BoxedViewSimpleCallback(BoxedView boxedView, int dragDirs, int swipeDirs)
+ : base(dragDirs, swipeDirs)
+ {
+ _boxedView = boxedView;
+ }
+
+ public override bool OnMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target)
+ {
+ if(!(viewHolder is ContentViewHolder fromContentHolder))
+ {
+ return false;
+ }
+ if(!(target is ContentViewHolder toContentHolder))
+ {
+ return false;
+ }
+ if(fromContentHolder.SectionIndex != toContentHolder.SectionIndex)
+ {
+ return false;
+ }
+ var section = _boxedView.Model.GetSection(fromContentHolder.SectionIndex);
+ if(section == null || !section.UseDragSort)
+ {
+ return false;
+ }
+
+ var fromPos = viewHolder.AdapterPosition;
+ var toPos = target.AdapterPosition;
+ _offset += toPos - fromPos;
+ var settingsAdapter = recyclerView.GetAdapter() as BoxedViewRecyclerAdapter;
+ settingsAdapter.NotifyItemMoved(fromPos, toPos); // rows update
+ settingsAdapter.CellMoved(fromPos, toPos); // caches update
+ return true;
+ }
+
+ public override void ClearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
+ {
+ base.ClearView(recyclerView, viewHolder);
+ if(!(viewHolder is ContentViewHolder contentHolder))
+ {
+ return;
+ }
+
+ var section = _boxedView.Model.GetSection(contentHolder.SectionIndex);
+ var pos = contentHolder.RowIndex;
+ if(section.ItemsSource == null)
+ {
+ var tmp = section[pos];
+ section.RemoveAt(pos);
+ section.Insert(pos + _offset, tmp);
+ }
+ else if(section.ItemsSource != null)
+ {
+ // must update DataSource at this timing.
+ var tmp = section.ItemsSource[pos];
+ section.ItemsSource.RemoveAt(pos);
+ section.ItemsSource.Insert(pos + _offset, tmp);
+ }
+ _offset = 0;
+ }
+
+ public override int GetDragDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
+ {
+ if(!(viewHolder is ContentViewHolder contentHolder))
+ {
+ return 0;
+ }
+ var section = _boxedView.Model.GetSection(contentHolder.SectionIndex);
+ if(section == null || !section.UseDragSort)
+ {
+ return 0;
+ }
+ return base.GetDragDirs(recyclerView, viewHolder);
+ }
+
+ public override void OnSwiped(RecyclerView.ViewHolder viewHolder, int direction)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if(disposing)
+ {
+ _boxedView = null;
+ }
+ base.Dispose(disposing);
+ }
+ }
+}
diff --git a/src/Android/Renderers/BoxedView/Cells/BaseCellRenderer.cs b/src/Android/Renderers/BoxedView/Cells/BaseCellRenderer.cs
new file mode 100644
index 000000000..2b92bddc6
--- /dev/null
+++ b/src/Android/Renderers/BoxedView/Cells/BaseCellRenderer.cs
@@ -0,0 +1,76 @@
+using Android.Content;
+using Android.Runtime;
+using Android.Views;
+using Bit.App.Controls.BoxedView;
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using Xamarin.Forms.Platform.Android;
+
+namespace Bit.Droid.Renderers
+{
+ [Preserve(AllMembers = true)]
+ public class BaseCellRenderer : CellRenderer where TNativeCell : BaseCellView
+ {
+ internal static class InstanceCreator
+ {
+ public static Func Create { get; } = CreateInstance();
+
+ private static Func CreateInstance()
+ {
+ var argsTypes = new[] { typeof(T1), typeof(T2) };
+ var constructor = typeof(TInstance).GetConstructor(
+ BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
+ Type.DefaultBinder, argsTypes, null);
+ var args = argsTypes.Select(Expression.Parameter).ToArray();
+ return Expression.Lambda>(Expression.New(constructor, args), args).Compile();
+ }
+ }
+
+ protected override View GetCellCore(Xamarin.Forms.Cell item, View convertView, ViewGroup parent,
+ Context context)
+ {
+ if(!(convertView is TNativeCell nativeCell))
+ {
+ nativeCell = InstanceCreator.Create(context, item);
+ }
+
+ ClearPropertyChanged(nativeCell);
+ nativeCell.Cell = item;
+ SetUpPropertyChanged(nativeCell);
+ nativeCell.UpdateCell();
+ return nativeCell;
+ }
+
+ protected void SetUpPropertyChanged(BaseCellView nativeCell)
+ {
+ var formsCell = nativeCell.Cell as BaseCell;
+ formsCell.PropertyChanged += nativeCell.CellPropertyChanged;
+ if(formsCell.Parent is BoxedView parentElement)
+ {
+ parentElement.PropertyChanged += nativeCell.ParentPropertyChanged;
+ var section = parentElement.Model.GetSection(BoxedModel.GetPath(formsCell).Item1);
+ if(section != null)
+ {
+ formsCell.Section = section;
+ formsCell.Section.PropertyChanged += nativeCell.SectionPropertyChanged;
+ }
+ }
+ }
+
+ private void ClearPropertyChanged(BaseCellView nativeCell)
+ {
+ var formsCell = nativeCell.Cell as BaseCell;
+ formsCell.PropertyChanged -= nativeCell.CellPropertyChanged;
+ if(formsCell.Parent is BoxedView parentElement)
+ {
+ parentElement.PropertyChanged -= nativeCell.ParentPropertyChanged;
+ if(formsCell.Section != null)
+ {
+ formsCell.Section.PropertyChanged -= nativeCell.SectionPropertyChanged;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Android/Renderers/BoxedView/Cells/BaseCellView.cs b/src/Android/Renderers/BoxedView/Cells/BaseCellView.cs
new file mode 100644
index 000000000..e35d6f943
--- /dev/null
+++ b/src/Android/Renderers/BoxedView/Cells/BaseCellView.cs
@@ -0,0 +1,353 @@
+using Android.Content;
+using Android.Graphics.Drawables;
+using Android.Runtime;
+using Android.Util;
+using Android.Views;
+using Android.Widget;
+using Bit.App.Controls.BoxedView;
+using System;
+using System.ComponentModel;
+using System.Threading;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Android;
+using ARelativeLayout = Android.Widget.RelativeLayout;
+
+namespace Bit.Droid.Renderers
+{
+ [Preserve(AllMembers = true)]
+ public class BaseCellView : ARelativeLayout, INativeElementView
+ {
+ private CancellationTokenSource _iconTokenSource;
+ private Android.Graphics.Color _defaultTextColor;
+ private ColorDrawable _backgroundColor;
+ private ColorDrawable _selectedColor;
+ private RippleDrawable _ripple;
+ private float _defaultFontSize;
+
+ protected Context _Context;
+
+ public BaseCellView(Context context, Cell cell)
+ : base(context)
+ {
+ _Context = context;
+ Cell = cell;
+ CreateContentView();
+ }
+
+ public Cell Cell { get; set; }
+ public Element Element => Cell;
+ protected BaseCell CellBase => Cell as BaseCell;
+ public BoxedView CellParent => Cell.Parent as BoxedView;
+ public TextView TitleLabel { get; set; }
+ public TextView DescriptionLabel { get; set; }
+ public LinearLayout ContentStack { get; set; }
+ public LinearLayout AccessoryStack { get; set; }
+
+ private void CreateContentView()
+ {
+ var contentView = (_Context as FormsAppCompatActivity)
+ .LayoutInflater
+ .Inflate(Resource.Layout.CellBaseView, this, true);
+
+ contentView.LayoutParameters = new ViewGroup.LayoutParams(-1, -1);
+
+ TitleLabel = contentView.FindViewById(Resource.Id.CellTitle);
+ DescriptionLabel = contentView.FindViewById(Resource.Id.CellDescription);
+ ContentStack = contentView.FindViewById(Resource.Id.CellContentStack);
+ AccessoryStack = contentView.FindViewById(Resource.Id.CellAccessoryView);
+
+ _backgroundColor = new ColorDrawable();
+ _selectedColor = new ColorDrawable(Android.Graphics.Color.Argb(125, 180, 180, 180));
+
+ var sel = new StateListDrawable();
+
+ sel.AddState(new int[] { Android.Resource.Attribute.StateSelected }, _selectedColor);
+ sel.AddState(new int[] { -Android.Resource.Attribute.StateSelected }, _backgroundColor);
+ sel.SetExitFadeDuration(250);
+ sel.SetEnterFadeDuration(250);
+
+ var rippleColor = Android.Graphics.Color.Rgb(180, 180, 180);
+ if(CellParent.SelectedColor != Color.Default)
+ {
+ rippleColor = CellParent.SelectedColor.ToAndroid();
+ }
+ _ripple = RendererUtils.CreateRipple(rippleColor, sel);
+ Background = _ripple;
+
+ _defaultTextColor = new Android.Graphics.Color(TitleLabel.CurrentTextColor);
+ _defaultFontSize = TitleLabel.TextSize;
+ }
+
+ public virtual void CellPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if(e.PropertyName == BaseCell.TitleProperty.PropertyName)
+ {
+ UpdateTitleText();
+ }
+ else if(e.PropertyName == BaseCell.TitleColorProperty.PropertyName)
+ {
+ UpdateTitleColor();
+ }
+ else if(e.PropertyName == BaseCell.TitleFontSizeProperty.PropertyName)
+ {
+ UpdateTitleFontSize();
+ }
+ else if(e.PropertyName == BaseCell.DescriptionProperty.PropertyName)
+ {
+ UpdateDescriptionText();
+ }
+ else if(e.PropertyName == BaseCell.DescriptionFontSizeProperty.PropertyName)
+ {
+ UpdateDescriptionFontSize();
+ }
+ else if(e.PropertyName == BaseCell.DescriptionColorProperty.PropertyName)
+ {
+ UpdateDescriptionColor();
+ }
+ else if(e.PropertyName == BaseCell.BackgroundColorProperty.PropertyName)
+ {
+ UpdateBackgroundColor();
+ }
+ else if(e.PropertyName == Cell.IsEnabledProperty.PropertyName)
+ {
+ UpdateIsEnabled();
+ }
+ }
+
+ public virtual void ParentPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ // Avoid running the vain process when popping a page.
+ if((sender as BindableObject)?.BindingContext == null)
+ {
+ return;
+ }
+
+ if(e.PropertyName == BoxedView.CellTitleColorProperty.PropertyName)
+ {
+ UpdateTitleColor();
+ }
+ else if(e.PropertyName == BoxedView.CellTitleFontSizeProperty.PropertyName)
+ {
+ UpdateWithForceLayout(UpdateTitleFontSize);
+ }
+ else if(e.PropertyName == BoxedView.CellDescriptionColorProperty.PropertyName)
+ {
+ UpdateDescriptionColor();
+ }
+ else if(e.PropertyName == BoxedView.CellDescriptionFontSizeProperty.PropertyName)
+ {
+ UpdateWithForceLayout(UpdateDescriptionFontSize);
+ }
+ else if(e.PropertyName == BoxedView.CellBackgroundColorProperty.PropertyName)
+ {
+ UpdateBackgroundColor();
+ }
+ else if(e.PropertyName == BoxedView.SelectedColorProperty.PropertyName)
+ {
+ UpdateWithForceLayout(UpdateSelectedColor);
+ }
+ }
+
+ public virtual void SectionPropertyChanged(object sender, PropertyChangedEventArgs e)
+ { }
+
+ public virtual void RowSelected(BoxedViewRecyclerAdapter adapter, int position)
+ { }
+
+ protected void UpdateWithForceLayout(Action updateAction)
+ {
+ updateAction();
+ Invalidate();
+ }
+
+ public virtual void UpdateCell()
+ {
+ UpdateBackgroundColor();
+ UpdateSelectedColor();
+ UpdateTitleText();
+ UpdateTitleColor();
+ UpdateTitleFontSize();
+ UpdateDescriptionText();
+ UpdateDescriptionColor();
+ UpdateDescriptionFontSize();
+
+ UpdateIsEnabled();
+
+ Invalidate();
+ }
+
+ private void UpdateBackgroundColor()
+ {
+ Selected = false;
+ if(CellBase.BackgroundColor != Color.Default)
+ {
+ _backgroundColor.Color = CellBase.BackgroundColor.ToAndroid();
+ }
+ else if(CellParent != null && CellParent.CellBackgroundColor != Color.Default)
+ {
+ _backgroundColor.Color = CellParent.CellBackgroundColor.ToAndroid();
+ }
+ else
+ {
+ _backgroundColor.Color = Android.Graphics.Color.Transparent;
+ }
+ }
+
+ private void UpdateSelectedColor()
+ {
+ if(CellParent != null && CellParent.SelectedColor != Color.Default)
+ {
+ _selectedColor.Color = CellParent.SelectedColor.MultiplyAlpha(0.5).ToAndroid();
+ _ripple.SetColor(RendererUtils.GetPressedColorSelector(CellParent.SelectedColor.ToAndroid()));
+ }
+ else
+ {
+ _selectedColor.Color = Android.Graphics.Color.Argb(125, 180, 180, 180);
+ _ripple.SetColor(RendererUtils.GetPressedColorSelector(Android.Graphics.Color.Rgb(180, 180, 180)));
+ }
+ }
+
+ private void UpdateTitleText()
+ {
+ TitleLabel.Text = CellBase.Title;
+ // Hide TextView right padding when TextView.Text empty.
+ TitleLabel.Visibility = string.IsNullOrEmpty(TitleLabel.Text) ? ViewStates.Gone : ViewStates.Visible;
+ }
+
+ private void UpdateTitleColor()
+ {
+ if(CellBase.TitleColor != Color.Default)
+ {
+ TitleLabel.SetTextColor(CellBase.TitleColor.ToAndroid());
+ }
+ else if(CellParent != null && CellParent.CellTitleColor != Color.Default)
+ {
+ TitleLabel.SetTextColor(CellParent.CellTitleColor.ToAndroid());
+ }
+ else
+ {
+ TitleLabel.SetTextColor(_defaultTextColor);
+ }
+ }
+
+ private void UpdateTitleFontSize()
+ {
+ if(CellBase.TitleFontSize > 0)
+ {
+ TitleLabel.SetTextSize(ComplexUnitType.Sp, (float)CellBase.TitleFontSize);
+ }
+ else if(CellParent != null)
+ {
+ TitleLabel.SetTextSize(ComplexUnitType.Sp, (float)CellParent.CellTitleFontSize);
+ }
+ else
+ {
+ TitleLabel.SetTextSize(ComplexUnitType.Sp, _defaultFontSize);
+ }
+ }
+
+ private void UpdateDescriptionText()
+ {
+ DescriptionLabel.Text = CellBase.Description;
+ DescriptionLabel.Visibility = string.IsNullOrEmpty(DescriptionLabel.Text) ?
+ ViewStates.Gone : ViewStates.Visible;
+ }
+
+ private void UpdateDescriptionFontSize()
+ {
+ if(CellBase.DescriptionFontSize > 0)
+ {
+ DescriptionLabel.SetTextSize(ComplexUnitType.Sp, (float)CellBase.DescriptionFontSize);
+ }
+ else if(CellParent != null)
+ {
+ DescriptionLabel.SetTextSize(ComplexUnitType.Sp, (float)CellParent.CellDescriptionFontSize);
+ }
+ else
+ {
+ DescriptionLabel.SetTextSize(ComplexUnitType.Sp, _defaultFontSize);
+ }
+ }
+
+ private void UpdateDescriptionColor()
+ {
+ if(CellBase.DescriptionColor != Color.Default)
+ {
+ DescriptionLabel.SetTextColor(CellBase.DescriptionColor.ToAndroid());
+ }
+ else if(CellParent != null && CellParent.CellDescriptionColor != Color.Default)
+ {
+ DescriptionLabel.SetTextColor(CellParent.CellDescriptionColor.ToAndroid());
+ }
+ else
+ {
+ DescriptionLabel.SetTextColor(_defaultTextColor);
+ }
+ }
+
+ protected virtual void UpdateIsEnabled()
+ {
+ SetEnabledAppearance(CellBase.IsEnabled);
+ }
+
+ protected virtual void SetEnabledAppearance(bool isEnabled)
+ {
+ if(isEnabled)
+ {
+ Focusable = false;
+ DescendantFocusability = DescendantFocusability.AfterDescendants;
+ TitleLabel.Alpha = 1f;
+ DescriptionLabel.Alpha = 1f;
+ }
+ else
+ {
+ // not to invoke a ripple effect and not to selected
+ Focusable = true;
+ DescendantFocusability = DescendantFocusability.BlockDescendants;
+ // to turn like disabled
+ TitleLabel.Alpha = 0.3f;
+ DescriptionLabel.Alpha = 0.3f;
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if(disposing)
+ {
+ CellBase.PropertyChanged -= CellPropertyChanged;
+ CellParent.PropertyChanged -= ParentPropertyChanged;
+
+ if(CellBase.Section != null)
+ {
+ CellBase.Section.PropertyChanged -= SectionPropertyChanged;
+ CellBase.Section = null;
+ }
+
+ TitleLabel?.Dispose();
+ TitleLabel = null;
+ DescriptionLabel?.Dispose();
+ DescriptionLabel = null;
+ ContentStack?.Dispose();
+ ContentStack = null;
+ AccessoryStack?.Dispose();
+ AccessoryStack = null;
+ Cell = null;
+
+ _iconTokenSource?.Dispose();
+ _iconTokenSource = null;
+ _Context = null;
+
+ _backgroundColor?.Dispose();
+ _backgroundColor = null;
+ _selectedColor?.Dispose();
+ _selectedColor = null;
+ _ripple?.Dispose();
+ _ripple = null;
+
+ Background?.Dispose();
+ Background = null;
+ }
+ base.Dispose(disposing);
+ }
+ }
+}
diff --git a/src/Android/Renderers/BoxedView/Cells/LabelCellRenderer.cs b/src/Android/Renderers/BoxedView/Cells/LabelCellRenderer.cs
new file mode 100644
index 000000000..7428d093c
--- /dev/null
+++ b/src/Android/Renderers/BoxedView/Cells/LabelCellRenderer.cs
@@ -0,0 +1,133 @@
+using Android.Content;
+using Android.Runtime;
+using Android.Text;
+using Android.Views;
+using Android.Widget;
+using Bit.App.Controls.BoxedView;
+using Bit.Droid.Renderers;
+using System.ComponentModel;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Android;
+
+[assembly: ExportRenderer(typeof(LabelCell), typeof(LabelCellRenderer))]
+namespace Bit.Droid.Renderers
+{
+ [Preserve(AllMembers = true)]
+ public class LabelCellRenderer : BaseCellRenderer
+ { }
+
+ [Preserve(AllMembers = true)]
+ public class LabelCellView : BaseCellView
+ {
+ public LabelCellView(Context context, Cell cell)
+ : base(context, cell)
+ {
+ ValueLabel = new TextView(context);
+ ValueLabel.SetSingleLine(true);
+ ValueLabel.Ellipsize = TextUtils.TruncateAt.End;
+ ValueLabel.Gravity = GravityFlags.Right;
+
+ var textParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WrapContent,
+ ViewGroup.LayoutParams.WrapContent);
+ using(textParams)
+ {
+ ContentStack.AddView(ValueLabel, textParams);
+ }
+ }
+
+ private LabelCell _LabelCell => Cell as LabelCell;
+
+ public TextView ValueLabel { get; set; }
+
+ public override void CellPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.CellPropertyChanged(sender, e);
+ if(e.PropertyName == LabelCell.ValueTextProperty.PropertyName)
+ {
+ UpdateValueText();
+ }
+ else if(e.PropertyName == LabelCell.ValueTextFontSizeProperty.PropertyName)
+ {
+ UpdateValueTextFontSize();
+ }
+ else if(e.PropertyName == LabelCell.ValueTextColorProperty.PropertyName)
+ {
+ UpdateValueTextColor();
+ }
+ }
+
+ public override void ParentPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ base.ParentPropertyChanged(sender, e);
+ if(e.PropertyName == BoxedView.CellValueTextColorProperty.PropertyName)
+ {
+ UpdateValueTextColor();
+ }
+ else if(e.PropertyName == BoxedView.CellValueTextFontSizeProperty.PropertyName)
+ {
+ UpdateValueTextFontSize();
+ }
+ }
+
+ public override void UpdateCell()
+ {
+ base.UpdateCell();
+ UpdateValueText();
+ UpdateValueTextColor();
+ UpdateValueTextFontSize();
+ }
+
+ protected override void SetEnabledAppearance(bool isEnabled)
+ {
+ if(isEnabled)
+ {
+ ValueLabel.Alpha = 1f;
+ }
+ else
+ {
+ ValueLabel.Alpha = 0.3f;
+ }
+ base.SetEnabledAppearance(isEnabled);
+ }
+
+ protected void UpdateValueText()
+ {
+ ValueLabel.Text = _LabelCell.ValueText;
+ }
+
+ private void UpdateValueTextFontSize()
+ {
+ if(_LabelCell.ValueTextFontSize > 0)
+ {
+ ValueLabel.SetTextSize(Android.Util.ComplexUnitType.Sp, (float)_LabelCell.ValueTextFontSize);
+ }
+ else if(CellParent != null)
+ {
+ ValueLabel.SetTextSize(Android.Util.ComplexUnitType.Sp, (float)CellParent.CellValueTextFontSize);
+ }
+ Invalidate();
+ }
+
+ private void UpdateValueTextColor()
+ {
+ if(_LabelCell.ValueTextColor != Color.Default)
+ {
+ ValueLabel.SetTextColor(_LabelCell.ValueTextColor.ToAndroid());
+ }
+ else if(CellParent != null && CellParent.CellValueTextColor != Color.Default)
+ {
+ ValueLabel.SetTextColor(CellParent.CellValueTextColor.ToAndroid());
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if(disposing)
+ {
+ ValueLabel?.Dispose();
+ ValueLabel = null;
+ }
+ base.Dispose(disposing);
+ }
+ }
+}
diff --git a/src/Android/Renderers/RendererUtils.cs b/src/Android/Renderers/RendererUtils.cs
new file mode 100644
index 000000000..8a66a58e9
--- /dev/null
+++ b/src/Android/Renderers/RendererUtils.cs
@@ -0,0 +1,79 @@
+using Android.Content.Res;
+using Android.Graphics.Drawables;
+using Android.Views;
+using Xamarin.Forms;
+
+namespace Bit.Droid.Renderers
+{
+ [Android.Runtime.Preserve(AllMembers = true)]
+ public static class RendererUtils
+ {
+ public static GravityFlags ToAndroidVertical(this LayoutAlignment formsAlignment)
+ {
+ switch(formsAlignment)
+ {
+ case LayoutAlignment.Start:
+ return GravityFlags.Top;
+ case LayoutAlignment.Center:
+ return GravityFlags.CenterVertical;
+ case LayoutAlignment.End:
+ return GravityFlags.Bottom;
+ default:
+ return GravityFlags.FillHorizontal;
+ }
+ }
+
+ public static GravityFlags ToAndroidHorizontal(this LayoutAlignment formsAlignment)
+ {
+ switch(formsAlignment)
+ {
+ case LayoutAlignment.Start:
+ return GravityFlags.Start;
+ case LayoutAlignment.Center:
+ return GravityFlags.CenterHorizontal;
+ case LayoutAlignment.End:
+ return GravityFlags.End;
+ default:
+ return GravityFlags.FillVertical;
+ }
+ }
+
+ public static GravityFlags ToAndroidVertical(this Xamarin.Forms.TextAlignment formsAlignment)
+ {
+ switch(formsAlignment)
+ {
+ case Xamarin.Forms.TextAlignment.Start:
+ return GravityFlags.Left | GravityFlags.CenterVertical;
+ case Xamarin.Forms.TextAlignment.Center:
+ return GravityFlags.Center | GravityFlags.CenterVertical;
+ case Xamarin.Forms.TextAlignment.End:
+ return GravityFlags.Right | GravityFlags.CenterVertical;
+ default:
+ return GravityFlags.Right | GravityFlags.CenterVertical;
+ }
+ }
+
+ public static RippleDrawable CreateRipple(Android.Graphics.Color color, Drawable background = null)
+ {
+ if(background == null)
+ {
+ var mask = new ColorDrawable(Android.Graphics.Color.White);
+ return new RippleDrawable(GetPressedColorSelector(color), null, mask);
+ }
+ return new RippleDrawable(GetPressedColorSelector(color), background, null);
+ }
+
+ public static ColorStateList GetPressedColorSelector(int pressedColor)
+ {
+ return new ColorStateList(
+ new int[][]
+ {
+ new int[]{}
+ },
+ new int[]
+ {
+ pressedColor,
+ });
+ }
+ }
+}
diff --git a/src/Android/Resources/Resource.designer.cs b/src/Android/Resources/Resource.designer.cs
index a8de379ee..91dd1286d 100644
--- a/src/Android/Resources/Resource.designer.cs
+++ b/src/Android/Resources/Resource.designer.cs
@@ -6534,9 +6534,39 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a003a
public const int CTRL = 2131361850;
+ // aapt resource value: 0x7f0a00a9
+ public const int CellAccessoryView = 2131361961;
+
+ // aapt resource value: 0x7f0a00a5
+ public const int CellBody = 2131361957;
+
+ // aapt resource value: 0x7f0a00a6
+ public const int CellContentStack = 2131361958;
+
+ // aapt resource value: 0x7f0a00a8
+ public const int CellDescription = 2131361960;
+
+ // aapt resource value: 0x7f0a00a7
+ public const int CellTitle = 2131361959;
+
+ // aapt resource value: 0x7f0a00aa
+ public const int ContentCellBody = 2131361962;
+
+ // aapt resource value: 0x7f0a00ab
+ public const int ContentCellBorder = 2131361963;
+
// aapt resource value: 0x7f0a003b
public const int FUNCTION = 2131361851;
+ // aapt resource value: 0x7f0a00ba
+ public const int FooterCellText = 2131361978;
+
+ // aapt resource value: 0x7f0a00bc
+ public const int HeaderCellBorder = 2131361980;
+
+ // aapt resource value: 0x7f0a00bb
+ public const int HeaderCellText = 2131361979;
+
// aapt resource value: 0x7f0a003c
public const int META = 2131361852;
@@ -6546,8 +6576,8 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a003e
public const int SYM = 2131361854;
- // aapt resource value: 0x7f0a00e5
- public const int action0 = 2131362021;
+ // aapt resource value: 0x7f0a00ef
+ public const int action0 = 2131362031;
// aapt resource value: 0x7f0a008d
public const int action_bar = 2131361933;
@@ -6570,17 +6600,17 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a0069
public const int action_bar_title = 2131361897;
- // aapt resource value: 0x7f0a00e2
- public const int action_container = 2131362018;
+ // aapt resource value: 0x7f0a00ec
+ public const int action_container = 2131362028;
// aapt resource value: 0x7f0a008e
public const int action_context_bar = 2131361934;
- // aapt resource value: 0x7f0a00e9
- public const int action_divider = 2131362025;
+ // aapt resource value: 0x7f0a00f3
+ public const int action_divider = 2131362035;
- // aapt resource value: 0x7f0a00e3
- public const int action_image = 2131362019;
+ // aapt resource value: 0x7f0a00ed
+ public const int action_image = 2131362029;
// aapt resource value: 0x7f0a0003
public const int action_menu_divider = 2131361795;
@@ -6597,11 +6627,11 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a006b
public const int action_mode_close_button = 2131361899;
- // aapt resource value: 0x7f0a00e4
- public const int action_text = 2131362020;
+ // aapt resource value: 0x7f0a00ee
+ public const int action_text = 2131362030;
- // aapt resource value: 0x7f0a00f2
- public const int actions = 2131362034;
+ // aapt resource value: 0x7f0a00fc
+ public const int actions = 2131362044;
// aapt resource value: 0x7f0a006c
public const int activity_chooser_view_content = 2131361900;
@@ -6657,8 +6687,8 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a0072
public const int buttonPanel = 2131361906;
- // aapt resource value: 0x7f0a00e6
- public const int cancel_action = 2131362022;
+ // aapt resource value: 0x7f0a00f0
+ public const int cancel_action = 2131362032;
// aapt resource value: 0x7f0a004c
public const int center = 2131361868;
@@ -6672,8 +6702,8 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a0086
public const int checkbox = 2131361926;
- // aapt resource value: 0x7f0a00ee
- public const int chronometer = 2131362030;
+ // aapt resource value: 0x7f0a00f8
+ public const int chronometer = 2131362040;
// aapt resource value: 0x7f0a0061
public const int clip_horizontal = 2131361889;
@@ -6684,8 +6714,8 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a0040
public const int collapseActionView = 2131361856;
- // aapt resource value: 0x7f0a00a7
- public const int container = 2131361959;
+ // aapt resource value: 0x7f0a00ae
+ public const int container = 2131361966;
// aapt resource value: 0x7f0a0082
public const int content = 2131361922;
@@ -6693,8 +6723,8 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a0075
public const int contentPanel = 2131361909;
- // aapt resource value: 0x7f0a00a8
- public const int coordinator = 2131361960;
+ // aapt resource value: 0x7f0a00af
+ public const int coordinator = 2131361967;
// aapt resource value: 0x7f0a007c
public const int custom = 2131361916;
@@ -6708,20 +6738,20 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a006f
public const int default_activity_button = 2131361903;
- // aapt resource value: 0x7f0a00aa
- public const int design_bottom_sheet = 2131361962;
+ // aapt resource value: 0x7f0a00b1
+ public const int design_bottom_sheet = 2131361969;
- // aapt resource value: 0x7f0a00af
- public const int design_menu_item_action_area = 2131361967;
+ // aapt resource value: 0x7f0a00b6
+ public const int design_menu_item_action_area = 2131361974;
- // aapt resource value: 0x7f0a00ae
- public const int design_menu_item_action_area_stub = 2131361966;
+ // aapt resource value: 0x7f0a00b5
+ public const int design_menu_item_action_area_stub = 2131361973;
- // aapt resource value: 0x7f0a00ad
- public const int design_menu_item_text = 2131361965;
+ // aapt resource value: 0x7f0a00b4
+ public const int design_menu_item_text = 2131361972;
- // aapt resource value: 0x7f0a00ac
- public const int design_navigation_view = 2131361964;
+ // aapt resource value: 0x7f0a00b3
+ public const int design_navigation_view = 2131361971;
// aapt resource value: 0x7f0a0027
public const int disableHome = 2131361831;
@@ -6732,8 +6762,8 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a0037
public const int end = 2131361847;
- // aapt resource value: 0x7f0a00f4
- public const int end_padder = 2131362036;
+ // aapt resource value: 0x7f0a00fe
+ public const int end_padder = 2131362046;
// aapt resource value: 0x7f0a0046
public const int enterAlways = 2131361862;
@@ -6765,11 +6795,11 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a005c
public const int @fixed = 2131361884;
- // aapt resource value: 0x7f0a00b1
- public const int flyoutcontent_appbar = 2131361969;
+ // aapt resource value: 0x7f0a00b8
+ public const int flyoutcontent_appbar = 2131361976;
- // aapt resource value: 0x7f0a00b2
- public const int flyoutcontent_recycler = 2131361970;
+ // aapt resource value: 0x7f0a00b9
+ public const int flyoutcontent_recycler = 2131361977;
// aapt resource value: 0x7f0a0067
public const int forever = 2131361895;
@@ -6789,8 +6819,8 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a0071
public const int icon = 2131361905;
- // aapt resource value: 0x7f0a00f3
- public const int icon_group = 2131362035;
+ // aapt resource value: 0x7f0a00fd
+ public const int icon_group = 2131362045;
// aapt resource value: 0x7f0a0041
public const int ifRoom = 2131361857;
@@ -6798,8 +6828,8 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a006e
public const int image = 2131361902;
- // aapt resource value: 0x7f0a00ef
- public const int info = 2131362031;
+ // aapt resource value: 0x7f0a00f9
+ public const int info = 2131362041;
// aapt resource value: 0x7f0a0068
public const int italic = 2131361896;
@@ -6810,8 +6840,8 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a004e
public const int labeled = 2131361870;
- // aapt resource value: 0x7f0a00a6
- public const int largeLabel = 2131361958;
+ // aapt resource value: 0x7f0a00ad
+ public const int largeLabel = 2131361965;
// aapt resource value: 0x7f0a0054
public const int left = 2131361876;
@@ -6828,23 +6858,23 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a0070
public const int list_item = 2131361904;
- // aapt resource value: 0x7f0a00f5
- public const int main_appbar = 2131362037;
-
- // aapt resource value: 0x7f0a00f8
- public const int main_scrollview = 2131362040;
-
- // aapt resource value: 0x7f0a00f7
- public const int main_tablayout = 2131362039;
-
- // aapt resource value: 0x7f0a00f6
- public const int main_toolbar = 2131362038;
-
// aapt resource value: 0x7f0a00ff
- public const int masked = 2131362047;
+ public const int main_appbar = 2131362047;
- // aapt resource value: 0x7f0a00e8
- public const int media_actions = 2131362024;
+ // aapt resource value: 0x7f0a0102
+ public const int main_scrollview = 2131362050;
+
+ // aapt resource value: 0x7f0a0101
+ public const int main_tablayout = 2131362049;
+
+ // aapt resource value: 0x7f0a0100
+ public const int main_toolbar = 2131362048;
+
+ // aapt resource value: 0x7f0a0109
+ public const int masked = 2131362057;
+
+ // aapt resource value: 0x7f0a00f2
+ public const int media_actions = 2131362034;
// aapt resource value: 0x7f0a009c
public const int message = 2131361948;
@@ -6855,143 +6885,143 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a0059
public const int mini = 2131361881;
- // aapt resource value: 0x7f0a00cf
- public const int mr_art = 2131361999;
-
- // aapt resource value: 0x7f0a00c0
- public const int mr_cast_checkbox = 2131361984;
-
- // aapt resource value: 0x7f0a00b9
- public const int mr_cast_close_button = 2131361977;
-
- // aapt resource value: 0x7f0a00b4
- public const int mr_cast_group_icon = 2131361972;
-
- // aapt resource value: 0x7f0a00b5
- public const int mr_cast_group_name = 2131361973;
-
- // aapt resource value: 0x7f0a00b3
- public const int mr_cast_list = 2131361971;
-
- // aapt resource value: 0x7f0a00b8
- public const int mr_cast_meta = 2131361976;
-
- // aapt resource value: 0x7f0a00ba
- public const int mr_cast_meta_art = 2131361978;
-
- // aapt resource value: 0x7f0a00bc
- public const int mr_cast_meta_subtitle = 2131361980;
-
- // aapt resource value: 0x7f0a00bb
- public const int mr_cast_meta_title = 2131361979;
-
- // aapt resource value: 0x7f0a00be
- public const int mr_cast_route_icon = 2131361982;
-
- // aapt resource value: 0x7f0a00bf
- public const int mr_cast_route_name = 2131361983;
-
- // aapt resource value: 0x7f0a00bd
- public const int mr_cast_stop_button = 2131361981;
-
- // aapt resource value: 0x7f0a00c1
- public const int mr_cast_volume_layout = 2131361985;
-
- // aapt resource value: 0x7f0a00c2
- public const int mr_cast_volume_slider = 2131361986;
-
- // aapt resource value: 0x7f0a00c4
- public const int mr_chooser_list = 2131361988;
-
- // aapt resource value: 0x7f0a00c7
- public const int mr_chooser_route_desc = 2131361991;
-
- // aapt resource value: 0x7f0a00c5
- public const int mr_chooser_route_icon = 2131361989;
-
- // aapt resource value: 0x7f0a00c6
- public const int mr_chooser_route_name = 2131361990;
-
- // aapt resource value: 0x7f0a00c3
- public const int mr_chooser_title = 2131361987;
-
- // aapt resource value: 0x7f0a00cc
- public const int mr_close = 2131361996;
-
- // aapt resource value: 0x7f0a00d2
- public const int mr_control_divider = 2131362002;
-
- // aapt resource value: 0x7f0a00dd
- public const int mr_control_playback_ctrl = 2131362013;
-
- // aapt resource value: 0x7f0a00e0
- public const int mr_control_subtitle = 2131362016;
-
- // aapt resource value: 0x7f0a00df
- public const int mr_control_title = 2131362015;
-
- // aapt resource value: 0x7f0a00de
- public const int mr_control_title_container = 2131362014;
-
- // aapt resource value: 0x7f0a00cd
- public const int mr_custom_control = 2131361997;
-
- // aapt resource value: 0x7f0a00ce
- public const int mr_default_control = 2131361998;
-
- // aapt resource value: 0x7f0a00c9
- public const int mr_dialog_area = 2131361993;
-
- // aapt resource value: 0x7f0a00d8
- public const int mr_dialog_header_name = 2131362008;
-
- // aapt resource value: 0x7f0a00c8
- public const int mr_expandable_area = 2131361992;
-
- // aapt resource value: 0x7f0a00e1
- public const int mr_group_expand_collapse = 2131362017;
-
- // aapt resource value: 0x7f0a00b6
- public const int mr_group_volume_route_name = 2131361974;
-
- // aapt resource value: 0x7f0a00b7
- public const int mr_group_volume_slider = 2131361975;
-
- // aapt resource value: 0x7f0a00d0
- public const int mr_media_main_control = 2131362000;
-
- // aapt resource value: 0x7f0a00cb
- public const int mr_name = 2131361995;
-
// aapt resource value: 0x7f0a00d9
- public const int mr_picker_close_button = 2131362009;
-
- // aapt resource value: 0x7f0a00da
- public const int mr_picker_list = 2131362010;
-
- // aapt resource value: 0x7f0a00db
- public const int mr_picker_route_icon = 2131362011;
-
- // aapt resource value: 0x7f0a00dc
- public const int mr_picker_route_name = 2131362012;
-
- // aapt resource value: 0x7f0a00d1
- public const int mr_playback_control = 2131362001;
+ public const int mr_art = 2131362009;
// aapt resource value: 0x7f0a00ca
- public const int mr_title_bar = 2131361994;
+ public const int mr_cast_checkbox = 2131361994;
- // aapt resource value: 0x7f0a00d3
- public const int mr_volume_control = 2131362003;
+ // aapt resource value: 0x7f0a00c3
+ public const int mr_cast_close_button = 2131361987;
- // aapt resource value: 0x7f0a00d4
- public const int mr_volume_group_list = 2131362004;
+ // aapt resource value: 0x7f0a00be
+ public const int mr_cast_group_icon = 2131361982;
+
+ // aapt resource value: 0x7f0a00bf
+ public const int mr_cast_group_name = 2131361983;
+
+ // aapt resource value: 0x7f0a00bd
+ public const int mr_cast_list = 2131361981;
+
+ // aapt resource value: 0x7f0a00c2
+ public const int mr_cast_meta = 2131361986;
+
+ // aapt resource value: 0x7f0a00c4
+ public const int mr_cast_meta_art = 2131361988;
+
+ // aapt resource value: 0x7f0a00c6
+ public const int mr_cast_meta_subtitle = 2131361990;
+
+ // aapt resource value: 0x7f0a00c5
+ public const int mr_cast_meta_title = 2131361989;
+
+ // aapt resource value: 0x7f0a00c8
+ public const int mr_cast_route_icon = 2131361992;
+
+ // aapt resource value: 0x7f0a00c9
+ public const int mr_cast_route_name = 2131361993;
+
+ // aapt resource value: 0x7f0a00c7
+ public const int mr_cast_stop_button = 2131361991;
+
+ // aapt resource value: 0x7f0a00cb
+ public const int mr_cast_volume_layout = 2131361995;
+
+ // aapt resource value: 0x7f0a00cc
+ public const int mr_cast_volume_slider = 2131361996;
+
+ // aapt resource value: 0x7f0a00ce
+ public const int mr_chooser_list = 2131361998;
+
+ // aapt resource value: 0x7f0a00d1
+ public const int mr_chooser_route_desc = 2131362001;
+
+ // aapt resource value: 0x7f0a00cf
+ public const int mr_chooser_route_icon = 2131361999;
+
+ // aapt resource value: 0x7f0a00d0
+ public const int mr_chooser_route_name = 2131362000;
+
+ // aapt resource value: 0x7f0a00cd
+ public const int mr_chooser_title = 2131361997;
// aapt resource value: 0x7f0a00d6
- public const int mr_volume_item_icon = 2131362006;
+ public const int mr_close = 2131362006;
+
+ // aapt resource value: 0x7f0a00dc
+ public const int mr_control_divider = 2131362012;
+
+ // aapt resource value: 0x7f0a00e7
+ public const int mr_control_playback_ctrl = 2131362023;
+
+ // aapt resource value: 0x7f0a00ea
+ public const int mr_control_subtitle = 2131362026;
+
+ // aapt resource value: 0x7f0a00e9
+ public const int mr_control_title = 2131362025;
+
+ // aapt resource value: 0x7f0a00e8
+ public const int mr_control_title_container = 2131362024;
// aapt resource value: 0x7f0a00d7
- public const int mr_volume_slider = 2131362007;
+ public const int mr_custom_control = 2131362007;
+
+ // aapt resource value: 0x7f0a00d8
+ public const int mr_default_control = 2131362008;
+
+ // aapt resource value: 0x7f0a00d3
+ public const int mr_dialog_area = 2131362003;
+
+ // aapt resource value: 0x7f0a00e2
+ public const int mr_dialog_header_name = 2131362018;
+
+ // aapt resource value: 0x7f0a00d2
+ public const int mr_expandable_area = 2131362002;
+
+ // aapt resource value: 0x7f0a00eb
+ public const int mr_group_expand_collapse = 2131362027;
+
+ // aapt resource value: 0x7f0a00c0
+ public const int mr_group_volume_route_name = 2131361984;
+
+ // aapt resource value: 0x7f0a00c1
+ public const int mr_group_volume_slider = 2131361985;
+
+ // aapt resource value: 0x7f0a00da
+ public const int mr_media_main_control = 2131362010;
+
+ // aapt resource value: 0x7f0a00d5
+ public const int mr_name = 2131362005;
+
+ // aapt resource value: 0x7f0a00e3
+ public const int mr_picker_close_button = 2131362019;
+
+ // aapt resource value: 0x7f0a00e4
+ public const int mr_picker_list = 2131362020;
+
+ // aapt resource value: 0x7f0a00e5
+ public const int mr_picker_route_icon = 2131362021;
+
+ // aapt resource value: 0x7f0a00e6
+ public const int mr_picker_route_name = 2131362022;
+
+ // aapt resource value: 0x7f0a00db
+ public const int mr_playback_control = 2131362011;
+
+ // aapt resource value: 0x7f0a00d4
+ public const int mr_title_bar = 2131362004;
+
+ // aapt resource value: 0x7f0a00dd
+ public const int mr_volume_control = 2131362013;
+
+ // aapt resource value: 0x7f0a00de
+ public const int mr_volume_group_list = 2131362014;
+
+ // aapt resource value: 0x7f0a00e0
+ public const int mr_volume_item_icon = 2131362016;
+
+ // aapt resource value: 0x7f0a00e1
+ public const int mr_volume_slider = 2131362017;
// aapt resource value: 0x7f0a0014
public const int mtrl_child_content_container = 2131361812;
@@ -7002,8 +7032,8 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a002f
public const int multiply = 2131361839;
- // aapt resource value: 0x7f0a00ab
- public const int navigation_header_container = 2131361963;
+ // aapt resource value: 0x7f0a00b2
+ public const int navigation_header_container = 2131361970;
// aapt resource value: 0x7f0a0042
public const int never = 2131361858;
@@ -7014,14 +7044,14 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a0025
public const int normal = 2131361829;
- // aapt resource value: 0x7f0a00f1
- public const int notification_background = 2131362033;
+ // aapt resource value: 0x7f0a00fb
+ public const int notification_background = 2131362043;
- // aapt resource value: 0x7f0a00eb
- public const int notification_main_column = 2131362027;
+ // aapt resource value: 0x7f0a00f5
+ public const int notification_main_column = 2131362037;
- // aapt resource value: 0x7f0a00ea
- public const int notification_main_column_container = 2131362026;
+ // aapt resource value: 0x7f0a00f4
+ public const int notification_main_column_container = 2131362036;
// aapt resource value: 0x7f0a0060
public const int outline = 2131361888;
@@ -7050,11 +7080,11 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a0055
public const int right = 2131361877;
- // aapt resource value: 0x7f0a00f0
- public const int right_icon = 2131362032;
+ // aapt resource value: 0x7f0a00fa
+ public const int right_icon = 2131362042;
- // aapt resource value: 0x7f0a00ec
- public const int right_side = 2131362028;
+ // aapt resource value: 0x7f0a00f6
+ public const int right_side = 2131362038;
// aapt resource value: 0x7f0a000c
public const int save_image_matrix = 2131361804;
@@ -7119,14 +7149,14 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a004f
public const int selected = 2131361871;
- // aapt resource value: 0x7f0a00f9
- public const int shellcontent_appbar = 2131362041;
+ // aapt resource value: 0x7f0a0103
+ public const int shellcontent_appbar = 2131362051;
- // aapt resource value: 0x7f0a00fb
- public const int shellcontent_scrollview = 2131362043;
+ // aapt resource value: 0x7f0a0105
+ public const int shellcontent_scrollview = 2131362053;
- // aapt resource value: 0x7f0a00fa
- public const int shellcontent_toolbar = 2131362042;
+ // aapt resource value: 0x7f0a0104
+ public const int shellcontent_toolbar = 2131362052;
// aapt resource value: 0x7f0a0083
public const int shortcut = 2131361923;
@@ -7140,11 +7170,11 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a002c
public const int showTitle = 2131361836;
- // aapt resource value: 0x7f0a00fc
- public const int sliding_tabs = 2131362044;
+ // aapt resource value: 0x7f0a0106
+ public const int sliding_tabs = 2131362054;
- // aapt resource value: 0x7f0a00a5
- public const int smallLabel = 2131361957;
+ // aapt resource value: 0x7f0a00ac
+ public const int smallLabel = 2131361964;
// aapt resource value: 0x7f0a0016
public const int snackbar_action = 2131361814;
@@ -7176,8 +7206,8 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a0056
public const int start = 2131361878;
- // aapt resource value: 0x7f0a00e7
- public const int status_bar_latest_event_content = 2131362023;
+ // aapt resource value: 0x7f0a00f1
+ public const int status_bar_latest_event_content = 2131362033;
// aapt resource value: 0x7f0a005b
public const int stretch = 2131361883;
@@ -7218,8 +7248,8 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a005a
public const int textStart = 2131361882;
- // aapt resource value: 0x7f0a00b0
- public const int text_input_password_toggle = 2131361968;
+ // aapt resource value: 0x7f0a00b7
+ public const int text_input_password_toggle = 2131361975;
// aapt resource value: 0x7f0a0018
public const int textinput_counter = 2131361816;
@@ -7230,8 +7260,8 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a001a
public const int textinput_helper_text = 2131361818;
- // aapt resource value: 0x7f0a00ed
- public const int time = 2131362029;
+ // aapt resource value: 0x7f0a00f7
+ public const int time = 2131362039;
// aapt resource value: 0x7f0a0023
public const int title = 2131361827;
@@ -7242,8 +7272,8 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a007e
public const int title_template = 2131361918;
- // aapt resource value: 0x7f0a00fd
- public const int toolbar = 2131362045;
+ // aapt resource value: 0x7f0a0107
+ public const int toolbar = 2131362055;
// aapt resource value: 0x7f0a0045
public const int top = 2131361861;
@@ -7251,8 +7281,8 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a007d
public const int topPanel = 2131361917;
- // aapt resource value: 0x7f0a00a9
- public const int touch_outside = 2131361961;
+ // aapt resource value: 0x7f0a00b0
+ public const int touch_outside = 2131361968;
// aapt resource value: 0x7f0a000f
public const int transition_current_scene = 2131361807;
@@ -7284,11 +7314,11 @@ namespace Bit.Droid
// aapt resource value: 0x7f0a001b
public const int view_offset_helper = 2131361819;
- // aapt resource value: 0x7f0a00fe
- public const int visible = 2131362046;
+ // aapt resource value: 0x7f0a0108
+ public const int visible = 2131362056;
- // aapt resource value: 0x7f0a00d5
- public const int volume_item_container = 2131362005;
+ // aapt resource value: 0x7f0a00df
+ public const int volume_item_container = 2131362015;
// aapt resource value: 0x7f0a0043
public const int withText = 2131361859;
@@ -7507,166 +7537,178 @@ namespace Bit.Droid
public const int browser_actions_context_menu_row = 2130968607;
// aapt resource value: 0x7f040020
- public const int design_bottom_navigation_item = 2130968608;
+ public const int CellBaseView = 2130968608;
// aapt resource value: 0x7f040021
- public const int design_bottom_sheet_dialog = 2130968609;
+ public const int ContentCell = 2130968609;
// aapt resource value: 0x7f040022
- public const int design_layout_snackbar = 2130968610;
+ public const int design_bottom_navigation_item = 2130968610;
// aapt resource value: 0x7f040023
- public const int design_layout_snackbar_include = 2130968611;
+ public const int design_bottom_sheet_dialog = 2130968611;
// aapt resource value: 0x7f040024
- public const int design_layout_tab_icon = 2130968612;
+ public const int design_layout_snackbar = 2130968612;
// aapt resource value: 0x7f040025
- public const int design_layout_tab_text = 2130968613;
+ public const int design_layout_snackbar_include = 2130968613;
// aapt resource value: 0x7f040026
- public const int design_menu_item_action_area = 2130968614;
+ public const int design_layout_tab_icon = 2130968614;
// aapt resource value: 0x7f040027
- public const int design_navigation_item = 2130968615;
+ public const int design_layout_tab_text = 2130968615;
// aapt resource value: 0x7f040028
- public const int design_navigation_item_header = 2130968616;
+ public const int design_menu_item_action_area = 2130968616;
// aapt resource value: 0x7f040029
- public const int design_navigation_item_separator = 2130968617;
+ public const int design_navigation_item = 2130968617;
// aapt resource value: 0x7f04002a
- public const int design_navigation_item_subheader = 2130968618;
+ public const int design_navigation_item_header = 2130968618;
// aapt resource value: 0x7f04002b
- public const int design_navigation_menu = 2130968619;
+ public const int design_navigation_item_separator = 2130968619;
// aapt resource value: 0x7f04002c
- public const int design_navigation_menu_item = 2130968620;
+ public const int design_navigation_item_subheader = 2130968620;
// aapt resource value: 0x7f04002d
- public const int design_text_input_password_icon = 2130968621;
+ public const int design_navigation_menu = 2130968621;
// aapt resource value: 0x7f04002e
- public const int FlyoutContent = 2130968622;
+ public const int design_navigation_menu_item = 2130968622;
// aapt resource value: 0x7f04002f
- public const int mr_cast_dialog = 2130968623;
+ public const int design_text_input_password_icon = 2130968623;
// aapt resource value: 0x7f040030
- public const int mr_cast_group_item = 2130968624;
+ public const int FlyoutContent = 2130968624;
// aapt resource value: 0x7f040031
- public const int mr_cast_group_volume_item = 2130968625;
+ public const int FooterCell = 2130968625;
// aapt resource value: 0x7f040032
- public const int mr_cast_media_metadata = 2130968626;
+ public const int HeaderCell = 2130968626;
// aapt resource value: 0x7f040033
- public const int mr_cast_route_item = 2130968627;
+ public const int mr_cast_dialog = 2130968627;
// aapt resource value: 0x7f040034
- public const int mr_chooser_dialog = 2130968628;
+ public const int mr_cast_group_item = 2130968628;
// aapt resource value: 0x7f040035
- public const int mr_chooser_list_item = 2130968629;
+ public const int mr_cast_group_volume_item = 2130968629;
// aapt resource value: 0x7f040036
- public const int mr_controller_material_dialog_b = 2130968630;
+ public const int mr_cast_media_metadata = 2130968630;
// aapt resource value: 0x7f040037
- public const int mr_controller_volume_item = 2130968631;
+ public const int mr_cast_route_item = 2130968631;
// aapt resource value: 0x7f040038
- public const int mr_dialog_header_item = 2130968632;
+ public const int mr_chooser_dialog = 2130968632;
// aapt resource value: 0x7f040039
- public const int mr_picker_dialog = 2130968633;
+ public const int mr_chooser_list_item = 2130968633;
// aapt resource value: 0x7f04003a
- public const int mr_picker_route_item = 2130968634;
+ public const int mr_controller_material_dialog_b = 2130968634;
// aapt resource value: 0x7f04003b
- public const int mr_playback_control = 2130968635;
+ public const int mr_controller_volume_item = 2130968635;
// aapt resource value: 0x7f04003c
- public const int mr_volume_control = 2130968636;
+ public const int mr_dialog_header_item = 2130968636;
// aapt resource value: 0x7f04003d
- public const int mtrl_layout_snackbar = 2130968637;
+ public const int mr_picker_dialog = 2130968637;
// aapt resource value: 0x7f04003e
- public const int mtrl_layout_snackbar_include = 2130968638;
+ public const int mr_picker_route_item = 2130968638;
// aapt resource value: 0x7f04003f
- public const int notification_action = 2130968639;
+ public const int mr_playback_control = 2130968639;
// aapt resource value: 0x7f040040
- public const int notification_action_tombstone = 2130968640;
+ public const int mr_volume_control = 2130968640;
// aapt resource value: 0x7f040041
- public const int notification_media_action = 2130968641;
+ public const int mtrl_layout_snackbar = 2130968641;
// aapt resource value: 0x7f040042
- public const int notification_media_cancel_action = 2130968642;
+ public const int mtrl_layout_snackbar_include = 2130968642;
// aapt resource value: 0x7f040043
- public const int notification_template_big_media = 2130968643;
+ public const int notification_action = 2130968643;
// aapt resource value: 0x7f040044
- public const int notification_template_big_media_custom = 2130968644;
+ public const int notification_action_tombstone = 2130968644;
// aapt resource value: 0x7f040045
- public const int notification_template_big_media_narrow = 2130968645;
+ public const int notification_media_action = 2130968645;
// aapt resource value: 0x7f040046
- public const int notification_template_big_media_narrow_custom = 2130968646;
+ public const int notification_media_cancel_action = 2130968646;
// aapt resource value: 0x7f040047
- public const int notification_template_custom_big = 2130968647;
+ public const int notification_template_big_media = 2130968647;
// aapt resource value: 0x7f040048
- public const int notification_template_icon_group = 2130968648;
+ public const int notification_template_big_media_custom = 2130968648;
// aapt resource value: 0x7f040049
- public const int notification_template_lines_media = 2130968649;
+ public const int notification_template_big_media_narrow = 2130968649;
// aapt resource value: 0x7f04004a
- public const int notification_template_media = 2130968650;
+ public const int notification_template_big_media_narrow_custom = 2130968650;
// aapt resource value: 0x7f04004b
- public const int notification_template_media_custom = 2130968651;
+ public const int notification_template_custom_big = 2130968651;
// aapt resource value: 0x7f04004c
- public const int notification_template_part_chronometer = 2130968652;
+ public const int notification_template_icon_group = 2130968652;
// aapt resource value: 0x7f04004d
- public const int notification_template_part_time = 2130968653;
+ public const int notification_template_lines_media = 2130968653;
// aapt resource value: 0x7f04004e
- public const int RootLayout = 2130968654;
+ public const int notification_template_media = 2130968654;
// aapt resource value: 0x7f04004f
- public const int select_dialog_item_material = 2130968655;
+ public const int notification_template_media_custom = 2130968655;
// aapt resource value: 0x7f040050
- public const int select_dialog_multichoice_material = 2130968656;
+ public const int notification_template_part_chronometer = 2130968656;
// aapt resource value: 0x7f040051
- public const int select_dialog_singlechoice_material = 2130968657;
+ public const int notification_template_part_time = 2130968657;
// aapt resource value: 0x7f040052
- public const int ShellContent = 2130968658;
+ public const int RootLayout = 2130968658;
// aapt resource value: 0x7f040053
- public const int support_simple_spinner_dropdown_item = 2130968659;
+ public const int select_dialog_item_material = 2130968659;
// aapt resource value: 0x7f040054
- public const int Tabbar = 2130968660;
+ public const int select_dialog_multichoice_material = 2130968660;
// aapt resource value: 0x7f040055
- public const int Toolbar = 2130968661;
+ public const int select_dialog_singlechoice_material = 2130968661;
+
+ // aapt resource value: 0x7f040056
+ public const int ShellContent = 2130968662;
+
+ // aapt resource value: 0x7f040057
+ public const int support_simple_spinner_dropdown_item = 2130968663;
+
+ // aapt resource value: 0x7f040058
+ public const int Tabbar = 2130968664;
+
+ // aapt resource value: 0x7f040059
+ public const int Toolbar = 2130968665;
static Layout()
{
diff --git a/src/Android/Resources/layout/CellBaseView.axml b/src/Android/Resources/layout/CellBaseView.axml
new file mode 100644
index 000000000..384a44632
--- /dev/null
+++ b/src/Android/Resources/layout/CellBaseView.axml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Android/Resources/layout/ContentCell.axml b/src/Android/Resources/layout/ContentCell.axml
new file mode 100644
index 000000000..4aa65924c
--- /dev/null
+++ b/src/Android/Resources/layout/ContentCell.axml
@@ -0,0 +1,21 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Android/Resources/layout/FooterCell.axml b/src/Android/Resources/layout/FooterCell.axml
new file mode 100644
index 000000000..09ca8cb59
--- /dev/null
+++ b/src/Android/Resources/layout/FooterCell.axml
@@ -0,0 +1,15 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Android/Resources/layout/HeaderCell.axml b/src/Android/Resources/layout/HeaderCell.axml
new file mode 100644
index 000000000..562cf5c62
--- /dev/null
+++ b/src/Android/Resources/layout/HeaderCell.axml
@@ -0,0 +1,22 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/App/Controls/BoxedView/BoxedModel.cs b/src/App/Controls/BoxedView/BoxedModel.cs
new file mode 100644
index 000000000..af478a454
--- /dev/null
+++ b/src/App/Controls/BoxedView/BoxedModel.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Xamarin.Forms;
+using Xamarin.Forms.Internals;
+
+namespace Bit.App.Controls.BoxedView
+{
+ public class BoxedModel : TableModel
+ {
+ private static readonly BindableProperty PathProperty = BindableProperty.Create(
+ "Path", typeof(Tuple), typeof(Cell), null);
+
+ private BoxedRoot _root;
+ private IEnumerable _visibleSections;
+
+ public BoxedModel(BoxedRoot root)
+ {
+ _root = root;
+ _visibleSections = _root.Where(x => x.IsVisible);
+ }
+
+ public override Cell GetCell(int section, int row)
+ {
+ var cell = (Cell)GetItem(section, row);
+ SetPath(cell, new Tuple(section, row));
+ return cell;
+ }
+
+ public override object GetItem(int section, int row)
+ {
+ return _visibleSections.ElementAt(section)[row];
+ }
+
+ public override int GetRowCount(int section)
+ {
+ return _visibleSections.ElementAt(section).Count;
+ }
+
+ public override int GetSectionCount()
+ {
+ return _visibleSections.Count();
+ }
+
+ public virtual BoxedSection GetSection(int section)
+ {
+ return _visibleSections.ElementAtOrDefault(section);
+ }
+
+ public override string GetSectionTitle(int section)
+ {
+ return _visibleSections.ElementAt(section).Title;
+ }
+
+ public virtual string GetFooterText(int section)
+ {
+ return _visibleSections.ElementAt(section).FooterText;
+ }
+
+ protected override void OnRowSelected(object item)
+ {
+ base.OnRowSelected(item);
+ (item as BaseCell)?.OnTapped();
+ }
+
+ public virtual double GetHeaderHeight(int section)
+ {
+ return _visibleSections.ElementAt(section).HeaderHeight;
+ }
+
+ public static Tuple GetPath(Cell item)
+ {
+ if(item == null)
+ {
+ throw new ArgumentNullException(nameof(item));
+ }
+ return item.GetValue(PathProperty) as Tuple;
+ }
+
+ private static void SetPath(Cell item, Tuple index)
+ {
+ item?.SetValue(PathProperty, index);
+ }
+ }
+}
diff --git a/src/App/Controls/BoxedView/BoxedRoot.cs b/src/App/Controls/BoxedView/BoxedRoot.cs
new file mode 100644
index 000000000..3f061f4ee
--- /dev/null
+++ b/src/App/Controls/BoxedView/BoxedRoot.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using Xamarin.Forms;
+
+namespace Bit.App.Controls.BoxedView
+{
+ public class BoxedRoot : TableSectionBase
+ {
+ public BoxedRoot()
+ {
+ SetupEvents();
+ }
+
+ public event EventHandler SectionCollectionChanged;
+
+ private void ChildCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
+ {
+ SectionCollectionChanged?.Invoke(this, EventArgs.Empty);
+ }
+
+ private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if(e.PropertyName == TitleProperty.PropertyName)
+ {
+ OnPropertyChanged(TitleProperty.PropertyName);
+ }
+ else if(e.PropertyName == BoxedSection.FooterTextProperty.PropertyName)
+ {
+ OnPropertyChanged(BoxedSection.FooterTextProperty.PropertyName);
+ }
+ else if(e.PropertyName == BoxedSection.IsVisibleProperty.PropertyName)
+ {
+ OnPropertyChanged(BoxedSection.IsVisibleProperty.PropertyName);
+ }
+ }
+
+ private void SetupEvents()
+ {
+ CollectionChanged += (sender, args) =>
+ {
+ if(args.NewItems != null)
+ {
+ foreach(BoxedSection section in args.NewItems)
+ {
+ section.CollectionChanged += ChildCollectionChanged;
+ section.PropertyChanged += ChildPropertyChanged;
+ }
+ }
+ if(args.OldItems != null)
+ {
+ foreach(BoxedSection section in args.OldItems)
+ {
+ section.CollectionChanged -= ChildCollectionChanged;
+ section.PropertyChanged -= ChildPropertyChanged;
+ }
+ }
+ };
+ }
+ }
+}
diff --git a/src/App/Controls/BoxedView/BoxedSection.cs b/src/App/Controls/BoxedView/BoxedSection.cs
new file mode 100644
index 000000000..a04cb4622
--- /dev/null
+++ b/src/App/Controls/BoxedView/BoxedSection.cs
@@ -0,0 +1,147 @@
+using System.Collections;
+using System.Collections.Specialized;
+using Xamarin.Forms;
+
+namespace Bit.App.Controls.BoxedView
+{
+ public class BoxedSection : TableSectionBase
+ {
+ public static BindableProperty IsVisibleProperty = BindableProperty.Create(
+ nameof(IsVisible), typeof(bool), typeof(BoxedSection), true, defaultBindingMode: BindingMode.OneWay);
+
+ public static BindableProperty FooterTextProperty = BindableProperty.Create(
+ nameof(FooterText), typeof(string), typeof(BoxedSection), default(string),
+ defaultBindingMode: BindingMode.OneWay);
+
+ public static BindableProperty ItemTemplateProperty = BindableProperty.Create(
+ nameof(ItemTemplate), typeof(DataTemplate), typeof(BoxedSection), default(DataTemplate),
+ defaultBindingMode: BindingMode.OneWay);
+
+ public static BindableProperty ItemsSourceProperty = BindableProperty.Create(
+ nameof(ItemsSource), typeof(IList), typeof(BoxedSection), default(IList),
+ defaultBindingMode: BindingMode.OneWay, propertyChanged: ItemsChanged);
+
+ public static BindableProperty HeaderHeightProperty = BindableProperty.Create(
+ nameof(HeaderHeight), typeof(double), typeof(BoxedSection), -1d, defaultBindingMode: BindingMode.OneWay);
+
+ public static BindableProperty UseDragSortProperty = BindableProperty.Create(
+ nameof(UseDragSort), typeof(bool), typeof(BoxedSection), false, defaultBindingMode: BindingMode.OneWay);
+
+ public BoxedSection()
+ { }
+
+ public BoxedSection(string title)
+ : base(title)
+ { }
+
+ public bool IsVisible
+ {
+ get => (bool)GetValue(IsVisibleProperty);
+ set => SetValue(IsVisibleProperty, value);
+ }
+
+ public string FooterText
+ {
+ get => (string)GetValue(FooterTextProperty);
+ set => SetValue(FooterTextProperty, value);
+ }
+
+ public DataTemplate ItemTemplate
+ {
+ get => (DataTemplate)GetValue(ItemTemplateProperty);
+ set => SetValue(ItemTemplateProperty, value);
+ }
+
+ public IList ItemsSource
+ {
+ get => (IList)GetValue(ItemsSourceProperty);
+ set => SetValue(ItemsSourceProperty, value);
+ }
+
+ public double HeaderHeight
+ {
+ get => (double)GetValue(HeaderHeightProperty);
+ set => SetValue(HeaderHeightProperty, value);
+ }
+
+ public bool UseDragSort
+ {
+ get => (bool)GetValue(UseDragSortProperty);
+ set => SetValue(UseDragSortProperty, value);
+ }
+
+ private static void ItemsChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var section = bindable as BoxedSection;
+ if(section.ItemTemplate == null)
+ {
+ return;
+ }
+
+ if(oldValue is INotifyCollectionChanged oldObservableCollection)
+ {
+ oldObservableCollection.CollectionChanged -= section.OnItemsSourceCollectionChanged;
+ }
+ if(newValue is INotifyCollectionChanged newObservableCollection)
+ {
+ newObservableCollection.CollectionChanged += section.OnItemsSourceCollectionChanged;
+ }
+
+ section.Clear();
+
+ if(newValue is IList newValueAsEnumerable)
+ {
+ foreach(var item in newValueAsEnumerable)
+ {
+ var view = CreateChildViewFor(section.ItemTemplate, item, section);
+ section.Add(view);
+ }
+ }
+ }
+
+ private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ if(e.Action == NotifyCollectionChangedAction.Replace)
+ {
+ RemoveAt(e.OldStartingIndex);
+ var item = e.NewItems[e.NewStartingIndex];
+ var view = CreateChildViewFor(ItemTemplate, item, this);
+ Insert(e.NewStartingIndex, view);
+ }
+ else if(e.Action == NotifyCollectionChangedAction.Add)
+ {
+ if(e.NewItems != null)
+ {
+ for(var i = 0; i < e.NewItems.Count; ++i)
+ {
+ var item = e.NewItems[i];
+ var view = CreateChildViewFor(ItemTemplate, item, this);
+ Insert(i + e.NewStartingIndex, view);
+ }
+ }
+ }
+ else if(e.Action == NotifyCollectionChangedAction.Remove)
+ {
+ if(e.OldItems != null)
+ {
+ RemoveAt(e.OldStartingIndex);
+ }
+ }
+ else if(e.Action == NotifyCollectionChangedAction.Reset)
+ {
+ Clear();
+ }
+ }
+
+ private static Cell CreateChildViewFor(DataTemplate template, object item, BindableObject container)
+ {
+ if(template is DataTemplateSelector selector)
+ {
+ template = selector.SelectTemplate(item, container);
+ }
+ // Binding context
+ template.SetValue(BindingContextProperty, item);
+ return template.CreateContent() as Cell;
+ }
+ }
+}
diff --git a/src/App/Controls/BoxedView/BoxedView.cs b/src/App/Controls/BoxedView/BoxedView.cs
new file mode 100644
index 000000000..8fd14d4bf
--- /dev/null
+++ b/src/App/Controls/BoxedView/BoxedView.cs
@@ -0,0 +1,602 @@
+using System;
+using System.Collections;
+using System.Collections.Specialized;
+using System.Linq;
+using Xamarin.Forms;
+
+namespace Bit.App.Controls.BoxedView
+{
+ [ContentProperty("Root")]
+ public class BoxedView : TableView
+ {
+ private BoxedRoot _root;
+
+ public new event EventHandler ModelChanged;
+
+ public BoxedView()
+ {
+ VerticalOptions = HorizontalOptions = LayoutOptions.FillAndExpand;
+ Root = new BoxedRoot();
+ Model = new BoxedModel(Root);
+ }
+
+ public new BoxedModel Model { get; set; }
+
+ public new BoxedRoot Root
+ {
+ get => _root;
+ set
+ {
+ if(_root != null)
+ {
+ _root.PropertyChanged -= RootOnPropertyChanged;
+ _root.CollectionChanged -= OnCollectionChanged;
+ _root.SectionCollectionChanged -= OnSectionCollectionChanged;
+ }
+
+ _root = value;
+
+ // Transfer binding context to the children (maybe...)
+ SetInheritedBindingContext(_root, BindingContext);
+
+ _root.PropertyChanged += RootOnPropertyChanged;
+ _root.CollectionChanged += OnCollectionChanged;
+ _root.SectionCollectionChanged += OnSectionCollectionChanged;
+ }
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+ if(Root != null)
+ {
+ SetInheritedBindingContext(Root, BindingContext);
+ }
+ }
+
+ private void RootOnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if(e.PropertyName == TableSectionBase.TitleProperty.PropertyName ||
+ e.PropertyName == BoxedSection.FooterTextProperty.PropertyName ||
+ e.PropertyName == BoxedSection.IsVisibleProperty.PropertyName)
+ {
+ OnModelChanged();
+ }
+ }
+
+ protected override void OnPropertyChanged(string propertyName = null)
+ {
+ base.OnPropertyChanged(propertyName);
+ var changed = propertyName == HasUnevenRowsProperty.PropertyName ||
+ propertyName == HeaderHeightProperty.PropertyName ||
+ propertyName == HeaderFontSizeProperty.PropertyName ||
+ propertyName == HeaderTextColorProperty.PropertyName ||
+ propertyName == HeaderBackgroundColorProperty.PropertyName ||
+ propertyName == HeaderTextVerticalAlignProperty.PropertyName ||
+ propertyName == HeaderPaddingProperty.PropertyName ||
+ propertyName == FooterFontSizeProperty.PropertyName ||
+ propertyName == FooterTextColorProperty.PropertyName ||
+ propertyName == FooterBackgroundColorProperty.PropertyName ||
+ propertyName == FooterPaddingProperty.PropertyName;
+ if(changed)
+ {
+ OnModelChanged();
+ }
+ }
+
+ public void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ OnModelChanged();
+ }
+
+ public void OnSectionCollectionChanged(object sender, EventArgs childCollectionChangedEventArgs)
+ {
+ OnModelChanged();
+ }
+
+ protected new void OnModelChanged()
+ {
+ var cells = Root?.SelectMany(r => r);
+ if(cells == null)
+ {
+ return;
+ }
+ foreach(var cell in cells)
+ {
+ cell.Parent = this;
+ }
+ ModelChanged?.Invoke(this, EventArgs.Empty);
+ }
+
+ // Make the unnecessary property existing at TableView sealed.
+ private new int Intent { get; set; }
+
+ public static new BindableProperty BackgroundColorProperty =
+ BindableProperty.Create(
+ nameof(BackgroundColor),
+ typeof(Color),
+ typeof(BoxedView),
+ default(Color),
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ public new Color BackgroundColor
+ {
+ get { return (Color)GetValue(BackgroundColorProperty); }
+ set { SetValue(BackgroundColorProperty, value); }
+ }
+
+ public static BindableProperty SeparatorColorProperty =
+ BindableProperty.Create(
+ nameof(SeparatorColor),
+ typeof(Color),
+ typeof(BoxedView),
+ Color.FromRgb(199, 199, 204),
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ public Color SeparatorColor
+ {
+ get { return (Color)GetValue(SeparatorColorProperty); }
+ set { SetValue(SeparatorColorProperty, value); }
+ }
+
+ public static BindableProperty SelectedColorProperty =
+ BindableProperty.Create(
+ nameof(SelectedColor),
+ typeof(Color),
+ typeof(BoxedView),
+ default(Color),
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ public Color SelectedColor
+ {
+ get { return (Color)GetValue(SelectedColorProperty); }
+ set { SetValue(SelectedColorProperty, value); }
+ }
+
+ public static BindableProperty HeaderPaddingProperty =
+ BindableProperty.Create(
+ nameof(HeaderPadding),
+ typeof(Thickness),
+ typeof(BoxedView),
+ new Thickness(14, 8, 8, 8),
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ public Thickness HeaderPadding
+ {
+ get { return (Thickness)GetValue(HeaderPaddingProperty); }
+ set { SetValue(HeaderPaddingProperty, value); }
+ }
+
+ public static BindableProperty HeaderTextColorProperty =
+ BindableProperty.Create(
+ nameof(HeaderTextColor),
+ typeof(Color),
+ typeof(BoxedView),
+ default(Color),
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ public Color HeaderTextColor
+ {
+ get { return (Color)GetValue(HeaderTextColorProperty); }
+ set { SetValue(HeaderTextColorProperty, value); }
+ }
+
+ public static BindableProperty HeaderFontSizeProperty =
+ BindableProperty.Create(
+ nameof(HeaderFontSize),
+ typeof(double),
+ typeof(BoxedView),
+ -1.0d,
+ defaultBindingMode: BindingMode.OneWay,
+ defaultValueCreator: bindable => Device.GetNamedSize(NamedSize.Small, (BoxedView)bindable)
+ );
+
+ [TypeConverter(typeof(FontSizeConverter))]
+ public double HeaderFontSize
+ {
+ get { return (double)GetValue(HeaderFontSizeProperty); }
+ set { SetValue(HeaderFontSizeProperty, value); }
+ }
+
+ public static BindableProperty HeaderTextVerticalAlignProperty =
+ BindableProperty.Create(
+ nameof(HeaderTextVerticalAlign),
+ typeof(LayoutAlignment),
+ typeof(BoxedView),
+ LayoutAlignment.End,
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ public LayoutAlignment HeaderTextVerticalAlign
+ {
+ get { return (LayoutAlignment)GetValue(HeaderTextVerticalAlignProperty); }
+ set { SetValue(HeaderTextVerticalAlignProperty, value); }
+ }
+
+ public static BindableProperty HeaderBackgroundColorProperty =
+ BindableProperty.Create(
+ nameof(HeaderBackgroundColor),
+ typeof(Color),
+ typeof(BoxedView),
+ default(Color),
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ public Color HeaderBackgroundColor
+ {
+ get { return (Color)GetValue(HeaderBackgroundColorProperty); }
+ set { SetValue(HeaderBackgroundColorProperty, value); }
+ }
+
+ public static BindableProperty HeaderHeightProperty =
+ BindableProperty.Create(
+ nameof(HeaderHeight),
+ typeof(double),
+ typeof(BoxedView),
+ -1d,
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ public double HeaderHeight
+ {
+ get { return (double)GetValue(HeaderHeightProperty); }
+ set { SetValue(HeaderHeightProperty, value); }
+ }
+
+ public static BindableProperty FooterTextColorProperty =
+ BindableProperty.Create(
+ nameof(FooterTextColor),
+ typeof(Color),
+ typeof(BoxedView),
+ default(Color),
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ public Color FooterTextColor
+ {
+ get { return (Color)GetValue(FooterTextColorProperty); }
+ set { SetValue(FooterTextColorProperty, value); }
+ }
+
+ public static BindableProperty FooterFontSizeProperty =
+ BindableProperty.Create(
+ nameof(FooterFontSize),
+ typeof(double),
+ typeof(BoxedView),
+ -1.0d,
+ defaultBindingMode: BindingMode.OneWay,
+ defaultValueCreator: bindable => Device.GetNamedSize(NamedSize.Small, (BoxedView)bindable)
+ );
+
+ [TypeConverter(typeof(FontSizeConverter))]
+ public double FooterFontSize
+ {
+ get { return (double)GetValue(FooterFontSizeProperty); }
+ set { SetValue(FooterFontSizeProperty, value); }
+ }
+
+ public static BindableProperty FooterBackgroundColorProperty =
+ BindableProperty.Create(
+ nameof(FooterBackgroundColor),
+ typeof(Color),
+ typeof(BoxedView),
+ default(Color),
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ public Color FooterBackgroundColor
+ {
+ get { return (Color)GetValue(FooterBackgroundColorProperty); }
+ set { SetValue(FooterBackgroundColorProperty, value); }
+ }
+
+ public static BindableProperty FooterPaddingProperty =
+ BindableProperty.Create(
+ nameof(FooterPadding),
+ typeof(Thickness),
+ typeof(BoxedView),
+ new Thickness(14, 8, 14, 8),
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ public Thickness FooterPadding
+ {
+ get { return (Thickness)GetValue(FooterPaddingProperty); }
+ set { SetValue(FooterPaddingProperty, value); }
+ }
+
+ public static BindableProperty CellTitleColorProperty =
+ BindableProperty.Create(
+ nameof(CellTitleColor),
+ typeof(Color),
+ typeof(BoxedView),
+ default(Color),
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ public Color CellTitleColor
+ {
+ get { return (Color)GetValue(CellTitleColorProperty); }
+ set { SetValue(CellTitleColorProperty, value); }
+ }
+
+ public static BindableProperty CellTitleFontSizeProperty =
+ BindableProperty.Create(
+ nameof(CellTitleFontSize),
+ typeof(double),
+ typeof(BoxedView),
+ -1.0,
+ defaultBindingMode: BindingMode.OneWay,
+ defaultValueCreator: bindable => Device.GetNamedSize(NamedSize.Default, (BoxedView)bindable)
+ );
+
+
+ [TypeConverter(typeof(FontSizeConverter))]
+ public double CellTitleFontSize
+ {
+ get { return (double)GetValue(CellTitleFontSizeProperty); }
+ set { SetValue(CellTitleFontSizeProperty, value); }
+ }
+
+ public static BindableProperty CellValueTextColorProperty =
+ BindableProperty.Create(
+ nameof(CellValueTextColor),
+ typeof(Color),
+ typeof(BoxedView),
+ default(Color),
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ public Color CellValueTextColor
+ {
+ get { return (Color)GetValue(CellValueTextColorProperty); }
+ set { SetValue(CellValueTextColorProperty, value); }
+ }
+
+ public static BindableProperty CellValueTextFontSizeProperty =
+ BindableProperty.Create(
+ nameof(CellValueTextFontSize),
+ typeof(double),
+ typeof(BoxedView),
+ -1.0d,
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ [TypeConverter(typeof(FontSizeConverter))]
+ public double CellValueTextFontSize
+ {
+ get { return (double)GetValue(CellValueTextFontSizeProperty); }
+ set { SetValue(CellValueTextFontSizeProperty, value); }
+ }
+
+ public static BindableProperty CellDescriptionColorProperty =
+ BindableProperty.Create(
+ nameof(CellDescriptionColor),
+ typeof(Color),
+ typeof(BoxedView),
+ default(Color),
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ public Color CellDescriptionColor
+ {
+ get { return (Color)GetValue(CellDescriptionColorProperty); }
+ set { SetValue(CellDescriptionColorProperty, value); }
+ }
+
+ public static BindableProperty CellDescriptionFontSizeProperty =
+ BindableProperty.Create(
+ nameof(CellDescriptionFontSize),
+ typeof(double),
+ typeof(BoxedView),
+ -1.0d,
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ [TypeConverter(typeof(FontSizeConverter))]
+ public double CellDescriptionFontSize
+ {
+ get { return (double)GetValue(CellDescriptionFontSizeProperty); }
+ set { SetValue(CellDescriptionFontSizeProperty, value); }
+ }
+
+ public static BindableProperty CellBackgroundColorProperty =
+ BindableProperty.Create(
+ nameof(CellBackgroundColor),
+ typeof(Color),
+ typeof(BoxedView),
+ default(Color),
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ public Color CellBackgroundColor
+ {
+ get { return (Color)GetValue(CellBackgroundColorProperty); }
+ set { SetValue(CellBackgroundColorProperty, value); }
+ }
+
+ public static BindableProperty CellAccentColorProperty =
+ BindableProperty.Create(
+ nameof(CellAccentColor),
+ typeof(Color),
+ typeof(BoxedView),
+ Color.Accent,
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ public Color CellAccentColor
+ {
+ get { return (Color)GetValue(CellAccentColorProperty); }
+ set { SetValue(CellAccentColorProperty, value); }
+ }
+
+ public static BindableProperty ShowSectionTopBottomBorderProperty =
+ BindableProperty.Create(
+ nameof(ShowSectionTopBottomBorder),
+ typeof(bool),
+ typeof(BoxedView),
+ true,
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ public bool ShowSectionTopBottomBorder
+ {
+ get { return (bool)GetValue(ShowSectionTopBottomBorderProperty); }
+ set { SetValue(ShowSectionTopBottomBorderProperty, value); }
+ }
+
+ public static BindableProperty ScrollToBottomProperty =
+ BindableProperty.Create(
+ nameof(ScrollToBottom),
+ typeof(bool),
+ typeof(BoxedView),
+ default(bool),
+ defaultBindingMode: BindingMode.TwoWay
+ );
+
+ public bool ScrollToBottom
+ {
+ get { return (bool)GetValue(ScrollToBottomProperty); }
+ set { SetValue(ScrollToBottomProperty, value); }
+ }
+
+ public static BindableProperty ScrollToTopProperty =
+ BindableProperty.Create(
+ nameof(ScrollToTop),
+ typeof(bool),
+ typeof(BoxedView),
+ default(bool),
+ defaultBindingMode: BindingMode.TwoWay
+ );
+
+ public bool ScrollToTop
+ {
+ get { return (bool)GetValue(ScrollToTopProperty); }
+ set { SetValue(ScrollToTopProperty, value); }
+ }
+
+ public static BindableProperty VisibleContentHeightProperty =
+ BindableProperty.Create(
+ nameof(VisibleContentHeight),
+ typeof(double),
+ typeof(BoxedView),
+ -1d,
+ defaultBindingMode: BindingMode.OneWayToSource
+ );
+
+ public double VisibleContentHeight
+ {
+ get { return (double)GetValue(VisibleContentHeightProperty); }
+ set { SetValue(VisibleContentHeightProperty, value); }
+ }
+
+ public static BindableProperty ItemsSourceProperty =
+ BindableProperty.Create(
+ nameof(ItemsSource),
+ typeof(IEnumerable),
+ typeof(BoxedView),
+ default(IEnumerable),
+ defaultBindingMode: BindingMode.OneWay,
+ propertyChanged: ItemsChanged
+ );
+
+ public IEnumerable ItemsSource
+ {
+ get { return (IEnumerable)GetValue(ItemsSourceProperty); }
+ set { SetValue(ItemsSourceProperty, value); }
+ }
+
+ public static BindableProperty ItemTemplateProperty =
+ BindableProperty.Create(
+ nameof(ItemTemplate),
+ typeof(DataTemplate),
+ typeof(BoxedView),
+ default(DataTemplate),
+ defaultBindingMode: BindingMode.OneWay
+ );
+
+ public DataTemplate ItemTemplate
+ {
+ get { return (DataTemplate)GetValue(ItemTemplateProperty); }
+ set { SetValue(ItemTemplateProperty, value); }
+ }
+
+ private static void ItemsChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var boxedView = bindable as BoxedView;
+ if(boxedView.ItemTemplate == null)
+ {
+ return;
+ }
+
+ if(oldValue is INotifyCollectionChanged oldObservableCollection)
+ {
+ oldObservableCollection.CollectionChanged -= boxedView.OnItemsSourceCollectionChanged;
+ }
+ if(newValue is INotifyCollectionChanged newObservableCollection)
+ {
+ newObservableCollection.CollectionChanged += boxedView.OnItemsSourceCollectionChanged;
+ }
+
+ boxedView.Root.Clear();
+
+ if(newValue is IList newValueAsEnumerable)
+ {
+ foreach(var item in newValueAsEnumerable)
+ {
+ var view = CreateChildViewFor(boxedView.ItemTemplate, item, boxedView);
+ boxedView.Root.Add(view);
+ }
+ }
+ }
+
+ private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ if(e.Action == NotifyCollectionChangedAction.Replace)
+ {
+ Root.RemoveAt(e.OldStartingIndex);
+ var item = e.NewItems[e.NewStartingIndex];
+ var view = CreateChildViewFor(ItemTemplate, item, this);
+ Root.Insert(e.NewStartingIndex, view);
+ }
+ else if(e.Action == NotifyCollectionChangedAction.Add)
+ {
+ if(e.NewItems != null)
+ {
+ for(var i = 0; i < e.NewItems.Count; ++i)
+ {
+ var item = e.NewItems[i];
+ var view = CreateChildViewFor(ItemTemplate, item, this);
+ Root.Insert(i + e.NewStartingIndex, view);
+ }
+ }
+ }
+ else if(e.Action == NotifyCollectionChangedAction.Remove)
+ {
+ if(e.OldItems != null)
+ {
+ Root.RemoveAt(e.OldStartingIndex);
+ }
+ }
+ else if(e.Action == NotifyCollectionChangedAction.Reset)
+ {
+ Root.Clear();
+ }
+ }
+
+ private static BoxedSection CreateChildViewFor(DataTemplate template, object item, BindableObject container)
+ {
+ if(template is DataTemplateSelector selector)
+ {
+ template = selector.SelectTemplate(item, container);
+ }
+ template.SetValue(BindingContextProperty, item);
+ return template.CreateContent() as BoxedSection;
+ }
+ }
+}
diff --git a/src/App/Controls/BoxedView/Cells/BaseCell.cs b/src/App/Controls/BoxedView/Cells/BaseCell.cs
new file mode 100644
index 000000000..7a9bde8e2
--- /dev/null
+++ b/src/App/Controls/BoxedView/Cells/BaseCell.cs
@@ -0,0 +1,87 @@
+using System;
+using Xamarin.Forms;
+
+namespace Bit.App.Controls.BoxedView
+{
+ public class BaseCell : Cell
+ {
+ public new event EventHandler Tapped;
+
+ internal new void OnTapped()
+ {
+ Tapped?.Invoke(this, EventArgs.Empty);
+ }
+
+ public static BindableProperty TitleProperty = BindableProperty.Create(
+ nameof(Title), typeof(string), typeof(BaseCell), default(string), defaultBindingMode: BindingMode.OneWay);
+
+ public static BindableProperty TitleColorProperty = BindableProperty.Create(
+ nameof(TitleColor), typeof(Color), typeof(BaseCell), default(Color),
+ defaultBindingMode: BindingMode.OneWay);
+
+ public static BindableProperty TitleFontSizeProperty = BindableProperty.Create(
+ nameof(TitleFontSize), typeof(double), typeof(BaseCell), -1.0, defaultBindingMode: BindingMode.OneWay);
+
+ public static BindableProperty DescriptionProperty = BindableProperty.Create(
+ nameof(Description), typeof(string), typeof(BaseCell), default(string),
+ defaultBindingMode: BindingMode.OneWay);
+
+ public static BindableProperty DescriptionColorProperty = BindableProperty.Create(
+ nameof(DescriptionColor), typeof(Color), typeof(BaseCell), default(Color),
+ defaultBindingMode: BindingMode.OneWay);
+
+ public static BindableProperty DescriptionFontSizeProperty = BindableProperty.Create(
+ nameof(DescriptionFontSize), typeof(double), typeof(BaseCell), -1.0d,
+ defaultBindingMode: BindingMode.OneWay);
+
+ public static BindableProperty BackgroundColorProperty = BindableProperty.Create(
+ nameof(BackgroundColor), typeof(Color), typeof(BaseCell), default(Color),
+ defaultBindingMode: BindingMode.OneWay);
+
+ public string Title
+ {
+ get => (string)GetValue(TitleProperty);
+ set => SetValue(TitleProperty, value);
+ }
+
+ public Color TitleColor
+ {
+ get => (Color)GetValue(TitleColorProperty);
+ set => SetValue(TitleColorProperty, value);
+ }
+
+ [TypeConverter(typeof(FontSizeConverter))]
+ public double TitleFontSize
+ {
+ get => (double)GetValue(TitleFontSizeProperty);
+ set => SetValue(TitleFontSizeProperty, value);
+ }
+
+ public string Description
+ {
+ get => (string)GetValue(DescriptionProperty);
+ set => SetValue(DescriptionProperty, value);
+ }
+
+ public Color DescriptionColor
+ {
+ get => (Color)GetValue(DescriptionColorProperty);
+ set => SetValue(DescriptionColorProperty, value);
+ }
+
+ [TypeConverter(typeof(FontSizeConverter))]
+ public double DescriptionFontSize
+ {
+ get => (double)GetValue(DescriptionFontSizeProperty);
+ set => SetValue(DescriptionFontSizeProperty, value);
+ }
+
+ public Color BackgroundColor
+ {
+ get => (Color)GetValue(BackgroundColorProperty);
+ set => SetValue(BackgroundColorProperty, value);
+ }
+
+ public BoxedSection Section { get; set; }
+ }
+}
diff --git a/src/App/Controls/BoxedView/Cells/LabelCell.cs b/src/App/Controls/BoxedView/Cells/LabelCell.cs
new file mode 100644
index 000000000..2e4f4b73e
--- /dev/null
+++ b/src/App/Controls/BoxedView/Cells/LabelCell.cs
@@ -0,0 +1,38 @@
+using Xamarin.Forms;
+
+namespace Bit.App.Controls.BoxedView
+{
+ public class LabelCell : BaseCell
+ {
+ public static BindableProperty ValueTextProperty = BindableProperty.Create(
+ nameof(ValueText), typeof(string), typeof(LabelCell), default(string),
+ defaultBindingMode: BindingMode.OneWay);
+
+ public static BindableProperty ValueTextColorProperty = BindableProperty.Create(
+ nameof(ValueTextColor), typeof(Color), typeof(LabelCell), default(Color),
+ defaultBindingMode: BindingMode.OneWay);
+
+ public static BindableProperty ValueTextFontSizeProperty = BindableProperty.Create(
+ nameof(ValueTextFontSize), typeof(double), typeof(LabelCell), -1.0d,
+ defaultBindingMode: BindingMode.OneWay);
+
+ public string ValueText
+ {
+ get => (string)GetValue(ValueTextProperty);
+ set => SetValue(ValueTextProperty, value);
+ }
+
+ public Color ValueTextColor
+ {
+ get => (Color)GetValue(ValueTextColorProperty);
+ set => SetValue(ValueTextColorProperty, value);
+ }
+
+ [TypeConverter(typeof(FontSizeConverter))]
+ public double ValueTextFontSize
+ {
+ get => (double)GetValue(ValueTextFontSizeProperty);
+ set => SetValue(ValueTextFontSizeProperty, value);
+ }
+ }
+}
diff --git a/src/App/Pages/BaseViewModel.cs b/src/App/Pages/BaseViewModel.cs
index 1f2e412c8..da6a1d453 100644
--- a/src/App/Pages/BaseViewModel.cs
+++ b/src/App/Pages/BaseViewModel.cs
@@ -1,4 +1,5 @@
using Bit.Core.Utilities;
+using Xamarin.Forms;
namespace Bit.App.Pages
{
@@ -11,5 +12,7 @@ namespace Bit.App.Pages
get => _pageTitle;
set => SetProperty(ref _pageTitle, value);
}
+
+ public ContentPage Page { get; set; }
}
}
diff --git a/src/App/Pages/SettingsPage.xaml b/src/App/Pages/SettingsPage.xaml
index 7654d4b81..da33251a0 100644
--- a/src/App/Pages/SettingsPage.xaml
+++ b/src/App/Pages/SettingsPage.xaml
@@ -4,21 +4,17 @@
x:Class="Bit.App.Pages.SettingsPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
+ xmlns:bv="clr-namespace:Bit.App.Controls.BoxedView"
x:DataType="pages:SettingsPageViewModel"
Title="{Binding PageTitle}">
-
-
-
-
-
-
+
+
+
+
+
|