1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

PM-5154 [Passkeys iOS] Updated UI for passkey creation

This commit is contained in:
Federico Maccaroni
2024-02-20 18:08:11 -03:00
parent a4adcdcfd4
commit c11df272be
4 changed files with 171 additions and 96 deletions

View File

@@ -0,0 +1,59 @@
using Bit.Core.Services;
using Foundation;
using ObjCRuntime;
using UIKit;
namespace Bit.iOS.Autofill.ListItems
{
public class HeaderItemView : UITableViewHeaderFooterView
{
private readonly UILabel _header = new UILabel();
private readonly UIView _separator = new UIView();
public HeaderItemView(NSString reuseIdentifier)
: base(reuseIdentifier)
{
Setup();
}
protected internal HeaderItemView(NativeHandle handle) : base(handle)
{
Setup();
}
public void SetHeaderText(string text) => _header.Text = text;
private void Setup()
{
try
{
_header.TextColor = UIColor.FromName(ColorConstants.LIGHT_TEXT_MUTED);
_header.Font = UIFont.SystemFontOfSize(15);
_separator.BackgroundColor = UIColor.FromName(ColorConstants.LIGHT_SECONDARY_300);
_header.TranslatesAutoresizingMaskIntoConstraints = false;
_separator.TranslatesAutoresizingMaskIntoConstraints = false;
ContentView.AddSubview(_header);
ContentView.AddSubview(_separator);
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),
_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.BottomAnchor.ConstraintEqualTo(ContentView.LayoutMarginsGuide.BottomAnchor, 2)
});
}
catch (System.Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
}
}
}

View File

