1
0
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:
Federico Maccaroni
2024-03-08 13:59:15 -03:00
committed by GitHub
parent 39187732c0
commit 67f7b3156e
8 changed files with 208 additions and 40 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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