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.iOS.Core.Utilities;
|
||||
using Foundation;
|
||||
using ObjCRuntime;
|
||||
using UIKit;
|
||||
@@ -27,9 +28,9 @@ namespace Bit.iOS.Autofill.ListItems
|
||||
{
|
||||
try
|
||||
{
|
||||
_header.TextColor = UIColor.FromName(ColorConstants.LIGHT_TEXT_MUTED);
|
||||
_header.Font = UIFont.SystemFontOfSize(15);
|
||||
_separator.BackgroundColor = UIColor.FromName(ColorConstants.LIGHT_SECONDARY_300);
|
||||
_header.TextColor = ThemeHelpers.TextColor;
|
||||
_header.Font = UIFont.SystemFontOfSize(15, UIFontWeight.Semibold);
|
||||
_separator.BackgroundColor = ThemeHelpers.SeparatorColor;
|
||||
|
||||
_header.TranslatesAutoresizingMaskIntoConstraints = false;
|
||||
_separator.TranslatesAutoresizingMaskIntoConstraints = false;
|
||||
@@ -39,14 +40,14 @@ namespace Bit.iOS.Autofill.ListItems
|
||||
|
||||
NSLayoutConstraint.ActivateConstraints(new NSLayoutConstraint[]
|
||||
{
|
||||
_header.LeadingAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.LeadingAnchor, 9),
|
||||
_header.TrailingAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.TrailingAnchor, 9),
|
||||
_header.TopAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.TopAnchor, 3),
|
||||
_header.LeadingAnchor.ConstraintEqualTo(ContentView.LeadingAnchor, 9),
|
||||
_header.TrailingAnchor.ConstraintEqualTo(ContentView.TrailingAnchor, 9),
|
||||
_header.TopAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.TopAnchor, 10),
|
||||
|
||||
_separator.HeightAnchor.ConstraintEqualTo(2),
|
||||
_separator.TopAnchor.ConstraintEqualTo(_header.BottomAnchor, 8),
|
||||
_separator.LeadingAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.LeadingAnchor, 5),
|
||||
_separator.TrailingAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.TrailingAnchor, 5),
|
||||
_separator.HeightAnchor.ConstraintEqualTo(1),
|
||||
_separator.TopAnchor.ConstraintEqualTo(_header.BottomAnchor, 12),
|
||||
_separator.LeadingAnchor.ConstraintEqualTo(ContentView.LeadingAnchor, 7),
|
||||
_separator.TrailingAnchor.ConstraintEqualTo(ContentView.TrailingAnchor, -7),
|
||||
_separator.BottomAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.BottomAnchor, 2)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -61,10 +61,12 @@ namespace Bit.iOS.Autofill
|
||||
NavItem.Title = Context.IsCreatingPasskey ? AppResources.SavePasskey : AppResources.Items;
|
||||
_cancelButton.Title = AppResources.Cancel;
|
||||
|
||||
TableView.RowHeight = UITableView.AutomaticDimension;
|
||||
TableView.EstimatedRowHeight = 44;
|
||||
TableView.BackgroundColor = ThemeHelpers.BackgroundColor;
|
||||
TableView.Source = new TableSource(this);
|
||||
|
||||
var tableSource = new TableSource(this);
|
||||
TableView.Source = tableSource;
|
||||
tableSource.RegisterTableViewCells(TableView);
|
||||
|
||||
if (Context.IsCreatingPasskey)
|
||||
{
|
||||
TableView.SectionHeaderHeight = 55;
|
||||
|
||||
@@ -35,7 +35,11 @@ namespace Bit.iOS.Autofill
|
||||
|
||||
TableView.RowHeight = UITableView.AutomaticDimension;
|
||||
TableView.EstimatedRowHeight = 44;
|
||||
TableView.Source = new TableSource(this);
|
||||
|
||||
var tableSource = new TableSource(this);
|
||||
TableView.Source = tableSource;
|
||||
tableSource.RegisterTableViewCells(TableView);
|
||||
|
||||
SearchBar.Delegate = new ExtensionSearchDelegate(TableView);
|
||||
await ((TableSource)TableView.Source).LoadAsync(false, SearchBar.Text);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using Bit.iOS.Core.Utilities;
|
||||
using CoreGraphics;
|
||||
using Foundation;
|
||||
using ObjCRuntime;
|
||||
using UIKit;
|
||||
|
||||
namespace Bit.iOS.Core.Controllers
|
||||
@@ -7,15 +10,40 @@ namespace Bit.iOS.Core.Controllers
|
||||
{
|
||||
public ExtendedUITableViewCell()
|
||||
{
|
||||
BackgroundColor = ThemeHelpers.BackgroundColor;
|
||||
if (!ThemeHelpers.LightTheme)
|
||||
{
|
||||
SelectionStyle = UITableViewCellSelectionStyle.None;
|
||||
}
|
||||
ApplyTheme();
|
||||
}
|
||||
|
||||
public ExtendedUITableViewCell(UITableViewCellStyle style, string reusedId)
|
||||
: base(style, reusedId)
|
||||
public ExtendedUITableViewCell(NSCoder coder) : base(coder)
|
||||
{
|
||||
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;
|
||||
if (!ThemeHelpers.LightTheme)
|
||||
|
||||
@@ -31,6 +31,10 @@ namespace Bit.iOS.Core.Models
|
||||
public CipherView CipherView { get; set; }
|
||||
public CipherRepromptType Reprompt { get; set; }
|
||||
|
||||
public bool HasFido2Credential => CipherView?.HasFido2Credential ?? false;
|
||||
|
||||
public bool IsShared => CipherView?.Shared ?? false;
|
||||
|
||||
public class LoginUriModel
|
||||
{
|
||||
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.Resources.Localization;
|
||||
using Bit.Core.Utilities;
|
||||
@@ -13,7 +12,7 @@ namespace Bit.iOS.Core.Views
|
||||
{
|
||||
public class ExtensionTableSource : ExtendedUITableViewSource
|
||||
{
|
||||
private const string CellIdentifier = "TableCell";
|
||||
public const string CellIdentifier = nameof(CipherLoginTableViewCell);
|
||||
|
||||
private IEnumerable<CipherViewModel> _allItems = new List<CipherViewModel>();
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (Items == null || Items.Count() == 0)
|
||||
@@ -110,14 +112,9 @@ namespace Bit.iOS.Core.Views
|
||||
}
|
||||
|
||||
var cell = tableView.DequeueReusableCell(CellIdentifier);
|
||||
|
||||
// if there are no cells to reuse, create a new one
|
||||
if (cell == null)
|
||||
if (cell is null)
|
||||
{
|
||||
Debug.WriteLine("BW Log, Make new cell for list.");
|
||||
cell = new ExtendedUITableViewCell(UITableViewCellStyle.Subtitle, CellIdentifier);
|
||||
cell.TextLabel.TextColor = cell.TextLabel.TintColor = ThemeHelpers.TextColor;
|
||||
cell.DetailTextLabel.TextColor = cell.DetailTextLabel.TintColor = ThemeHelpers.MutedColor;
|
||||
throw new InvalidOperationException($"The cell {CellIdentifier} has not been registered in the UITableView");
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
@@ -126,15 +123,35 @@ namespace Bit.iOS.Core.Views
|
||||
{
|
||||
if (Items == null
|
||||
|| !Items.Any()
|
||||
|| cell?.TextLabel == null
|
||||
|| cell.DetailTextLabel == null)
|
||||
|| !(cell is CipherLoginTableViewCell cipherCell))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = Items.ElementAt(indexPath.Row);
|
||||
cell.TextLabel.Text = item.Name;
|
||||
cell.DetailTextLabel.Text = item.Username;
|
||||
if (item is null)
|
||||
{
|
||||
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)
|
||||
|
||||
@@ -41,9 +41,11 @@ namespace Bit.iOS.Extension
|
||||
{
|
||||
CancelBarButton.Title = AppResources.Cancel;
|
||||
}
|
||||
TableView.RowHeight = UITableView.AutomaticDimension;
|
||||
TableView.EstimatedRowHeight = 44;
|
||||
TableView.Source = new TableSource(this);
|
||||
|
||||
var tableSource = new TableSource(this);
|
||||
TableView.Source = tableSource;
|
||||
tableSource.RegisterTableViewCells(TableView);
|
||||
|
||||
await ((TableSource)TableView.Source).LoadAsync();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user