@@ -7,6 +7,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Resources.Localization;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.iOS.Autofill.ListItems;
using Bit.iOS.Autofill.Models;
using Bit.iOS.Autofill.Utilities;
using Bit.iOS.Core.Controllers;
@@ -20,7 +21,7 @@ namespace Bit.iOS.Autofill
{
public partial class LoginListViewController : ExtendedUIViewController
{
//internal const string HEADER_SECTION_IDENTIFIER = "headerSectionId";
internal const string HEADER_SECTION_IDENTIFIER = "headerSectionId";
UIBarButtonItem _cancelButton;
UIControl _accountSwitchButton;
@@ -48,59 +49,71 @@ namespace Bit.iOS.Autofill
public async override void ViewDidLoad()
{
_cancelButton = new UIBarButtonItem(UIBarButtonSystemItem.Cancel, CancelButton_TouchUpInside);
base.ViewDidLoad();
SubscribeSyncCompleted();
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);
//TableView.RegisterClassForHeaderFooterViewReuse(typeof(AccountViewCell), HEADER_SECTION_IDENTIFIER);
await ((TableSource)TableView.Source).LoadAsync();
if (Context.IsCreatingPasskey)
try
{
_headerLabel.Text = AppResources.ChooseALoginToSaveThisPasskeyTo;
_emptyViewLabel.Text = string.Format(AppResources.NoItemsForUri, Context.UrlString);
_cancelButton = new UIBarButtonItem(UIBarButtonSystemItem.Cancel, CancelButton_TouchUpInside);
_emptyViewButton.SetTitle(AppResources.SavePasskeyAsNewLogin, UIControlState.Normal);
_emptyViewButton.Layer.BorderWidth = 2;
_emptyViewButton.Layer.BorderColor = UIColor.FromName(ColorConstants.LIGHT_TEXT_MUTED).CGColor;
_emptyViewButton.Layer.CornerRadius = 10;
_emptyViewButton.ClipsToBounds = true;
base.ViewDidLoad();
_headerView.Hidden = false;
SubscribeSyncCompleted();
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);
TableView.SectionHeaderHeight = 55;
TableView.RegisterClassForHeaderFooterViewReuse(typeof(HeaderItemView), HEADER_SECTION_IDENTIFIER);
if (UIDevice.CurrentDevice.CheckSystemVersion(15, 0))
{
TableView.SectionHeaderTopPadding = 0;
}
await ((TableSource)TableView.Source).LoadAsync();
if (Context.IsCreatingPasskey)
{
_headerLabel.Text = AppResources.ChooseALoginToSaveThisPasskeyTo;
_emptyViewLabel.Text = string.Format(AppResources.NoItemsForUri, Context.UrlString);
_emptyViewButton.SetTitle(AppResources.SavePasskeyAsNewLogin, UIControlState.Normal);
_emptyViewButton.Layer.BorderWidth = 2;
_emptyViewButton.Layer.BorderColor = UIColor.FromName(ColorConstants.LIGHT_TEXT_MUTED).CGColor;
_emptyViewButton.Layer.CornerRadius = 10;
_emptyViewButton.ClipsToBounds = true;
_headerView.Hidden = false;
}
_alreadyLoadItemsOnce = true;
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
var needsAutofillReplacement = await storageService.GetAsync<bool?>(
Core.Constants.AutofillNeedsIdentityReplacementKey);
if (needsAutofillReplacement.GetValueOrDefault())
{
await ASHelpers.ReplaceAllIdentitiesAsync();
}
_accountSwitchingOverlayHelper = new AccountSwitchingOverlayHelper();
_accountSwitchButton = await _accountSwitchingOverlayHelper.CreateAccountSwitchToolbarButtonItemCustomViewAsync();
_accountSwitchButton.TouchUpInside += AccountSwitchedButton_TouchUpInside;
NavItem.SetLeftBarButtonItems(new UIBarButtonItem[]
{
_cancelButton,
new UIBarButtonItem(_accountSwitchButton)
}, false);
_accountSwitchingOverlayView = _accountSwitchingOverlayHelper.CreateAccountSwitchingOverlayView(OverlayView);
}
_alreadyLoadItemsOnce = true;
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
var needsAutofillReplacement = await storageService.GetAsync<bool?>(
Core.Constants.AutofillNeedsIdentityReplacementKey);
if (needsAutofillReplacement.GetValueOrDefault())
catch (Exception ex)
{
await ASHelpers.ReplaceAllIdentitiesAsync();
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
_accountSwitchingOverlayHelper = new AccountSwitchingOverlayHelper();
_accountSwitchButton = await _accountSwitchingOverlayHelper.CreateAccountSwitchToolbarButtonItemCustomViewAsync();
_accountSwitchButton.TouchUpInside += AccountSwitchedButton_TouchUpInside;
NavItem.SetLeftBarButtonItems(new UIBarButtonItem[]
{
_cancelButton,
new UIBarButtonItem(_accountSwitchButton)
}, false);
_accountSwitchingOverlayView = _accountSwitchingOverlayHelper.CreateAccountSwitchingOverlayView(OverlayView);
}
private void CancelButton_TouchUpInside(object sender, EventArgs e)
@@ -130,7 +143,6 @@ namespace Bit.iOS.Autofill
partial void EmptyButton_Activated(UIButton sender)
{
ClipLogger.Log($"EmptyButton_Activated");
SavePasskeyAsNewLoginAsync().FireAndForget(ex =>
{
_platformUtilsService.Value.ShowDialogAsync(AppResources.GenericErrorMessage, AppResources.AnErrorHasOccurred).FireAndForget();
@@ -145,11 +157,7 @@ namespace Bit.iOS.Autofill
return;
}
ClipLogger.Log($"SavePasskeyAsNewLoginAsync ");
var cipherId = await _cipherService.Value.CreateNewLoginForPasskeyAsync(Context.PasskeyCredentialIdentity.RelyingPartyIdentifier);
ClipLogger.Log($"SavePasskeyAsNewLoginAsync -> setting result {cipherId}");
Context.ConfirmNewCredentialTcs.TrySetResult(new Fido2ConfirmNewCredentialResult
{
CipherId = cipherId,
@@ -203,7 +211,6 @@ namespace Bit.iOS.Autofill
public void OnEmptyList()
{
ClipLogger.Log($"OnEmptyList");
_emptyView.Hidden = false;
_headerView.Hidden = false;
TableView.Hidden = true;
@@ -262,42 +269,43 @@ namespace Bit.iOS.Autofill
private Context Context => (Context)_context;
//protected override async Task<IEnumerable<CipherViewModel>> LoadItemsAsync(bool urlFilter = true, string searchFilter = null)
//{
// if (!Context.IsCreatingPasskey)
// {
// return await base.LoadItemsAsync(urlFilter, searchFilter);
// }
//}
public override async Task LoadAsync(bool urlFilter = true, string searchFilter = null)
{
await base.LoadAsync(urlFilter, searchFilter);
if (Context.IsCreatingPasskey && !Items.Any())
try
{
_controller?.OnEmptyList();
await base.LoadAsync(urlFilter, searchFilter);
if (Context.IsCreatingPasskey && !Items.Any())
{
_controller?.OnEmptyList();
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
}
//public override nint NumberOfSections(UITableView tableView)
//{
// return Context.IsCreatingPasskey ? 1 : 0;
//}
public override UIView GetViewForHeader(UITableView tableView, nint section)
{
try
{
if (Context.IsCreatingPasskey
&&
tableView.DequeueReusableHeaderFooterView(LoginListViewController.HEADER_SECTION_IDENTIFIER) is HeaderItemView headerItemView)
{
headerItemView.SetHeaderText(AppResources.ChooseALoginToSaveThisPasskeyTo);
return headerItemView;
}
//public override UIView GetViewForHeader(UITableView tableView, nint section)
//{
// if (Context.IsCreatingPasskey)
// {
// var view = tableView.DequeueReusableHeaderFooterView(LoginListViewController.HEADER_SECTION_IDENTIFIER);
// return view;
// }
// return base.GetViewForHeader(tableView, section);
//}
return base.GetViewForHeader(tableView, section);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
return new UIView();
}
}
public override nint RowsInSection(UITableView tableview, nint section)
{
@@ -311,27 +319,31 @@ namespace Bit.iOS.Autofill
public async override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
if (Context.IsCreatingPasskey)
try
{
await SelectRowForPasskeyCreationAsync(tableView, indexPath);
return;
}
if (Context.IsCreatingPasskey)
{
await SelectRowForPasskeyCreationAsync(tableView, indexPath);
return;
}
await AutofillHelpers.TableRowSelectedAsync(tableView, indexPath, this,
_controller.CPViewController, _controller, _controller.PasswordRepromptService, "loginAddSegue");
await AutofillHelpers.TableRowSelectedAsync(tableView, indexPath, this,
_controller.CPViewController, _controller, _controller.PasswordRepromptService, "loginAddSegue");
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
}
private async Task SelectRowForPasskeyCreationAsync(UITableView tableView, NSIndexPath indexPath)
{
ClipLogger.Log($"SelectRowForPasskeyCreationAsync");
tableView.DeselectRow(indexPath, true);
tableView.EndEditing(true);
var item = Items.ElementAt(indexPath.Row);
if (item is null)
{
ClipLogger.Log($"SelectRowForPasskeyCreationAsync -> item is null");
await _platformUtilsService.Value.ShowDialogAsync(AppResources.GenericErrorMessage, AppResources.AnErrorHasOccurred);
return;
}
@@ -344,19 +356,16 @@ namespace Bit.iOS.Autofill
AppResources.Yes,
AppResources.No))
{
ClipLogger.Log($"SelectRowForPasskeyCreationAsync -> don't want to overwrite");
return;
}
if (!await _passwordRepromptService.Value.PromptAndCheckPasswordIfNeededAsync(item.Reprompt))
{
ClipLogger.Log($"SelectRowForPasskeyCreationAsync -> PromptAndCheckPasswordIfNeededAsync -> false");
return;
}
// TODO: Check user verification
ClipLogger.Log($"SelectRowForPasskeyCreationAsync -> Setting result {item.Id}");
Context.ConfirmNewCredentialTcs.SetResult(new Fido2ConfirmNewCredentialResult
{
CipherId = item.Id,

View File

@@ -200,7 +200,7 @@
<constraint firstItem="FDN-Dp-jl3" firstAttribute="top" secondItem="wNm-Sy-bJv" secondAttribute="top" id="kKX-UE-JzG"/>
</constraints>
</view>
<tableView opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="2305">
<tableView opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="2305">
<rect key="frame" x="0.0" y="0.0" width="414" height="781"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<prototypes>

View File

@@ -44,6 +44,9 @@
<ItemGroup>
<TrimmerRootAssembly Include="System.Security.Cryptography" />
</ItemGroup>
<ItemGroup>
<None Remove="ListItems\" />
</ItemGroup>
<ItemGroup>
<Compile Include="CredentialProviderViewController.cs" />
<Compile Include="CredentialProviderViewController.designer.cs">
@@ -84,6 +87,7 @@
<Compile Include="CredentialProviderViewController.Passkeys.cs" />
<Compile Include="SegueConstants.cs" />
<Compile Include="ColorConstants.cs" />
<Compile Include="ListItems\HeaderItemView.cs" />
</ItemGroup>
<ItemGroup>
<BundleResource Include="Resources\check.png" />
@@ -182,4 +186,7 @@
<ProjectReference Include="..\Core\Core.csproj" />
<ProjectReference Include="..\iOS.Core\iOS.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="ListItems\" />
</ItemGroup>
</Project>