[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:
parent
3cb9f37997
commit
505426cd6a
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,5 +37,6 @@ namespace Bit.App.Abstractions
|
||||||
Task OnAccountSwitchCompleteAsync();
|
Task OnAccountSwitchCompleteAsync();
|
||||||
Task SetScreenCaptureAllowedAsync();
|
Task SetScreenCaptureAllowedAsync();
|
||||||
void OpenAppSettings();
|
void OpenAppSettings();
|
||||||
|
void CloseExtensionPopUp();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue