BoxedView with LabelCell

This commit is contained in:
Kyle Spearrin 2019-04-04 22:28:03 -04:00
parent 2b2342bcad
commit 61e95e03c8
21 changed files with 2945 additions and 285 deletions

View File

@ -63,8 +63,15 @@
<PackageReference Include="Xamarin.Android.Support.v7.MediaRouter" Version="28.0.0.1" />
</ItemGroup>
<ItemGroup>
<Compile Include="Renderers\BoxedView\BoxedViewRecyclerAdapter.cs" />
<Compile Include="Renderers\BoxedView\BoxedViewRenderer.cs" />
<Compile Include="Renderers\BoxedView\BoxedViewSimpleCallback.cs" />
<Compile Include="Renderers\BoxedView\Cells\BaseCellRenderer.cs" />
<Compile Include="Renderers\BoxedView\Cells\BaseCellView.cs" />
<Compile Include="Renderers\BoxedView\Cells\LabelCellRenderer.cs" />
<Compile Include="MainApplication.cs" />
<Compile Include="MainActivity.cs" />
<Compile Include="Renderers\RendererUtils.cs" />
<Compile Include="Resources\Resource.designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\CryptoPrimitiveService.cs" />
@ -194,5 +201,17 @@
<ItemGroup>
<AndroidResource Include="Resources\drawable\lock.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\CellBaseView.axml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\ContentCell.axml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\FooterCell.axml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\HeaderCell.axml" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
</Project>

View File

@ -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<Type, int> _viewTypes;
private List<CellCache> _cellCaches;
internal List<CellCache> 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<ViewHolder> _viewHolders = new List<ViewHolder>();
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<LinearLayout>(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<CellCache>();
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<TextView>(Resource.Id.HeaderCellText);
Border = view.FindViewById<LinearLayout>(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<TextView>(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<LinearLayout>(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);
}
}
}

View File

@ -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<BoxedView, RecyclerView>
{
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<BoxedView> 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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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<TNativeCell> : CellRenderer where TNativeCell : BaseCellView
{
internal static class InstanceCreator<T1, T2, TInstance>
{
public static Func<T1, T2, TInstance> Create { get; } = CreateInstance();
private static Func<T1, T2, TInstance> 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<Func<T1, T2, TInstance>>(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<Context, Xamarin.Forms.Cell, TNativeCell>.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;
}
}
}
}
}

View File

@ -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<TextView>(Resource.Id.CellTitle);
DescriptionLabel = contentView.FindViewById<TextView>(Resource.Id.CellDescription);
ContentStack = contentView.FindViewById<LinearLayout>(Resource.Id.CellContentStack);
AccessoryStack = contentView.FindViewById<LinearLayout>(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);
}
}
}

View File

@ -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<LabelCellView>
{ }
[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);
}
}
}

View File

@ -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,
});
}
}
}

View File

@ -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()
{

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="44dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="4dp"
android:paddingBottom="4dp">
<LinearLayout
android:id="@+id/CellBody"
android:orientation="vertical"
android:layout_centerVertical="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="4dp">
<LinearLayout
android:id="@+id/CellContentStack"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:weightSum="1">
<TextView
android:id="@+id/CellTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="end"
android:gravity="left"
android:paddingRight="6dp"
android:layout_gravity="left|center_vertical" />
</LinearLayout>
<TextView
android:id="@+id/CellDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left"
android:layout_gravity="left"
android:layout_marginTop="2dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/CellAccessoryView"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical" />
</RelativeLayout>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="44dp">
<LinearLayout
android:id="@+id/ContentCellBody"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
android:id="@+id/ContentCellBorder"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="1px" />
</RelativeLayout>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="0dp">
<TextView
android:id="@+id/FooterCellText"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top|left" />
</RelativeLayout>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="0dp">
<TextView
android:id="@+id/HeaderCellText"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="left" />
<LinearLayout
android:id="@+id/HeaderCellBorder"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="1px" />
</RelativeLayout>

View File

@ -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<int, int>), typeof(Cell), null);
private BoxedRoot _root;
private IEnumerable<BoxedSection> _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<int, int>(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<int, int> GetPath(Cell item)
{
if(item == null)
{
throw new ArgumentNullException(nameof(item));
}
return item.GetValue(PathProperty) as Tuple<int, int>;
}
private static void SetPath(Cell item, Tuple<int, int> index)
{
item?.SetValue(PathProperty, index);
}
}
}

View File

@ -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<BoxedSection>
{
public BoxedRoot()
{
SetupEvents();
}
public event EventHandler<EventArgs> 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;
}
}
};
}
}
}

View File

@ -0,0 +1,147 @@
using System.Collections;
using System.Collections.Specialized;
using Xamarin.Forms;
namespace Bit.App.Controls.BoxedView
{
public class BoxedSection : TableSectionBase<Cell>
{
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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}
}

View File

@ -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; }
}
}

View File

@ -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}">
<ContentPage.BindingContext>
<pages:SettingsPageViewModel />
</ContentPage.BindingContext>
<StackLayout>
<!-- Place new controls here -->
<Label Text="Settings!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<controls:FaLabel Text="&#xf2b9;"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
</controls:FaLabel>
</StackLayout>
<bv:BoxedView>
<bv:BoxedSection Title="The Title" FooterText="The Footer">
<bv:LabelCell Title="The title" ValueText="The value" />
</bv:BoxedSection>
</bv:BoxedView>
</ContentPage>