[SG 547] Mobile username generator iOS.Extension UI changes (#2140)

* [SG-547] - Added button to generate username when using iOS extension

* [SG-547] - Missing changes from last commit

* SG-547 - Added missing interface method

* SG-547 - Added token renovation for iOS.Extension flow

* SG-547 Replaced generate buttons for icons

* SG-547 Removed unnecessary validation

* SG-547 - Fixed PR comments

* SG 547 - Missing file from last commit

* SG-547 - Fixed PR comments

* SG-547 - Renamed method
This commit is contained in:
Carlos Gonçalves 2022-10-25 21:05:15 +01:00 committed by GitHub
parent 3cb9f37997
commit 505426cd6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 179 additions and 75 deletions

View File

@ -520,5 +520,11 @@ namespace Bit.Droid.Services
intent.SetData(uri); intent.SetData(uri);
Application.Context.StartActivity(intent); Application.Context.StartActivity(intent);
} }
public void CloseExtensionPopUp()
{
// only used by iOS
throw new NotImplementedException();
}
} }
} }

View File

@ -37,5 +37,6 @@ namespace Bit.App.Abstractions
Task OnAccountSwitchCompleteAsync(); Task OnAccountSwitchCompleteAsync();
Task SetScreenCaptureAllowedAsync(); Task SetScreenCaptureAllowedAsync();
void OpenAppSettings(); void OpenAppSettings();
void CloseExtensionPopUp();
} }
} }

View File

