mirror of
https://github.com/bitwarden/mobile
synced 2025-12-21 10:43:22 +00:00
[PM-6496] Improved iOS extensions cipher cell UI (#3058)
* PM-6496 Improved iOS extensions cipher list to have an updated UI for each cell * PM-6496 Improved UI on iOS extensions list cells
This commit is contained in:
committed by
GitHub
parent
39187732c0
commit
67f7b3156e
@@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
|
using Bit.iOS.Core.Utilities;
|
||||||
using Foundation;
|
using Foundation;
|
||||||
using ObjCRuntime;
|
using ObjCRuntime;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
@@ -27,9 +28,9 @@ namespace Bit.iOS.Autofill.ListItems
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_header.TextColor = UIColor.FromName(ColorConstants.LIGHT_TEXT_MUTED);
|
_header.TextColor = ThemeHelpers.TextColor;
|
||||||
_header.Font = UIFont.SystemFontOfSize(15);
|
_header.Font = UIFont.SystemFontOfSize(15, UIFontWeight.Semibold);
|
||||||
_separator.BackgroundColor = UIColor.FromName(ColorConstants.LIGHT_SECONDARY_300);
|
_separator.BackgroundColor = ThemeHelpers.SeparatorColor;
|
||||||
|
|
||||||
_header.TranslatesAutoresizingMaskIntoConstraints = false;
|
_header.TranslatesAutoresizingMaskIntoConstraints = false;
|
||||||
_separator.TranslatesAutoresizingMaskIntoConstraints = false;
|
_separator.TranslatesAutoresizingMaskIntoConstraints = false;
|
||||||
@@ -39,14 +40,14 @@ namespace Bit.iOS.Autofill.ListItems
|
|||||||
|
|
||||||
NSLayoutConstraint.ActivateConstraints(new NSLayoutConstraint[]
|
NSLayoutConstraint.ActivateConstraints(new NSLayoutConstraint[]
|
||||||
{
|
{
|
||||||
_header.LeadingAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.LeadingAnchor, 9),
|
_header.LeadingAnchor.ConstraintEqualTo(ContentView.LeadingAnchor, 9),
|
||||||
_header.TrailingAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.TrailingAnchor, 9),
|
_header.TrailingAnchor.ConstraintEqualTo(ContentView.TrailingAnchor, 9),
|
||||||
_header.TopAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.TopAnchor, 3),
|
_header.TopAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.TopAnchor, 10),
|
||||||
|
|
||||||
_separator.HeightAnchor.ConstraintEqualTo(2),
|
_separator.HeightAnchor.ConstraintEqualTo(1),
|
||||||
_separator.TopAnchor.ConstraintEqualTo(_header.BottomAnchor, 8),
|
_separator.TopAnchor.ConstraintEqualTo(_header.BottomAnchor, 12),
|
||||||
_separator.LeadingAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.LeadingAnchor, 5),
|
_separator.LeadingAnchor.ConstraintEqualTo(ContentView.LeadingAnchor, 7),
|
||||||
_separator.TrailingAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.TrailingAnchor, 5),
|
_separator.TrailingAnchor.ConstraintEqualTo(ContentView.TrailingAnchor, -7),
|
||||||
_separator.BottomAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.BottomAnchor, 2)
|
_separator.BottomAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.BottomAnchor, 2)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,10 +61,12 @@ namespace Bit.iOS.Autofill
|
|||||||
NavItem.Title = Context.IsCreatingPasskey ? AppResources.SavePasskey : AppResources.Items;
|
NavItem.Title = Context.IsCreatingPasskey ? AppResources.SavePasskey : AppResources.Items;
|
||||||
_cancelButton.Title = AppResources.Cancel;
|
_cancelButton.Title = AppResources.Cancel;
|
||||||
|
|
||||||
TableView.RowHeight = UITableView.AutomaticDimension;
|
|
||||||
TableView.EstimatedRowHeight = 44;
|
|
||||||
TableView.BackgroundColor = ThemeHelpers.BackgroundColor;
|
TableView.BackgroundColor = ThemeHelpers.BackgroundColor;
|
||||||
TableView.Source = new TableSource(this);
|
|
||||||
|
var tableSource = new TableSource(this);
|
||||||
|
TableView.Source = tableSource;
|
||||||
|
tableSource.RegisterTableViewCells(TableView);
|
||||||
|
|
||||||
if (Context.IsCreatingPasskey)
|
if (Context.IsCreatingPasskey)
|
||||||
{
|
{
|
||||||
TableView.SectionHeaderHeight = 55;
|
TableView.SectionHeaderHeight = 55;
|
||||||
|
|||||||
@@ -35,7 +35,11 @@ namespace Bit.iOS.Autofill
|
|||||||
|
|
||||||
TableView.RowHeight = UITableView.AutomaticDimension;
|
TableView.RowHeight = UITableView.AutomaticDimension;
|
||||||
TableView.EstimatedRowHeight = 44;
|
TableView.EstimatedRowHeight = 44;
|
||||||
TableView.Source = new TableSource(this);
|
|
||||||
|
var tableSource = new TableSource(this);
|
||||||
|
TableView.Source = tableSource;
|
||||||
|
tableSource.RegisterTableViewCells(TableView);
|
||||||
|
|
||||||
SearchBar.Delegate = new ExtensionSearchDelegate(TableView);
|
SearchBar.Delegate = new ExtensionSearchDelegate(TableView);
|
||||||
await ((TableSource)TableView.Source).LoadAsync(false, SearchBar.Text);
|
await ((TableSource)TableView.Source).LoadAsync(false, SearchBar.Text);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
using Bit.iOS.Core.Utilities;
|
using Bit.iOS.Core.Utilities;
|
||||||
|
using CoreGraphics;
|
||||||
|
using Foundation;
|
||||||
|
using ObjCRuntime;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
|
|
||||||
namespace Bit.iOS.Core.Controllers
|
namespace Bit.iOS.Core.Controllers
|
||||||
@@ -7,15 +10,40 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
{
|
{
|
||||||
public ExtendedUITableViewCell()
|
public ExtendedUITableViewCell()
|
||||||
{
|
{
|
||||||
BackgroundColor = ThemeHelpers.BackgroundColor;
|
ApplyTheme();
|
||||||
if (!ThemeHelpers.LightTheme)
|
|
||||||
{
|
|
||||||
SelectionStyle = UITableViewCellSelectionStyle.None;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExtendedUITableViewCell(UITableViewCellStyle style, string reusedId)
|
public ExtendedUITableViewCell(NSCoder coder) : base(coder)
|
||||||
: base(style, reusedId)
|
{
|
||||||
|
ApplyTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExtendedUITableViewCell(CGRect frame) : base(frame)
|
||||||
|
{
|
||||||
|
ApplyTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExtendedUITableViewCell(UITableViewCellStyle style, string reuseIdentifier) : base(style, reuseIdentifier)
|
||||||
|
{
|
||||||
|
ApplyTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExtendedUITableViewCell(UITableViewCellStyle style, NSString? reuseIdentifier) : base(style, reuseIdentifier)
|
||||||
|
{
|
||||||
|
ApplyTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ExtendedUITableViewCell(NSObjectFlag t) : base(t)
|
||||||
|
{
|
||||||
|
ApplyTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal ExtendedUITableViewCell(NativeHandle handle) : base(handle)
|
||||||
|
{
|
||||||
|
ApplyTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyTheme()
|
||||||
{
|
{
|
||||||
BackgroundColor = ThemeHelpers.BackgroundColor;
|
BackgroundColor = ThemeHelpers.BackgroundColor;
|
||||||
if (!ThemeHelpers.LightTheme)
|
if (!ThemeHelpers.LightTheme)
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ namespace Bit.iOS.Core.Models
|
|||||||
public CipherView CipherView { get; set; }
|
public CipherView CipherView { get; set; }
|
||||||
public CipherRepromptType Reprompt { get; set; }
|
public CipherRepromptType Reprompt { get; set; }
|
||||||
|
|
||||||
|
public bool HasFido2Credential => CipherView?.HasFido2Credential ?? false;
|
||||||
|
|
||||||
|
public bool IsShared => CipherView?.Shared ?? false;
|
||||||
|
|
||||||
public class LoginUriModel
|
public class LoginUriModel
|
||||||
{
|
{
|
||||||
public LoginUriModel(LoginUriView data)
|
public LoginUriModel(LoginUriView data)
|
||||||
|
|||||||
110
src/iOS.Core/Views/CipherLoginTableViewCell.cs
Normal file
110
src/iOS.Core/Views/CipherLoginTableViewCell.cs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
using Bit.Core;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.iOS.Core.Controllers;
|
||||||
|
using Bit.iOS.Core.Utilities;
|
||||||
|
using ObjCRuntime;
|
||||||
|
using UIKit;
|
||||||
|
|
||||||
|
namespace Bit.iOS.Core.Views
|
||||||
|
{
|
||||||
|
public static class NSLayoutConstraintExt
|
||||||
|
{
|
||||||
|
public static NSLayoutConstraint WithId(this NSLayoutConstraint constraint, string id)
|
||||||
|
{
|
||||||
|
constraint.SetIdentifier(id);
|
||||||
|
return constraint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CipherLoginTableViewCell : ExtendedUITableViewCell
|
||||||
|
{
|
||||||
|
private readonly UILabel _title = new UILabel();
|
||||||
|
private readonly UILabel _subtitle = new UILabel();
|
||||||
|
private readonly UILabel _mainIcon = new UILabel();
|
||||||
|
private readonly UILabel _orgIcon = new UILabel();
|
||||||
|
private readonly UIView _separator = new UIView();
|
||||||
|
|
||||||
|
private UIStackView _mainStackView, _titleStackView;
|
||||||
|
|
||||||
|
protected internal CipherLoginTableViewCell(NativeHandle handle) : base(handle)
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Setup()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_title.TextColor = ThemeHelpers.TextColor;
|
||||||
|
_title.Font = UIFont.SystemFontOfSize(14);
|
||||||
|
_title.LineBreakMode = UILineBreakMode.TailTruncation;
|
||||||
|
_title.Lines = 1;
|
||||||
|
|
||||||
|
_subtitle.TextColor = ThemeHelpers.MutedColor;
|
||||||
|
_subtitle.Font = UIFont.SystemFontOfSize(12);
|
||||||
|
_subtitle.LineBreakMode = UILineBreakMode.TailTruncation;
|
||||||
|
_subtitle.Lines = 1;
|
||||||
|
|
||||||
|
_mainIcon.Font = UIFont.FromName("bwi-font", 24);
|
||||||
|
_mainIcon.AdjustsFontSizeToFitWidth = true;
|
||||||
|
_mainIcon.TextColor = ThemeHelpers.PrimaryColor;
|
||||||
|
|
||||||
|
_orgIcon.Font = UIFont.FromName("bwi-font", 15);
|
||||||
|
_orgIcon.TextColor = ThemeHelpers.MutedColor;
|
||||||
|
_orgIcon.Text = BitwardenIcons.Collection;
|
||||||
|
_orgIcon.Hidden = true;
|
||||||
|
|
||||||
|
_separator.BackgroundColor = ThemeHelpers.SeparatorColor;
|
||||||
|
|
||||||
|
_titleStackView = new UIStackView(new UIView[] { _title, _orgIcon })
|
||||||
|
{
|
||||||
|
Axis = UILayoutConstraintAxis.Horizontal,
|
||||||
|
Spacing = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
_mainStackView = new UIStackView(new UIView[] { _titleStackView, _subtitle })
|
||||||
|
{
|
||||||
|
Axis = UILayoutConstraintAxis.Vertical
|
||||||
|
};
|
||||||
|
|
||||||
|
_mainIcon.TranslatesAutoresizingMaskIntoConstraints = false;
|
||||||
|
_separator.TranslatesAutoresizingMaskIntoConstraints = false;
|
||||||
|
_mainStackView.TranslatesAutoresizingMaskIntoConstraints = false;
|
||||||
|
|
||||||
|
ContentView.AddSubview(_mainStackView);
|
||||||
|
ContentView.AddSubview(_mainIcon);
|
||||||
|
ContentView.AddSubview(_separator);
|
||||||
|
|
||||||
|
NSLayoutConstraint.ActivateConstraints(new NSLayoutConstraint[]
|
||||||
|
{
|
||||||
|
_mainIcon.LeadingAnchor.ConstraintEqualTo(ContentView.LeadingAnchor, 9),
|
||||||
|
_mainIcon.CenterYAnchor.ConstraintEqualTo(ContentView.CenterYAnchor),
|
||||||
|
_mainIcon.WidthAnchor.ConstraintEqualTo(31),
|
||||||
|
_mainIcon.HeightAnchor.ConstraintEqualTo(31),
|
||||||
|
|
||||||
|
_mainStackView.LeadingAnchor.ConstraintEqualTo(_mainIcon.TrailingAnchor, 3),
|
||||||
|
_mainStackView.TopAnchor.ConstraintEqualTo(ContentView.TopAnchor, 8),
|
||||||
|
_mainStackView.TrailingAnchor.ConstraintLessThanOrEqualTo(ContentView.TrailingAnchor, 9),
|
||||||
|
|
||||||
|
_separator.HeightAnchor.ConstraintEqualTo(1),
|
||||||
|
_separator.TopAnchor.ConstraintEqualTo(_mainStackView.BottomAnchor, 8),
|
||||||
|
_separator.LeadingAnchor.ConstraintEqualTo(ContentView.LeadingAnchor, 7),
|
||||||
|
_separator.TrailingAnchor.ConstraintEqualTo(ContentView.TrailingAnchor, -7),
|
||||||
|
_separator.BottomAnchor.ConstraintEqualTo(ContentView.BottomAnchor, 0)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetTitle(string title) => _title.Text = title;
|
||||||
|
|
||||||
|
public void SetSubtitle(string subtitle) => _subtitle.Text = subtitle;
|
||||||
|
|
||||||
|
public void SetHasFido2Credential(bool has) => _mainIcon.Text = has ? BitwardenIcons.Passkey : BitwardenIcons.Globe;
|
||||||
|
|
||||||
|
public void ShowOrganizationIcon() => _orgIcon.Hidden = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Diagnostics;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Resources.Localization;
|
using Bit.Core.Resources.Localization;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
@@ -13,7 +12,7 @@ namespace Bit.iOS.Core.Views
|
|||||||
{
|
{
|
||||||
public class ExtensionTableSource : ExtendedUITableViewSource
|
public class ExtensionTableSource : ExtendedUITableViewSource
|
||||||
{
|
{
|
||||||
private const string CellIdentifier = "TableCell";
|
public const string CellIdentifier = nameof(CipherLoginTableViewCell);
|
||||||
|
|
||||||
private IEnumerable<CipherViewModel> _allItems = new List<CipherViewModel>();
|
private IEnumerable<CipherViewModel> _allItems = new List<CipherViewModel>();
|
||||||
protected ICipherService _cipherService;
|
protected ICipherService _cipherService;
|
||||||
@@ -89,13 +88,16 @@ namespace Bit.iOS.Core.Views
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//public IEnumerable<CipherViewModel> TableItems { get; set; }
|
|
||||||
|
|
||||||
public override nint RowsInSection(UITableView tableview, nint section)
|
public override nint RowsInSection(UITableView tableview, nint section)
|
||||||
{
|
{
|
||||||
return Items == null || Items.Count() == 0 ? 1 : Items.Count();
|
return Items == null || Items.Count() == 0 ? 1 : Items.Count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RegisterTableViewCells(UITableView tableView)
|
||||||
|
{
|
||||||
|
tableView.RegisterClassForCellReuse(typeof(CipherLoginTableViewCell), CellIdentifier);
|
||||||
|
}
|
||||||
|
|
||||||
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
|
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
|
||||||
{
|
{
|
||||||
if (Items == null || Items.Count() == 0)
|
if (Items == null || Items.Count() == 0)
|
||||||
@@ -110,14 +112,9 @@ namespace Bit.iOS.Core.Views
|
|||||||
}
|
}
|
||||||
|
|
||||||
var cell = tableView.DequeueReusableCell(CellIdentifier);
|
var cell = tableView.DequeueReusableCell(CellIdentifier);
|
||||||
|
if (cell is null)
|
||||||
// if there are no cells to reuse, create a new one
|
|
||||||
if (cell == null)
|
|
||||||
{
|
{
|
||||||
Debug.WriteLine("BW Log, Make new cell for list.");
|
throw new InvalidOperationException($"The cell {CellIdentifier} has not been registered in the UITableView");
|
||||||
cell = new ExtendedUITableViewCell(UITableViewCellStyle.Subtitle, CellIdentifier);
|
|
||||||
cell.TextLabel.TextColor = cell.TextLabel.TintColor = ThemeHelpers.TextColor;
|
|
||||||
cell.DetailTextLabel.TextColor = cell.DetailTextLabel.TintColor = ThemeHelpers.MutedColor;
|
|
||||||
}
|
}
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
@@ -126,15 +123,35 @@ namespace Bit.iOS.Core.Views
|
|||||||
{
|
{
|
||||||
if (Items == null
|
if (Items == null
|
||||||
|| !Items.Any()
|
|| !Items.Any()
|
||||||
|| cell?.TextLabel == null
|
|| !(cell is CipherLoginTableViewCell cipherCell))
|
||||||
|| cell.DetailTextLabel == null)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var item = Items.ElementAt(indexPath.Row);
|
var item = Items.ElementAt(indexPath.Row);
|
||||||
cell.TextLabel.Text = item.Name;
|
if (item is null)
|
||||||
cell.DetailTextLabel.Text = item.Username;
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cipherCell.SetTitle(item.Name);
|
||||||
|
cipherCell.SetSubtitle(item.Username);
|
||||||
|
cipherCell.SetHasFido2Credential(item.HasFido2Credential);
|
||||||
|
if (item.IsShared)
|
||||||
|
{
|
||||||
|
cipherCell.ShowOrganizationIcon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
|
||||||
|
{
|
||||||
|
if (Items == null
|
||||||
|
|| !Items.Any())
|
||||||
|
{
|
||||||
|
return base.GetHeightForRow(tableView, indexPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 55;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string?> GetTotpAsync(CipherViewModel item)
|
public async Task<string?> GetTotpAsync(CipherViewModel item)
|
||||||
|
|||||||
@@ -41,9 +41,11 @@ namespace Bit.iOS.Extension
|
|||||||
{
|
{
|
||||||
CancelBarButton.Title = AppResources.Cancel;
|
CancelBarButton.Title = AppResources.Cancel;
|
||||||
}
|
}
|
||||||
TableView.RowHeight = UITableView.AutomaticDimension;
|
|
||||||
TableView.EstimatedRowHeight = 44;
|
var tableSource = new TableSource(this);
|
||||||
TableView.Source = new TableSource(this);
|
TableView.Source = tableSource;
|
||||||
|
tableSource.RegisterTableViewCells(TableView);
|
||||||
|
|
||||||
await ((TableSource)TableView.Source).LoadAsync();
|
await ((TableSource)TableView.Source).LoadAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user