@ -21,7 +21,7 @@
<u:InverseBoolConverter x:Key="inverseBool" /> <u:InverseBoolConverter x:Key="inverseBool" />
<u:LocalizableEnumConverter x:Key="localizableEnum" /> <u:LocalizableEnumConverter x:Key="localizableEnum" />
<xct:EnumToBoolConverter x:Key="enumToBool"/> <xct:EnumToBoolConverter x:Key="enumToBool"/>
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" <ToolbarItem Text="{u:I18n Cancel}" Command="{Binding CloseCommand}" Order="Primary" Priority="-1"
x:Name="_closeItem" x:Key="closeItem" /> x:Name="_closeItem" x:Key="closeItem" />
<ToolbarItem Text="{u:I18n Select}" <ToolbarItem Text="{u:I18n Select}"
Clicked="Select_Clicked" Clicked="Select_Clicked"

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Styles; using Bit.App.Styles;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
@ -18,11 +19,11 @@ namespace Bit.App.Pages
private readonly Action<string> _selectAction; private readonly Action<string> _selectAction;
private readonly TabsPage _tabsPage; private readonly TabsPage _tabsPage;
public GeneratorPage(bool fromTabPage, Action<string> selectAction = null, TabsPage tabsPage = null, bool isUsernameGenerator = false, string emailWebsite = null, bool editMode = false) public GeneratorPage(bool fromTabPage, Action<string> selectAction = null, TabsPage tabsPage = null, bool isUsernameGenerator = false, string emailWebsite = null, bool editMode = false, AppOptions appOptions = null)
{ {
_tabsPage = tabsPage; _tabsPage = tabsPage;
InitializeComponent(); InitializeComponent();
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"); _broadcasterService = ServiceContainer.Resolve<IBroadcasterService>();
_vm = BindingContext as GeneratorPageViewModel; _vm = BindingContext as GeneratorPageViewModel;
_vm.Page = this; _vm.Page = this;
_fromTabPage = fromTabPage; _fromTabPage = fromTabPage;
@ -31,6 +32,7 @@ namespace Bit.App.Pages
_vm.IsUsername = isUsernameGenerator; _vm.IsUsername = isUsernameGenerator;
_vm.EmailWebsite = emailWebsite; _vm.EmailWebsite = emailWebsite;
_vm.EditMode = editMode; _vm.EditMode = editMode;
_vm.IosExtension = appOptions?.IosExtension ?? false;
var isIos = Device.RuntimePlatform == Device.iOS; var isIos = Device.RuntimePlatform == Device.iOS;
if (selectAction != null) if (selectAction != null)
{ {
@ -134,14 +136,6 @@ namespace Bit.App.Pages
await _vm.SliderChangedAsync(); await _vm.SliderChangedAsync();
} }
private async void Close_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
await Navigation.PopModalAsync();
}
}
public override async Task UpdateOnThemeChanged() public override async Task UpdateOnThemeChanged()
{ {
await base.UpdateOnThemeChanged(); await base.UpdateOnThemeChanged();

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Input; using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core; using Bit.Core;
@ -21,6 +22,7 @@ namespace Bit.App.Pages
private readonly IClipboardService _clipboardService; private readonly IClipboardService _clipboardService;
private readonly IUsernameGenerationService _usernameGenerationService; private readonly IUsernameGenerationService _usernameGenerationService;
private readonly ITokenService _tokenService; private readonly ITokenService _tokenService;
private readonly IDeviceActionService _deviceActionService;
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger"); readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
private PasswordGenerationOptions _options; private PasswordGenerationOptions _options;
@ -59,6 +61,7 @@ namespace Bit.App.Pages
_clipboardService = ServiceContainer.Resolve<IClipboardService>(); _clipboardService = ServiceContainer.Resolve<IClipboardService>();
_usernameGenerationService = ServiceContainer.Resolve<IUsernameGenerationService>(); _usernameGenerationService = ServiceContainer.Resolve<IUsernameGenerationService>();
_tokenService = ServiceContainer.Resolve<ITokenService>(); _tokenService = ServiceContainer.Resolve<ITokenService>();
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>();
PageTitle = AppResources.Generator; PageTitle = AppResources.Generator;
GeneratorTypeOptions = new List<GeneratorType> { GeneratorTypeOptions = new List<GeneratorType> {
@ -89,8 +92,9 @@ namespace Bit.App.Pages
UsernameTypePromptHelpCommand = new Command(UsernameTypePromptHelp); UsernameTypePromptHelpCommand = new Command(UsernameTypePromptHelp);
RegenerateCommand = new AsyncCommand(RegenerateAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false); RegenerateCommand = new AsyncCommand(RegenerateAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
RegenerateUsernameCommand = new AsyncCommand(RegenerateUsernameAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false); RegenerateUsernameCommand = new AsyncCommand(RegenerateUsernameAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
ToggleForwardedEmailHiddenValueCommand = new AsyncCommand(ToggleForwardedEmailHiddenValueAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false); ToggleForwardedEmailHiddenValueCommand = new AsyncCommand(ToggleForwardedEmailHiddenValueAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
CopyCommand = new AsyncCommand(CopyAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false); CopyCommand = new AsyncCommand(CopyAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
CloseCommand = new AsyncCommand(CloseAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
} }
public List<GeneratorType> GeneratorTypeOptions { get; set; } public List<GeneratorType> GeneratorTypeOptions { get; set; }
@ -104,6 +108,7 @@ namespace Bit.App.Pages
public ICommand RegenerateUsernameCommand { get; set; } public ICommand RegenerateUsernameCommand { get; set; }
public ICommand ToggleForwardedEmailHiddenValueCommand { get; set; } public ICommand ToggleForwardedEmailHiddenValueCommand { get; set; }
public ICommand CopyCommand { get; set; } public ICommand CopyCommand { get; set; }
public ICommand CloseCommand { get; set; }
public string Password public string Password
{ {
@ -140,6 +145,8 @@ namespace Bit.App.Pages
set => SetProperty(ref _isUsername, value); set => SetProperty(ref _isUsername, value);
} }
public bool IosExtension { get; set; }
public bool ShowTypePicker public bool ShowTypePicker
{ {
get => _showTypePicker; get => _showTypePicker;
@ -606,6 +613,7 @@ namespace Bit.App.Pages
LoadFromOptions(); LoadFromOptions();
_usernameOptions = await _usernameGenerationService.GetOptionsAsync(); _usernameOptions = await _usernameGenerationService.GetOptionsAsync();
await _tokenService.PrepareTokenForDecodingAsync();
_usernameOptions.PlusAddressedEmail = _tokenService.GetEmail(); _usernameOptions.PlusAddressedEmail = _tokenService.GetEmail();
_usernameOptions.EmailWebsite = EmailWebsite; _usernameOptions.EmailWebsite = EmailWebsite;
_usernameOptions.CatchAllEmailType = _usernameOptions.PlusAddressedEmailType = string.IsNullOrWhiteSpace(EmailWebsite) || !EditMode ? UsernameEmailType.Random : UsernameEmailType.Website; _usernameOptions.CatchAllEmailType = _usernameOptions.PlusAddressedEmailType = string.IsNullOrWhiteSpace(EmailWebsite) || !EditMode ? UsernameEmailType.Random : UsernameEmailType.Website;
@ -681,6 +689,7 @@ namespace Bit.App.Pages
return; return;
} }
_usernameOptions.EmailWebsite = EmailWebsite;
await _usernameGenerationService.SaveOptionsAsync(_usernameOptions); await _usernameGenerationService.SaveOptionsAsync(_usernameOptions);
if (regenerate && UsernameTypeSelected != UsernameType.ForwardedEmailAlias) if (regenerate && UsernameTypeSelected != UsernameType.ForwardedEmailAlias)
@ -729,6 +738,18 @@ namespace Bit.App.Pages
} }
} }
public async Task CloseAsync()
{
if (IosExtension)
{
_deviceActionService.CloseExtensionPopUp();
}
else
{
await Page.Navigation.PopModalAsync();
}
}
private void LoadFromOptions() private void LoadFromOptions()
{ {
AllowAmbiguousChars = _options.AllowAmbiguousChar.GetValueOrDefault(); AllowAmbiguousChars = _options.AllowAmbiguousChar.GetValueOrDefault();
@ -765,6 +786,7 @@ namespace Bit.App.Pages
TriggerPropertyChanged(nameof(PlusAddressedEmail)); TriggerPropertyChanged(nameof(PlusAddressedEmail));
TriggerPropertyChanged(nameof(GeneratorTypeSelected)); TriggerPropertyChanged(nameof(GeneratorTypeSelected));
TriggerPropertyChanged(nameof(UsernameTypeDescriptionLabel)); TriggerPropertyChanged(nameof(UsernameTypeDescriptionLabel));
TriggerPropertyChanged(nameof(EmailWebsite));
} }
private void SetOptions() private void SetOptions()

View File

@ -28,5 +28,6 @@ namespace Bit.Core.Abstractions
Task SetTwoFactorTokenAsync(string token, string email); Task SetTwoFactorTokenAsync(string token, string email);
bool TokenNeedsRefresh(int minutes = 5); bool TokenNeedsRefresh(int minutes = 5);
int TokenSecondsRemaining(); int TokenSecondsRemaining();
Task PrepareTokenForDecodingAsync();
} }
} }

View File

@ -44,6 +44,11 @@ namespace Bit.Core.Services
return _accessTokenForDecoding; return _accessTokenForDecoding;
} }
public async Task PrepareTokenForDecodingAsync()
{
_accessTokenForDecoding = await _stateService.GetAccessTokenAsync();
}
public async Task SetRefreshTokenAsync(string refreshToken) public async Task SetRefreshTokenAsync(string refreshToken)
{ {
await _stateService.SetRefreshTokenAsync(refreshToken, await SkipTokenStorage()); await _stateService.SetRefreshTokenAsync(refreshToken, await SkipTokenStorage());

View File

@ -5,7 +5,6 @@ using Bit.App.Models;
using Bit.App.Pages; using Bit.App.Pages;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
@ -55,7 +54,7 @@ namespace Bit.iOS.Core.Controllers
public abstract Action Cancel { get; } public abstract Action Cancel { get; }
public FormEntryTableViewCell MasterPasswordCell { get; set; } = new FormEntryTableViewCell( public FormEntryTableViewCell MasterPasswordCell { get; set; } = new FormEntryTableViewCell(
AppResources.MasterPassword, useButton: true); AppResources.MasterPassword, buttonsConfig: FormEntryTableViewCell.ButtonsConfig.One);
public string BiometricIntegrityKey { get; set; } public string BiometricIntegrityKey { get; set; }
@ -161,13 +160,7 @@ namespace Bit.iOS.Core.Controllers
{ {
MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad; MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad;
} }
MasterPasswordCell.Button.TitleLabel.Font = UIFont.FromName("bwi-font", 28f); MasterPasswordCell.ConfigureToggleSecureTextCell();
MasterPasswordCell.Button.SetTitle(BitwardenIcons.Eye, UIControlState.Normal);
MasterPasswordCell.Button.TouchUpInside += (sender, e) =>
{
MasterPasswordCell.TextField.SecureTextEntry = !MasterPasswordCell.TextField.SecureTextEntry;
MasterPasswordCell.Button.SetTitle(MasterPasswordCell.TextField.SecureTextEntry ? BitwardenIcons.Eye : BitwardenIcons.EyeSlash, UIControlState.Normal);
};
} }
if (TableView != null) if (TableView != null)

View File

@ -14,7 +14,6 @@ using Bit.Core.Enums;
using Bit.App.Pages; using Bit.App.Pages;
using Bit.App.Models; using Bit.App.Models;
using Xamarin.Forms; using Xamarin.Forms;
using Bit.Core;
namespace Bit.iOS.Core.Controllers namespace Bit.iOS.Core.Controllers
{ {
@ -52,7 +51,7 @@ namespace Bit.iOS.Core.Controllers
public abstract Action Cancel { get; } public abstract Action Cancel { get; }
public FormEntryTableViewCell MasterPasswordCell { get; set; } = new FormEntryTableViewCell( public FormEntryTableViewCell MasterPasswordCell { get; set; } = new FormEntryTableViewCell(
AppResources.MasterPassword, useButton: true); AppResources.MasterPassword, buttonsConfig: FormEntryTableViewCell.ButtonsConfig.One);
public string BiometricIntegrityKey { get; set; } public string BiometricIntegrityKey { get; set; }
@ -155,12 +154,7 @@ namespace Bit.iOS.Core.Controllers
{ {
MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad; MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad;
} }
MasterPasswordCell.Button.TitleLabel.Font = UIFont.FromName("bwi-font", 28f); MasterPasswordCell.ConfigureToggleSecureTextCell();
MasterPasswordCell.Button.SetTitle(BitwardenIcons.Eye, UIControlState.Normal);
MasterPasswordCell.Button.TouchUpInside += (sender, e) => {
MasterPasswordCell.TextField.SecureTextEntry = !MasterPasswordCell.TextField.SecureTextEntry;
MasterPasswordCell.Button.SetTitle(MasterPasswordCell.TextField.SecureTextEntry ? BitwardenIcons.Eye : BitwardenIcons.EyeSlash, UIControlState.Normal);
};
} }
TableView.RowHeight = UITableView.AutomaticDimension; TableView.RowHeight = UITableView.AutomaticDimension;

View File

@ -1,18 +1,23 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using AuthenticationServices;
using Bit.App.Models;
using Bit.App.Pages;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Bit.iOS.Core.Models;
using Bit.iOS.Core.Utilities;
using Bit.iOS.Core.Views; using Bit.iOS.Core.Views;
using Foundation; using Foundation;
using UIKit; using UIKit;
using Bit.iOS.Core.Utilities; using Xamarin.Forms;
using Bit.iOS.Core.Models;
using System.Threading.Tasks;
using AuthenticationServices;
using Bit.Core.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Bit.Core.Exceptions;
namespace Bit.iOS.Core.Controllers namespace Bit.iOS.Core.Controllers
{ {
@ -29,10 +34,8 @@ namespace Bit.iOS.Core.Controllers
public AppExtensionContext Context { get; set; } public AppExtensionContext Context { get; set; }
public FormEntryTableViewCell NameCell { get; set; } = new FormEntryTableViewCell(AppResources.Name); public FormEntryTableViewCell NameCell { get; set; } = new FormEntryTableViewCell(AppResources.Name);
public FormEntryTableViewCell UsernameCell { get; set; } = new FormEntryTableViewCell(AppResources.Username); public FormEntryTableViewCell UsernameCell { get; set; } = new FormEntryTableViewCell(AppResources.Username, buttonsConfig: FormEntryTableViewCell.ButtonsConfig.One);
public FormEntryTableViewCell PasswordCell { get; set; } = new FormEntryTableViewCell(AppResources.Password); public FormEntryTableViewCell PasswordCell { get; set; } = new FormEntryTableViewCell(AppResources.Password, buttonsConfig: FormEntryTableViewCell.ButtonsConfig.Two);
public UITableViewCell GeneratePasswordCell { get; set; } = new ExtendedUITableViewCell(
UITableViewCellStyle.Subtitle, "GeneratePasswordCell");
public FormEntryTableViewCell UriCell { get; set; } = new FormEntryTableViewCell(AppResources.URI); public FormEntryTableViewCell UriCell { get; set; } = new FormEntryTableViewCell(AppResources.URI);
public SwitchTableViewCell FavoriteCell { get; set; } = new SwitchTableViewCell(AppResources.Favorite); public SwitchTableViewCell FavoriteCell { get; set; } = new SwitchTableViewCell(AppResources.Favorite);
public FormEntryTableViewCell NotesCell { get; set; } = new FormEntryTableViewCell( public FormEntryTableViewCell NotesCell { get; set; } = new FormEntryTableViewCell(
@ -67,6 +70,12 @@ namespace Bit.iOS.Core.Controllers
UsernameCell.TextField.AutocorrectionType = UITextAutocorrectionType.No; UsernameCell.TextField.AutocorrectionType = UITextAutocorrectionType.No;
UsernameCell.TextField.SpellCheckingType = UITextSpellCheckingType.No; UsernameCell.TextField.SpellCheckingType = UITextSpellCheckingType.No;
UsernameCell.TextField.ReturnKeyType = UIReturnKeyType.Next; UsernameCell.TextField.ReturnKeyType = UIReturnKeyType.Next;
UsernameCell.Button.TitleLabel.Font = UIFont.FromName("bwi-font", 28f);
UsernameCell.Button.SetTitle(BitwardenIcons.Generate, UIControlState.Normal);
UsernameCell.Button.TouchUpInside += (sender, e) =>
{
LaunchUsernameGeneratorFlow();
};
UsernameCell.TextField.ShouldReturn += (UITextField tf) => UsernameCell.TextField.ShouldReturn += (UITextField tf) =>
{ {
PasswordCell.TextField.BecomeFirstResponder(); PasswordCell.TextField.BecomeFirstResponder();
@ -75,17 +84,20 @@ namespace Bit.iOS.Core.Controllers
PasswordCell.TextField.SecureTextEntry = true; PasswordCell.TextField.SecureTextEntry = true;
PasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Next; PasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Next;
PasswordCell.Button.TitleLabel.Font = UIFont.FromName("bwi-font", 28f);
PasswordCell.Button.SetTitle(BitwardenIcons.Generate, UIControlState.Normal);
PasswordCell.Button.TouchUpInside += (sender, e) =>
{
PerformSegue("passwordGeneratorSegue", this);
};
PasswordCell.ConfigureToggleSecureTextCell(true);
PasswordCell.TextField.ShouldReturn += (UITextField tf) => PasswordCell.TextField.ShouldReturn += (UITextField tf) =>
{ {
UriCell.TextField.BecomeFirstResponder(); UriCell.TextField.BecomeFirstResponder();
return true; return true;
}; };
GeneratePasswordCell.TextLabel.Text = AppResources.GeneratePassword;
GeneratePasswordCell.TextLabel.TextColor = GeneratePasswordCell.TextLabel.TintColor =
ThemeHelpers.TextColor;
GeneratePasswordCell.Accessory = UITableViewCellAccessory.DisclosureIndicator;
UriCell.TextField.Text = Context?.UrlString ?? string.Empty; UriCell.TextField.Text = Context?.UrlString ?? string.Empty;
UriCell.TextField.KeyboardType = UIKeyboardType.Url; UriCell.TextField.KeyboardType = UIKeyboardType.Url;
UriCell.TextField.ReturnKeyType = UIReturnKeyType.Next; UriCell.TextField.ReturnKeyType = UIReturnKeyType.Next;
@ -210,6 +222,26 @@ namespace Bit.iOS.Core.Controllers
AppResources.InternetConnectionRequiredMessage, AppResources.Ok); AppResources.InternetConnectionRequiredMessage, AppResources.Ok);
} }
private void LaunchUsernameGeneratorFlow()
{
var appOptions = new AppOptions { IosExtension = true };
var app = new App.App(appOptions);
var generatorPage = new GeneratorPage(false, selectAction: async (username) =>
{
UsernameCell.TextField.Text = username;
DismissViewController(false, null);
}, isUsernameGenerator: true, emailWebsite: NameCell.TextField.Text, appOptions: appOptions);
ThemeManager.SetTheme(app.Resources);
ThemeManager.ApplyResourcesTo(generatorPage);
var navigationPage = new NavigationPage(generatorPage);
var generatorController = navigationPage.CreateViewController();
generatorController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
PresentViewController(generatorController, true, null);
}
public class TableSource : ExtendedUITableViewSource public class TableSource : ExtendedUITableViewSource
{ {
private LoginAddViewController _controller; private LoginAddViewController _controller;
@ -235,10 +267,6 @@ namespace Bit.iOS.Core.Controllers
{ {
return _controller.PasswordCell; return _controller.PasswordCell;
} }
else if (indexPath.Row == 3)
{
return _controller.GeneratePasswordCell;
}
} }
else if (indexPath.Section == 1) else if (indexPath.Section == 1)
{ {
@ -277,7 +305,7 @@ namespace Bit.iOS.Core.Controllers
{ {
if (section == 0) if (section == 0)
{ {
return 4; return 3;
} }
else if (section == 1) else if (section == 1)
{ {
@ -317,11 +345,6 @@ namespace Bit.iOS.Core.Controllers
tableView.DeselectRow(indexPath, true); tableView.DeselectRow(indexPath, true);
tableView.EndEditing(true); tableView.EndEditing(true);
if (indexPath.Section == 0 && indexPath.Row == 3)
{
_controller.PerformSegue("passwordGeneratorSegue", this);
}
var cell = tableView.CellAt(indexPath); var cell = tableView.CellAt(indexPath);
if (cell == null) if (cell == null)
{ {

View File

@ -379,5 +379,10 @@ namespace Bit.iOS.Core.Services
var url = new NSUrl(UIApplication.OpenSettingsUrlString); var url = new NSUrl(UIApplication.OpenSettingsUrlString);
UIApplication.SharedApplication.OpenUrl(url); UIApplication.SharedApplication.OpenUrl(url);
} }
public void CloseExtensionPopUp()
{
GetPresentedViewController().DismissViewController(true, null);
}
} }
} }

View File

@ -1,7 +1,7 @@
using Bit.iOS.Core.Controllers; using System;
using Bit.Core;
using Bit.iOS.Core.Controllers;
using Bit.iOS.Core.Utilities; using Bit.iOS.Core.Utilities;
using System;
using System.Drawing;
using UIKit; using UIKit;
namespace Bit.iOS.Core.Views namespace Bit.iOS.Core.Views
@ -12,14 +12,14 @@ namespace Bit.iOS.Core.Views
public UITextField TextField { get; set; } public UITextField TextField { get; set; }
public UITextView TextView { get; set; } public UITextView TextView { get; set; }
public UIButton Button { get; set; } public UIButton Button { get; set; }
public UIButton SecondButton { get; set; }
public event EventHandler ValueChanged; public event EventHandler ValueChanged;
public FormEntryTableViewCell( public FormEntryTableViewCell(
string labelName = null, string labelName = null,
bool useTextView = false, bool useTextView = false,
nfloat? height = null, nfloat? height = null,
bool useButton = false, ButtonsConfig buttonsConfig = ButtonsConfig.None,
bool useLabelAsPlaceholder = false, bool useLabelAsPlaceholder = false,
float leadingConstant = 15f) float leadingConstant = 15f)
: base(UITableViewCellStyle.Default, nameof(FormEntryTableViewCell)) : base(UITableViewCellStyle.Default, nameof(FormEntryTableViewCell))
@ -85,7 +85,6 @@ namespace Bit.iOS.Core.Views
ValueChanged?.Invoke(sender, e); ValueChanged?.Invoke(sender, e);
}; };
} }
else else
{ {
TextField = new UITextField TextField = new UITextField
@ -110,9 +109,10 @@ namespace Bit.iOS.Core.Views
} }
ContentView.Add(TextField); ContentView.Add(TextField);
ContentView.AddConstraints(new NSLayoutConstraint[] { ContentView.AddConstraints(new NSLayoutConstraint[] {
NSLayoutConstraint.Create(TextField, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Leading, 1f, leadingConstant), NSLayoutConstraint.Create(TextField, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Leading, 1f, leadingConstant),
NSLayoutConstraint.Create(ContentView, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, TextField, NSLayoutAttribute.Trailing, 1f, useButton ? 55f : 15f), NSLayoutConstraint.Create(ContentView, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, TextField, NSLayoutAttribute.Trailing, 1f, GetTextFieldToContainerTrailingConstant(buttonsConfig)),
NSLayoutConstraint.Create(ContentView, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, TextField, NSLayoutAttribute.Bottom, 1f, 10f) NSLayoutConstraint.Create(ContentView, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, TextField, NSLayoutAttribute.Bottom, 1f, 10f)
}); });
@ -148,18 +148,9 @@ namespace Bit.iOS.Core.Views
}); });
} }
if (useButton) if(buttonsConfig != ButtonsConfig.None)
{ {
Button = new UIButton(UIButtonType.System); AddButtons(buttonsConfig);
Button.Frame = ContentView.Bounds;
Button.TranslatesAutoresizingMaskIntoConstraints = false;
Button.SetTitleColor(ThemeHelpers.PrimaryColor, UIControlState.Normal);
ContentView.Add(Button);
ContentView.BottomAnchor.ConstraintEqualTo(Button.BottomAnchor, 10f).Active = true;
ContentView.TrailingAnchor.ConstraintEqualTo(Button.TrailingAnchor, 10f).Active = true;
Button.LeadingAnchor.ConstraintEqualTo(TextField.TrailingAnchor, 10f).Active = true;
} }
} }
@ -174,5 +165,74 @@ namespace Bit.iOS.Core.Views
TextField.BecomeFirstResponder(); TextField.BecomeFirstResponder();
} }
} }
public void ConfigureToggleSecureTextCell(bool useSecondaryButton = false)
{
var button = useSecondaryButton ? SecondButton : Button;
button.TitleLabel.Font = UIFont.FromName("bwi-font", 28f);
button.SetTitle(BitwardenIcons.Eye, UIControlState.Normal);
button.TouchUpInside += (sender, e) =>
{
TextField.SecureTextEntry = !TextField.SecureTextEntry;
button.SetTitle(TextField.SecureTextEntry ? BitwardenIcons.Eye : BitwardenIcons.EyeSlash, UIControlState.Normal);
};
}
private void AddButtons(ButtonsConfig buttonsConfig)
{
Button = new UIButton(UIButtonType.System);
Button.TranslatesAutoresizingMaskIntoConstraints = false;
Button.SetTitleColor(ThemeHelpers.PrimaryColor, UIControlState.Normal);
ContentView.Add(Button);
ContentView.BottomAnchor.ConstraintEqualTo(Button.BottomAnchor, 10f).Active = true;
switch (buttonsConfig)
{
case ButtonsConfig.One:
ContentView.AddConstraints(new NSLayoutConstraint[] {
NSLayoutConstraint.Create(ContentView, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, Button, NSLayoutAttribute.Trailing, 1f, 10f),
NSLayoutConstraint.Create(Button, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, TextField, NSLayoutAttribute.Trailing, 1f, 10f)
});
break;
case ButtonsConfig.Two:
SecondButton = new UIButton(UIButtonType.System);
SecondButton.TranslatesAutoresizingMaskIntoConstraints = false;
SecondButton.SetTitleColor(ThemeHelpers.PrimaryColor, UIControlState.Normal);
ContentView.Add(SecondButton);
ContentView.AddConstraints(new NSLayoutConstraint[] {
NSLayoutConstraint.Create(ContentView, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, SecondButton, NSLayoutAttribute.Bottom, 1f, 10f),
NSLayoutConstraint.Create(SecondButton, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, TextField, NSLayoutAttribute.Trailing, 1f, 9f),
NSLayoutConstraint.Create(Button, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, SecondButton, NSLayoutAttribute.Trailing, 1f, 10f),
NSLayoutConstraint.Create(ContentView, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, Button, NSLayoutAttribute.Trailing, 1f, 10f)
});
break;
}
}
private float GetTextFieldToContainerTrailingConstant(ButtonsConfig buttonsConfig)
{
switch (buttonsConfig)
{
case ButtonsConfig.None:
return 15f;
case ButtonsConfig.One:
return 55f;
case ButtonsConfig.Two:
return 95f;
default:
throw new ArgumentOutOfRangeException();
}
}
public enum ButtonsConfig : byte
{
None = 0,
One = 1,
Two = 2
}
} }
} }