mirror of
https://github.com/bitwarden/android.git
synced 2024-11-01 07:35:52 +03:00
moved view controllers to core library for reuse
This commit is contained in:
parent
df80122ce1
commit
5f1a8017f1
18 changed files with 1138 additions and 1019 deletions
|
@ -69,8 +69,6 @@ namespace Bit.iOS.Autofill
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
PerformSegue("loginListSegue", this);
|
PerformSegue("loginListSegue", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +89,8 @@ namespace Bit.iOS.Autofill
|
||||||
|
|
||||||
public void CompleteRequest(string username = null, string password = null, string totp = null)
|
public void CompleteRequest(string username = null, string password = null, string totp = null)
|
||||||
{
|
{
|
||||||
if(string.IsNullOrWhiteSpace(username) && string.IsNullOrWhiteSpace(password)) {
|
if(string.IsNullOrWhiteSpace(username) && string.IsNullOrWhiteSpace(password))
|
||||||
|
{
|
||||||
_googleAnalyticsService.TrackAutofillExtensionEvent("Canceled");
|
_googleAnalyticsService.TrackAutofillExtensionEvent("Canceled");
|
||||||
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
||||||
Convert.ToInt32(ASExtensionErrorCode.UserCanceled), null);
|
Convert.ToInt32(ASExtensionErrorCode.UserCanceled), null);
|
||||||
|
@ -121,8 +120,6 @@ namespace Bit.iOS.Autofill
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
|
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
|
||||||
{
|
{
|
||||||
var navController = segue.DestinationViewController as UINavigationController;
|
var navController = segue.DestinationViewController as UINavigationController;
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
using AuthenticationServices;
|
using AuthenticationServices;
|
||||||
|
using Bit.iOS.Core.Models;
|
||||||
using Foundation;
|
using Foundation;
|
||||||
|
|
||||||
namespace Bit.iOS.Autofill.Models
|
namespace Bit.iOS.Autofill.Models
|
||||||
{
|
{
|
||||||
public class Context
|
public class Context : AppExtensionContext
|
||||||
{
|
{
|
||||||
public NSExtensionContext ExtContext { get; set; }
|
public NSExtensionContext ExtContext { get; set; }
|
||||||
public ASCredentialServiceIdentifier[] ServiceIdentifiers { get; set; }
|
public ASCredentialServiceIdentifier[] ServiceIdentifiers { get; set; }
|
||||||
public string UrlString { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
86
src/iOS.Core/Controllers/LockFingerprintViewController.cs
Normal file
86
src/iOS.Core/Controllers/LockFingerprintViewController.cs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
using System;
|
||||||
|
using UIKit;
|
||||||
|
using XLabs.Ioc;
|
||||||
|
using Plugin.Fingerprint.Abstractions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.iOS.Core.Controllers;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
|
||||||
|
namespace Bit.iOS.Core.Controllers
|
||||||
|
{
|
||||||
|
public abstract class LockFingerprintViewController : ExtendedUIViewController
|
||||||
|
{
|
||||||
|
private IAppSettingsService _appSettingsService;
|
||||||
|
private IFingerprint _fingerprint;
|
||||||
|
private IDeviceInfoService _deviceInfo;
|
||||||
|
|
||||||
|
public LockFingerprintViewController(IntPtr handle) : base(handle)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public abstract UINavigationItem BaseNavItem { get; }
|
||||||
|
public abstract UIBarButtonItem BaseCancelButton { get; }
|
||||||
|
public abstract UIButton BaseUseButton { get; }
|
||||||
|
public abstract UIButton BaseFingerprintButton { get; }
|
||||||
|
public abstract Action Success { get; }
|
||||||
|
|
||||||
|
public override void ViewWillAppear(bool animated)
|
||||||
|
{
|
||||||
|
UINavigationBar.Appearance.ShadowImage = new UIImage();
|
||||||
|
UINavigationBar.Appearance.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
|
||||||
|
base.ViewWillAppear(animated);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ViewDidLoad()
|
||||||
|
{
|
||||||
|
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
|
||||||
|
_fingerprint = Resolver.Resolve<IFingerprint>();
|
||||||
|
_deviceInfo = Resolver.Resolve<IDeviceInfoService>();
|
||||||
|
|
||||||
|
BaseNavItem.Title = _deviceInfo.HasFaceIdSupport ? AppResources.VerifyFaceID : AppResources.VerifyFingerprint;
|
||||||
|
BaseCancelButton.Title = AppResources.Cancel;
|
||||||
|
View.BackgroundColor = new UIColor(red: 0.94f, green: 0.94f, blue: 0.96f, alpha: 1.0f);
|
||||||
|
|
||||||
|
BaseUseButton.SetTitle(_deviceInfo.HasFaceIdSupport ? AppResources.UseFaceIDToUnlock :
|
||||||
|
AppResources.UseFingerprintToUnlock, UIControlState.Normal);
|
||||||
|
var descriptor = UIFontDescriptor.PreferredBody;
|
||||||
|
BaseUseButton.Font = UIFont.FromDescriptor(descriptor, descriptor.PointSize);
|
||||||
|
BaseUseButton.BackgroundColor = new UIColor(red: 0.24f, green: 0.55f, blue: 0.74f, alpha: 1.0f);
|
||||||
|
BaseUseButton.TintColor = UIColor.White;
|
||||||
|
BaseUseButton.TouchUpInside += UseButton_TouchUpInside;
|
||||||
|
|
||||||
|
BaseFingerprintButton.SetImage(new UIImage(_deviceInfo.HasFaceIdSupport ? "smile.png" : "fingerprint.png"),
|
||||||
|
UIControlState.Normal);
|
||||||
|
|
||||||
|
base.ViewDidLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UseButton_TouchUpInside(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var task = CheckFingerprintAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ViewDidAppear(bool animated)
|
||||||
|
{
|
||||||
|
base.ViewDidAppear(animated);
|
||||||
|
var task = CheckFingerprintAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CheckFingerprintAsync()
|
||||||
|
{
|
||||||
|
var fingerprintRequest = new AuthenticationRequestConfiguration(
|
||||||
|
_deviceInfo.HasFaceIdSupport ? AppResources.FaceIDDirection : AppResources.FingerprintDirection)
|
||||||
|
{
|
||||||
|
AllowAlternativeAuthentication = true,
|
||||||
|
CancelTitle = AppResources.Cancel,
|
||||||
|
FallbackTitle = AppResources.LogOut
|
||||||
|
};
|
||||||
|
var result = await _fingerprint.AuthenticateAsync(fingerprintRequest);
|
||||||
|
if(result.Authenticated)
|
||||||
|
{
|
||||||
|
_appSettingsService.Locked = false;
|
||||||
|
Success();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
178
src/iOS.Core/Controllers/LockPasswordViewController.cs
Normal file
178
src/iOS.Core/Controllers/LockPasswordViewController.cs
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
using System;
|
||||||
|
using UIKit;
|
||||||
|
using XLabs.Ioc;
|
||||||
|
using Foundation;
|
||||||
|
using Bit.iOS.Core.Views;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.iOS.Core.Utilities;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using System.Linq;
|
||||||
|
using Bit.iOS.Core.Controllers;
|
||||||
|
|
||||||
|
namespace Bit.iOS.Core.Controllers
|
||||||
|
{
|
||||||
|
public abstract class LockPasswordViewController : ExtendedUITableViewController
|
||||||
|
{
|
||||||
|
private IAppSettingsService _appSettingsService;
|
||||||
|
private IAuthService _authService;
|
||||||
|
private ICryptoService _cryptoService;
|
||||||
|
|
||||||
|
public LockPasswordViewController(IntPtr handle) : base(handle)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public abstract UINavigationItem BaseNavItem { get; }
|
||||||
|
public abstract UIBarButtonItem BaseCancelButton { get; }
|
||||||
|
public abstract UIBarButtonItem BaseSubmitButton { get; }
|
||||||
|
public abstract Action Success { get; }
|
||||||
|
|
||||||
|
public FormEntryTableViewCell MasterPasswordCell { get; set; } = new FormEntryTableViewCell(
|
||||||
|
AppResources.MasterPassword, useLabelAsPlaceholder: true);
|
||||||
|
|
||||||
|
public override void ViewWillAppear(bool animated)
|
||||||
|
{
|
||||||
|
UINavigationBar.Appearance.ShadowImage = new UIImage();
|
||||||
|
UINavigationBar.Appearance.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
|
||||||
|
base.ViewWillAppear(animated);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ViewDidLoad()
|
||||||
|
{
|
||||||
|
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
|
||||||
|
_authService = Resolver.Resolve<IAuthService>();
|
||||||
|
_cryptoService = Resolver.Resolve<ICryptoService>();
|
||||||
|
|
||||||
|
BaseNavItem.Title = AppResources.VerifyMasterPassword;
|
||||||
|
BaseCancelButton.Title = AppResources.Cancel;
|
||||||
|
BaseSubmitButton.Title = AppResources.Submit;
|
||||||
|
View.BackgroundColor = new UIColor(red: 0.94f, green: 0.94f, blue: 0.96f, alpha: 1.0f);
|
||||||
|
|
||||||
|
var descriptor = UIFontDescriptor.PreferredBody;
|
||||||
|
|
||||||
|
MasterPasswordCell.TextField.SecureTextEntry = true;
|
||||||
|
MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go;
|
||||||
|
MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) =>
|
||||||
|
{
|
||||||
|
CheckPassword();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
TableView.RowHeight = UITableView.AutomaticDimension;
|
||||||
|
TableView.EstimatedRowHeight = 70;
|
||||||
|
TableView.Source = new TableSource(this);
|
||||||
|
TableView.AllowsSelection = true;
|
||||||
|
|
||||||
|
base.ViewDidLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ViewDidAppear(bool animated)
|
||||||
|
{
|
||||||
|
base.ViewDidAppear(animated);
|
||||||
|
MasterPasswordCell.TextField.BecomeFirstResponder();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void CheckPassword()
|
||||||
|
{
|
||||||
|
if(string.IsNullOrWhiteSpace(MasterPasswordCell.TextField.Text))
|
||||||
|
{
|
||||||
|
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
|
||||||
|
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword), AppResources.Ok);
|
||||||
|
PresentViewController(alert, true, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = _cryptoService.MakeKeyFromPassword(MasterPasswordCell.TextField.Text, _authService.Email,
|
||||||
|
_authService.Kdf, _authService.KdfIterations);
|
||||||
|
if(key.Key.SequenceEqual(_cryptoService.Key.Key))
|
||||||
|
{
|
||||||
|
_appSettingsService.Locked = false;
|
||||||
|
MasterPasswordCell.TextField.ResignFirstResponder();
|
||||||
|
Success();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: keep track of invalid attempts and logout?
|
||||||
|
|
||||||
|
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
|
||||||
|
string.Format(null, AppResources.InvalidMasterPassword), AppResources.Ok, (a) =>
|
||||||
|
{
|
||||||
|
|
||||||
|
MasterPasswordCell.TextField.Text = string.Empty;
|
||||||
|
MasterPasswordCell.TextField.BecomeFirstResponder();
|
||||||
|
});
|
||||||
|
|
||||||
|
PresentViewController(alert, true, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TableSource : UITableViewSource
|
||||||
|
{
|
||||||
|
private LockPasswordViewController _controller;
|
||||||
|
|
||||||
|
public TableSource(LockPasswordViewController controller)
|
||||||
|
{
|
||||||
|
_controller = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
|
||||||
|
{
|
||||||
|
if(indexPath.Section == 0)
|
||||||
|
{
|
||||||
|
if(indexPath.Row == 0)
|
||||||
|
{
|
||||||
|
return _controller.MasterPasswordCell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UITableViewCell();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
|
||||||
|
{
|
||||||
|
return UITableView.AutomaticDimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override nint NumberOfSections(UITableView tableView)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override nint RowsInSection(UITableView tableview, nint section)
|
||||||
|
{
|
||||||
|
if(section == 0)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override nfloat GetHeightForHeader(UITableView tableView, nint section)
|
||||||
|
{
|
||||||
|
return UITableView.AutomaticDimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string TitleForHeader(UITableView tableView, nint section)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
|
||||||
|
{
|
||||||
|
tableView.DeselectRow(indexPath, true);
|
||||||
|
tableView.EndEditing(true);
|
||||||
|
|
||||||
|
var cell = tableView.CellAt(indexPath);
|
||||||
|
if(cell == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectableCell = cell as ISelectable;
|
||||||
|
if(selectableCell != null)
|
||||||
|
{
|
||||||
|
selectableCell.Select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
102
src/iOS.Core/Controllers/LockPinViewController.cs
Normal file
102
src/iOS.Core/Controllers/LockPinViewController.cs
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
using System;
|
||||||
|
using UIKit;
|
||||||
|
using XLabs.Ioc;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.iOS.Core.Utilities;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Bit.iOS.Core.Controllers;
|
||||||
|
|
||||||
|
namespace Bit.iOS.Core.Controllers
|
||||||
|
{
|
||||||
|
public abstract class LockPinViewController : ExtendedUIViewController
|
||||||
|
{
|
||||||
|
private IAppSettingsService _appSettingsService;
|
||||||
|
private IAuthService _authService;
|
||||||
|
|
||||||
|
public LockPinViewController(IntPtr handle) : base(handle)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public abstract UINavigationItem BaseNavItem { get; }
|
||||||
|
public abstract UIBarButtonItem BaseCancelButton { get; }
|
||||||
|
public abstract UILabel BasePinLabel { get; }
|
||||||
|
public abstract UILabel BaseInstructionLabel { get; }
|
||||||
|
public abstract UITextField BasePinTextField { get; }
|
||||||
|
public abstract Action Success { get; }
|
||||||
|
|
||||||
|
public override void ViewWillAppear(bool animated)
|
||||||
|
{
|
||||||
|
UINavigationBar.Appearance.ShadowImage = new UIImage();
|
||||||
|
UINavigationBar.Appearance.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
|
||||||
|
base.ViewWillAppear(animated);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ViewDidLoad()
|
||||||
|
{
|
||||||
|
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
|
||||||
|
_authService = Resolver.Resolve<IAuthService>();
|
||||||
|
|
||||||
|
BaseNavItem.Title = AppResources.VerifyPIN;
|
||||||
|
BaseCancelButton.Title = AppResources.Cancel;
|
||||||
|
View.BackgroundColor = new UIColor(red: 0.94f, green: 0.94f, blue: 0.96f, alpha: 1.0f);
|
||||||
|
|
||||||
|
var descriptor = UIFontDescriptor.PreferredBody;
|
||||||
|
BasePinLabel.Font = UIFont.FromName("Menlo-Regular", 35);
|
||||||
|
|
||||||
|
BaseInstructionLabel.Text = AppResources.EnterPIN;
|
||||||
|
BaseInstructionLabel.LineBreakMode = UILineBreakMode.WordWrap;
|
||||||
|
BaseInstructionLabel.Lines = 0;
|
||||||
|
BaseInstructionLabel.Font = UIFont.FromDescriptor(descriptor, descriptor.PointSize * 0.8f);
|
||||||
|
BaseInstructionLabel.TextColor = new UIColor(red: 0.47f, green: 0.47f, blue: 0.47f, alpha: 1.0f);
|
||||||
|
|
||||||
|
BasePinTextField.EditingChanged += PinTextField_EditingChanged;
|
||||||
|
|
||||||
|
base.ViewDidLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ViewDidAppear(bool animated)
|
||||||
|
{
|
||||||
|
base.ViewDidAppear(animated);
|
||||||
|
BasePinTextField.BecomeFirstResponder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PinTextField_EditingChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
SetLabelText();
|
||||||
|
|
||||||
|
if(BasePinTextField.Text.Length >= 4)
|
||||||
|
{
|
||||||
|
if(BasePinTextField.Text == _authService.PIN)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("BW Log, Start Dismiss PIN controller.");
|
||||||
|
_appSettingsService.Locked = false;
|
||||||
|
BasePinTextField.ResignFirstResponder();
|
||||||
|
Success();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: keep track of invalid attempts and logout?
|
||||||
|
|
||||||
|
var alert = Dialogs.CreateAlert(null, AppResources.InvalidPIN, AppResources.Ok, (a) =>
|
||||||
|
{
|
||||||
|
BasePinTextField.Text = string.Empty;
|
||||||
|
SetLabelText();
|
||||||
|
BasePinTextField.BecomeFirstResponder();
|
||||||
|
});
|
||||||
|
PresentViewController(alert, true, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetLabelText()
|
||||||
|
{
|
||||||
|
var newText = string.Empty;
|
||||||
|
for(int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
newText += BasePinTextField.Text.Length <= i ? "- " : "• ";
|
||||||
|
}
|
||||||
|
|
||||||
|
BasePinLabel.Text = newText.TrimEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
324
src/iOS.Core/Controllers/LoginAddViewController.cs
Normal file
324
src/iOS.Core/Controllers/LoginAddViewController.cs
Normal file
|
@ -0,0 +1,324 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Models;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.iOS.Core.Views;
|
||||||
|
using Foundation;
|
||||||
|
using UIKit;
|
||||||
|
using XLabs.Ioc;
|
||||||
|
using Bit.App;
|
||||||
|
using Plugin.Connectivity.Abstractions;
|
||||||
|
using Bit.iOS.Core.Utilities;
|
||||||
|
using Bit.iOS.Core.Models;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Bit.iOS.Core.Controllers
|
||||||
|
{
|
||||||
|
public abstract class LoginAddViewController : ExtendedUITableViewController
|
||||||
|
{
|
||||||
|
private ICipherService _cipherService;
|
||||||
|
private IFolderService _folderService;
|
||||||
|
private IConnectivity _connectivity;
|
||||||
|
private IEnumerable<Folder> _folders;
|
||||||
|
private IGoogleAnalyticsService _googleAnalyticsService;
|
||||||
|
|
||||||
|
public LoginAddViewController(IntPtr handle) : base(handle)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public AppExtensionContext Context { get; set; }
|
||||||
|
public FormEntryTableViewCell NameCell { get; set; } = new FormEntryTableViewCell(AppResources.Name);
|
||||||
|
public FormEntryTableViewCell UsernameCell { get; set; } = new FormEntryTableViewCell(AppResources.Username);
|
||||||
|
public FormEntryTableViewCell PasswordCell { get; set; } = new FormEntryTableViewCell(AppResources.Password);
|
||||||
|
public UITableViewCell GeneratePasswordCell { get; set; } = new UITableViewCell(UITableViewCellStyle.Subtitle, "GeneratePasswordCell");
|
||||||
|
public FormEntryTableViewCell UriCell { get; set; } = new FormEntryTableViewCell(AppResources.URI);
|
||||||
|
public SwitchTableViewCell FavoriteCell { get; set; } = new SwitchTableViewCell(AppResources.Favorite);
|
||||||
|
public FormEntryTableViewCell NotesCell { get; set; } = new FormEntryTableViewCell(useTextView: true, height: 180);
|
||||||
|
public PickerTableViewCell FolderCell { get; set; } = new PickerTableViewCell(AppResources.Folder);
|
||||||
|
|
||||||
|
public abstract UINavigationItem BaseNavItem { get; }
|
||||||
|
public abstract UIBarButtonItem BaseCancelButton { get; }
|
||||||
|
public abstract UIBarButtonItem BaseSaveButton { get; }
|
||||||
|
public abstract Action Success { get; }
|
||||||
|
|
||||||
|
public override void ViewWillAppear(bool animated)
|
||||||
|
{
|
||||||
|
UINavigationBar.Appearance.ShadowImage = new UIImage();
|
||||||
|
UINavigationBar.Appearance.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
|
||||||
|
base.ViewWillAppear(animated);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ViewDidLoad()
|
||||||
|
{
|
||||||
|
_cipherService = Resolver.Resolve<ICipherService>();
|
||||||
|
_connectivity = Resolver.Resolve<IConnectivity>();
|
||||||
|
_folderService = Resolver.Resolve<IFolderService>();
|
||||||
|
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||||
|
|
||||||
|
BaseNavItem.Title = AppResources.AddItem;
|
||||||
|
BaseCancelButton.Title = AppResources.Cancel;
|
||||||
|
BaseSaveButton.Title = AppResources.Save;
|
||||||
|
View.BackgroundColor = new UIColor(red: 0.94f, green: 0.94f, blue: 0.96f, alpha: 1.0f);
|
||||||
|
|
||||||
|
NameCell.TextField.Text = Context?.Uri?.Host ?? string.Empty;
|
||||||
|
NameCell.TextField.ReturnKeyType = UIReturnKeyType.Next;
|
||||||
|
NameCell.TextField.ShouldReturn += (UITextField tf) =>
|
||||||
|
{
|
||||||
|
UsernameCell.TextField.BecomeFirstResponder();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
UsernameCell.TextField.AutocapitalizationType = UITextAutocapitalizationType.None;
|
||||||
|
UsernameCell.TextField.AutocorrectionType = UITextAutocorrectionType.No;
|
||||||
|
UsernameCell.TextField.SpellCheckingType = UITextSpellCheckingType.No;
|
||||||
|
UsernameCell.TextField.ReturnKeyType = UIReturnKeyType.Next;
|
||||||
|
UsernameCell.TextField.ShouldReturn += (UITextField tf) =>
|
||||||
|
{
|
||||||
|
PasswordCell.TextField.BecomeFirstResponder();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
PasswordCell.TextField.SecureTextEntry = true;
|
||||||
|
PasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Next;
|
||||||
|
PasswordCell.TextField.ShouldReturn += (UITextField tf) =>
|
||||||
|
{
|
||||||
|
UriCell.TextField.BecomeFirstResponder();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
GeneratePasswordCell.TextLabel.Text = AppResources.GeneratePassword;
|
||||||
|
GeneratePasswordCell.Accessory = UITableViewCellAccessory.DisclosureIndicator;
|
||||||
|
|
||||||
|
UriCell.TextField.Text = Context?.UrlString ?? string.Empty;
|
||||||
|
UriCell.TextField.KeyboardType = UIKeyboardType.Url;
|
||||||
|
UriCell.TextField.ReturnKeyType = UIReturnKeyType.Next;
|
||||||
|
UriCell.TextField.ShouldReturn += (UITextField tf) =>
|
||||||
|
{
|
||||||
|
NotesCell.TextView.BecomeFirstResponder();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
_folders = _folderService.GetAllAsync().GetAwaiter().GetResult();
|
||||||
|
var folderNames = _folders.Select(s => s.Name.Decrypt()).OrderBy(s => s).ToList();
|
||||||
|
folderNames.Insert(0, AppResources.FolderNone);
|
||||||
|
FolderCell.Items = folderNames;
|
||||||
|
|
||||||
|
TableView.RowHeight = UITableView.AutomaticDimension;
|
||||||
|
TableView.EstimatedRowHeight = 70;
|
||||||
|
TableView.Source = new TableSource(this);
|
||||||
|
TableView.AllowsSelection = true;
|
||||||
|
|
||||||
|
base.ViewDidLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ViewDidAppear(bool animated)
|
||||||
|
{
|
||||||
|
base.ViewDidAppear(animated);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task SaveAsync()
|
||||||
|
{
|
||||||
|
if(!_connectivity.IsConnected)
|
||||||
|
{
|
||||||
|
AlertNoConnection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(string.IsNullOrWhiteSpace(PasswordCell.TextField.Text))
|
||||||
|
{
|
||||||
|
DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired, AppResources.Password), AppResources.Ok);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(string.IsNullOrWhiteSpace(NameCell.TextField.Text))
|
||||||
|
{
|
||||||
|
DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired, AppResources.Name), AppResources.Ok);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cipher = new Cipher
|
||||||
|
{
|
||||||
|
Name = string.IsNullOrWhiteSpace(NameCell.TextField.Text) ? null : NameCell.TextField.Text.Encrypt(),
|
||||||
|
Notes = string.IsNullOrWhiteSpace(NotesCell.TextView.Text) ? null : NotesCell.TextView.Text.Encrypt(),
|
||||||
|
Favorite = FavoriteCell.Switch.On,
|
||||||
|
FolderId = FolderCell.SelectedIndex == 0 ? null : _folders.ElementAtOrDefault(FolderCell.SelectedIndex - 1)?.Id,
|
||||||
|
Type = App.Enums.CipherType.Login,
|
||||||
|
Login = new Login
|
||||||
|
{
|
||||||
|
Uris = null,
|
||||||
|
Username = string.IsNullOrWhiteSpace(UsernameCell.TextField.Text) ? null : UsernameCell.TextField.Text.Encrypt(),
|
||||||
|
Password = string.IsNullOrWhiteSpace(PasswordCell.TextField.Text) ? null : PasswordCell.TextField.Text.Encrypt()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!string.IsNullOrWhiteSpace(UriCell.TextField.Text))
|
||||||
|
{
|
||||||
|
cipher.Login.Uris = new List<LoginUri>
|
||||||
|
{
|
||||||
|
new LoginUri
|
||||||
|
{
|
||||||
|
Uri = UriCell.TextField.Text.Encrypt()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var saveTask = _cipherService.SaveAsync(cipher);
|
||||||
|
var loadingAlert = Dialogs.CreateLoadingAlert(AppResources.Saving);
|
||||||
|
PresentViewController(loadingAlert, true, null);
|
||||||
|
await saveTask;
|
||||||
|
|
||||||
|
await loadingAlert.DismissViewControllerAsync(true);
|
||||||
|
if(saveTask.Result.Succeeded)
|
||||||
|
{
|
||||||
|
_googleAnalyticsService.TrackExtensionEvent("CreatedLogin");
|
||||||
|
Success();
|
||||||
|
}
|
||||||
|
else if(saveTask.Result.Errors.Count() > 0)
|
||||||
|
{
|
||||||
|
DisplayAlert(AppResources.AnErrorHasOccurred, saveTask.Result.Errors.First().Message, AppResources.Ok);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DisplayAlert(null, AppResources.AnErrorHasOccurred, AppResources.Ok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisplayAlert(string title, string message, string accept)
|
||||||
|
{
|
||||||
|
var alert = Dialogs.CreateAlert(title, message, accept);
|
||||||
|
PresentViewController(alert, true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AlertNoConnection()
|
||||||
|
{
|
||||||
|
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage, AppResources.Ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TableSource : UITableViewSource
|
||||||
|
{
|
||||||
|
private LoginAddViewController _controller;
|
||||||
|
|
||||||
|
public TableSource(LoginAddViewController controller)
|
||||||
|
{
|
||||||
|
_controller = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
|
||||||
|
{
|
||||||
|
if(indexPath.Section == 0)
|
||||||
|
{
|
||||||
|
if(indexPath.Row == 0)
|
||||||
|
{
|
||||||
|
return _controller.NameCell;
|
||||||
|
}
|
||||||
|
else if(indexPath.Row == 1)
|
||||||
|
{
|
||||||
|
return _controller.UsernameCell;
|
||||||
|
}
|
||||||
|
else if(indexPath.Row == 2)
|
||||||
|
{
|
||||||
|
return _controller.PasswordCell;
|
||||||
|
}
|
||||||
|
else if(indexPath.Row == 3)
|
||||||
|
{
|
||||||
|
return _controller.GeneratePasswordCell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(indexPath.Section == 1)
|
||||||
|
{
|
||||||
|
return _controller.UriCell;
|
||||||
|
}
|
||||||
|
else if(indexPath.Section == 2)
|
||||||
|
{
|
||||||
|
if(indexPath.Row == 0)
|
||||||
|
{
|
||||||
|
return _controller.FolderCell;
|
||||||
|
}
|
||||||
|
else if(indexPath.Row == 1)
|
||||||
|
{
|
||||||
|
return _controller.FavoriteCell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(indexPath.Section == 3)
|
||||||
|
{
|
||||||
|
return _controller.NotesCell;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UITableViewCell();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
|
||||||
|
{
|
||||||
|
return UITableView.AutomaticDimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override nint NumberOfSections(UITableView tableView)
|
||||||
|
{
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override nint RowsInSection(UITableView tableview, nint section)
|
||||||
|
{
|
||||||
|
if(section == 0)
|
||||||
|
{
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
else if(section == 1)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else if(section == 2)
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override nfloat GetHeightForHeader(UITableView tableView, nint section)
|
||||||
|
{
|
||||||
|
return section == 0 || section == 3 ? UITableView.AutomaticDimension : 0.00001f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string TitleForHeader(UITableView tableView, nint section)
|
||||||
|
{
|
||||||
|
if(section == 0)
|
||||||
|
{
|
||||||
|
return AppResources.ItemInformation;
|
||||||
|
}
|
||||||
|
else if(section == 3)
|
||||||
|
{
|
||||||
|
return AppResources.Notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
|
||||||
|
{
|
||||||
|
tableView.DeselectRow(indexPath, true);
|
||||||
|
tableView.EndEditing(true);
|
||||||
|
|
||||||
|
if(indexPath.Section == 0 && indexPath.Row == 3)
|
||||||
|
{
|
||||||
|
_controller.PerformSegue("passwordGeneratorSegue", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
var cell = tableView.CellAt(indexPath);
|
||||||
|
if(cell == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectableCell = cell as ISelectable;
|
||||||
|
if(selectableCell != null)
|
||||||
|
{
|
||||||
|
selectableCell.Select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
336
src/iOS.Core/Controllers/PasswordGeneratorViewController.cs
Normal file
336
src/iOS.Core/Controllers/PasswordGeneratorViewController.cs
Normal file
|
@ -0,0 +1,336 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.iOS.Core.Views;
|
||||||
|
using Bit.iOS.Core.Models;
|
||||||
|
using Foundation;
|
||||||
|
using UIKit;
|
||||||
|
using XLabs.Ioc;
|
||||||
|
using Plugin.Settings.Abstractions;
|
||||||
|
using CoreGraphics;
|
||||||
|
using Bit.iOS.Core.Utilities;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
|
||||||
|
namespace Bit.iOS.Core.Controllers
|
||||||
|
{
|
||||||
|
public abstract class PasswordGeneratorViewController : ExtendedUIViewController
|
||||||
|
{
|
||||||
|
private IPasswordGenerationService _passwordGenerationService;
|
||||||
|
private ISettings _settings;
|
||||||
|
|
||||||
|
public PasswordGeneratorViewController(IntPtr handle) : base(handle)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected IGoogleAnalyticsService GoogleAnalyticsService { get; private set; }
|
||||||
|
public UITableViewController OptionsTableViewController { get; set; }
|
||||||
|
public SwitchTableViewCell UppercaseCell { get; set; } = new SwitchTableViewCell("A-Z");
|
||||||
|
public SwitchTableViewCell LowercaseCell { get; set; } = new SwitchTableViewCell("a-z");
|
||||||
|
public SwitchTableViewCell NumbersCell { get; set; } = new SwitchTableViewCell("0-9");
|
||||||
|
public SwitchTableViewCell SpecialCell { get; set; } = new SwitchTableViewCell("!@#$%^&*");
|
||||||
|
public StepperTableViewCell MinNumbersCell { get; set; } = new StepperTableViewCell(AppResources.MinNumbers, 1, 0, 5, 1);
|
||||||
|
public StepperTableViewCell MinSpecialCell { get; set; } = new StepperTableViewCell(AppResources.MinSpecial, 1, 0, 5, 1);
|
||||||
|
public SliderTableViewCell LengthCell { get; set; } = new SliderTableViewCell(AppResources.Length, 10, 5, 64);
|
||||||
|
|
||||||
|
public PasswordGenerationOptions PasswordOptions { get; set; }
|
||||||
|
public abstract UINavigationItem BaseNavItem { get; }
|
||||||
|
public abstract UIBarButtonItem BaseCancelButton { get; }
|
||||||
|
public abstract UIBarButtonItem BaseSelectBarButton { get; }
|
||||||
|
public abstract UILabel BasePasswordLabel { get; }
|
||||||
|
|
||||||
|
public override void ViewWillAppear(bool animated)
|
||||||
|
{
|
||||||
|
UINavigationBar.Appearance.ShadowImage = new UIImage();
|
||||||
|
UINavigationBar.Appearance.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
|
||||||
|
base.ViewWillAppear(animated);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ViewDidLoad()
|
||||||
|
{
|
||||||
|
_passwordGenerationService = Resolver.Resolve<IPasswordGenerationService>();
|
||||||
|
_settings = Resolver.Resolve<ISettings>();
|
||||||
|
GoogleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||||
|
|
||||||
|
BaseNavItem.Title = AppResources.PasswordGenerator;
|
||||||
|
BaseCancelButton.Title = AppResources.Cancel;
|
||||||
|
BaseSelectBarButton.Title = AppResources.Select;
|
||||||
|
View.BackgroundColor = new UIColor(red: 0.94f, green: 0.94f, blue: 0.96f, alpha: 1.0f);
|
||||||
|
|
||||||
|
var descriptor = UIFontDescriptor.PreferredBody;
|
||||||
|
BasePasswordLabel.Font = UIFont.FromName("Menlo-Regular", descriptor.PointSize * 1.3f);
|
||||||
|
BasePasswordLabel.LineBreakMode = UILineBreakMode.TailTruncation;
|
||||||
|
BasePasswordLabel.Lines = 1;
|
||||||
|
BasePasswordLabel.AdjustsFontSizeToFitWidth = false;
|
||||||
|
|
||||||
|
var controller = ChildViewControllers.LastOrDefault();
|
||||||
|
if(controller != null)
|
||||||
|
{
|
||||||
|
OptionsTableViewController = controller as UITableViewController;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(OptionsTableViewController != null)
|
||||||
|
{
|
||||||
|
OptionsTableViewController.TableView.RowHeight = UITableView.AutomaticDimension;
|
||||||
|
OptionsTableViewController.TableView.EstimatedRowHeight = 70;
|
||||||
|
OptionsTableViewController.TableView.Source = new TableSource(this);
|
||||||
|
OptionsTableViewController.TableView.AllowsSelection = true;
|
||||||
|
OptionsTableViewController.View.BackgroundColor = new UIColor(red: 0.94f, green: 0.94f, blue: 0.96f, alpha: 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
UppercaseCell.Switch.On = _settings.GetValueOrDefault(App.Constants.PasswordGeneratorUppercase, true);
|
||||||
|
LowercaseCell.Switch.On = _settings.GetValueOrDefault(App.Constants.PasswordGeneratorLowercase, true);
|
||||||
|
SpecialCell.Switch.On = _settings.GetValueOrDefault(App.Constants.PasswordGeneratorSpecial, true);
|
||||||
|
NumbersCell.Switch.On = _settings.GetValueOrDefault(App.Constants.PasswordGeneratorNumbers, true);
|
||||||
|
MinNumbersCell.Value = _settings.GetValueOrDefault(App.Constants.PasswordGeneratorMinNumbers, 1);
|
||||||
|
MinSpecialCell.Value = _settings.GetValueOrDefault(App.Constants.PasswordGeneratorMinSpecial, 1);
|
||||||
|
LengthCell.Value = _settings.GetValueOrDefault(App.Constants.PasswordGeneratorLength, 10);
|
||||||
|
|
||||||
|
UppercaseCell.ValueChanged += Options_ValueChanged;
|
||||||
|
LowercaseCell.ValueChanged += Options_ValueChanged;
|
||||||
|
NumbersCell.ValueChanged += Options_ValueChanged;
|
||||||
|
SpecialCell.ValueChanged += Options_ValueChanged;
|
||||||
|
MinNumbersCell.ValueChanged += Options_ValueChanged;
|
||||||
|
MinSpecialCell.ValueChanged += Options_ValueChanged;
|
||||||
|
LengthCell.ValueChanged += Options_ValueChanged;
|
||||||
|
|
||||||
|
// Adjust based on context password options
|
||||||
|
if(PasswordOptions != null)
|
||||||
|
{
|
||||||
|
if(PasswordOptions.RequireDigits)
|
||||||
|
{
|
||||||
|
NumbersCell.Switch.On = true;
|
||||||
|
NumbersCell.Switch.Enabled = false;
|
||||||
|
|
||||||
|
if(MinNumbersCell.Value < 1)
|
||||||
|
{
|
||||||
|
MinNumbersCell.Value = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
MinNumbersCell.Stepper.MinimumValue = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(PasswordOptions.RequireSymbols)
|
||||||
|
{
|
||||||
|
SpecialCell.Switch.On = true;
|
||||||
|
SpecialCell.Switch.Enabled = false;
|
||||||
|
|
||||||
|
if(MinSpecialCell.Value < 1)
|
||||||
|
{
|
||||||
|
MinSpecialCell.Value = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
MinSpecialCell.Stepper.MinimumValue = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(PasswordOptions.MinLength < PasswordOptions.MaxLength)
|
||||||
|
{
|
||||||
|
if(PasswordOptions.MinLength > 0 && PasswordOptions.MinLength > LengthCell.Slider.MinValue)
|
||||||
|
{
|
||||||
|
if(LengthCell.Value < PasswordOptions.MinLength)
|
||||||
|
{
|
||||||
|
LengthCell.Slider.Value = PasswordOptions.MinLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
LengthCell.Slider.MinValue = PasswordOptions.MinLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(PasswordOptions.MaxLength > 5 && PasswordOptions.MaxLength < LengthCell.Slider.MaxValue)
|
||||||
|
{
|
||||||
|
if(LengthCell.Value > PasswordOptions.MaxLength)
|
||||||
|
{
|
||||||
|
LengthCell.Slider.Value = PasswordOptions.MaxLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
LengthCell.Slider.MaxValue = PasswordOptions.MaxLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GeneratePassword();
|
||||||
|
GoogleAnalyticsService.TrackExtensionEvent("GeneratedPassword");
|
||||||
|
base.ViewDidLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Options_ValueChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if(InvalidState())
|
||||||
|
{
|
||||||
|
LowercaseCell.Switch.On = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GeneratePassword();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool InvalidState()
|
||||||
|
{
|
||||||
|
return !LowercaseCell.Switch.On && !UppercaseCell.Switch.On && !NumbersCell.Switch.On && !SpecialCell.Switch.On;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GeneratePassword()
|
||||||
|
{
|
||||||
|
BasePasswordLabel.Text = _passwordGenerationService.GeneratePassword(
|
||||||
|
length: LengthCell.Value,
|
||||||
|
uppercase: UppercaseCell.Switch.On,
|
||||||
|
lowercase: LowercaseCell.Switch.On,
|
||||||
|
numbers: NumbersCell.Switch.On,
|
||||||
|
special: SpecialCell.Switch.On,
|
||||||
|
minSpecial: MinSpecialCell.Value,
|
||||||
|
minNumbers: MinNumbersCell.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TableSource : UITableViewSource
|
||||||
|
{
|
||||||
|
private PasswordGeneratorViewController _controller;
|
||||||
|
|
||||||
|
public TableSource(PasswordGeneratorViewController controller)
|
||||||
|
{
|
||||||
|
_controller = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
|
||||||
|
{
|
||||||
|
if(indexPath.Section == 0)
|
||||||
|
{
|
||||||
|
var cell = new UITableViewCell();
|
||||||
|
cell.TextLabel.TextColor = new UIColor(red: 0.24f, green: 0.55f, blue: 0.74f, alpha: 1.0f);
|
||||||
|
if(indexPath.Row == 0)
|
||||||
|
{
|
||||||
|
cell.TextLabel.Text = AppResources.RegeneratePassword;
|
||||||
|
}
|
||||||
|
else if(indexPath.Row == 1)
|
||||||
|
{
|
||||||
|
cell.TextLabel.Text = AppResources.CopyPassword;
|
||||||
|
}
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(indexPath.Row == 0)
|
||||||
|
{
|
||||||
|
return _controller.LengthCell;
|
||||||
|
}
|
||||||
|
else if(indexPath.Row == 1)
|
||||||
|
{
|
||||||
|
return _controller.UppercaseCell;
|
||||||
|
}
|
||||||
|
else if(indexPath.Row == 2)
|
||||||
|
{
|
||||||
|
return _controller.LowercaseCell;
|
||||||
|
}
|
||||||
|
else if(indexPath.Row == 3)
|
||||||
|
{
|
||||||
|
return _controller.NumbersCell;
|
||||||
|
}
|
||||||
|
else if(indexPath.Row == 4)
|
||||||
|
{
|
||||||
|
return _controller.SpecialCell;
|
||||||
|
}
|
||||||
|
else if(indexPath.Row == 5)
|
||||||
|
{
|
||||||
|
return _controller.MinNumbersCell;
|
||||||
|
}
|
||||||
|
else if(indexPath.Row == 6)
|
||||||
|
{
|
||||||
|
return _controller.MinSpecialCell;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UITableViewCell();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
|
||||||
|
{
|
||||||
|
return UITableView.AutomaticDimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override nint NumberOfSections(UITableView tableView)
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override nint RowsInSection(UITableView tableview, nint section)
|
||||||
|
{
|
||||||
|
if(section == 0)
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override nfloat GetHeightForHeader(UITableView tableView, nint section)
|
||||||
|
{
|
||||||
|
if(section == 0)
|
||||||
|
{
|
||||||
|
return 0.00001f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return UITableView.AutomaticDimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override UIView GetViewForHeader(UITableView tableView, nint section)
|
||||||
|
{
|
||||||
|
if(section == 0)
|
||||||
|
{
|
||||||
|
return new UIView(CGRect.Empty)
|
||||||
|
{
|
||||||
|
Hidden = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string TitleForHeader(UITableView tableView, nint section)
|
||||||
|
{
|
||||||
|
if(section == 1)
|
||||||
|
{
|
||||||
|
return AppResources.Options;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string TitleForFooter(UITableView tableView, nint section)
|
||||||
|
{
|
||||||
|
if(section == 1)
|
||||||
|
{
|
||||||
|
return AppResources.OptionDefaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
|
||||||
|
{
|
||||||
|
if(indexPath.Section == 0)
|
||||||
|
{
|
||||||
|
if(indexPath.Row == 0)
|
||||||
|
{
|
||||||
|
_controller.GoogleAnalyticsService.TrackExtensionEvent("RegeneratedPassword");
|
||||||
|
_controller.GeneratePassword();
|
||||||
|
}
|
||||||
|
else if(indexPath.Row == 1)
|
||||||
|
{
|
||||||
|
_controller.GoogleAnalyticsService.TrackExtensionEvent("CopiedGeneratedPassword");
|
||||||
|
UIPasteboard clipboard = UIPasteboard.General;
|
||||||
|
clipboard.String = _controller.BasePasswordLabel.Text;
|
||||||
|
var alert = Dialogs.CreateMessageAlert(AppResources.Copied);
|
||||||
|
_controller.PresentViewController(alert, true, () =>
|
||||||
|
{
|
||||||
|
_controller.DismissViewController(true, null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tableView.DeselectRow(indexPath, true);
|
||||||
|
tableView.EndEditing(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NSDate DateTimeToNSDate(DateTime date)
|
||||||
|
{
|
||||||
|
DateTime reference = TimeZone.CurrentTimeZone.ToLocalTime(
|
||||||
|
new DateTime(2001, 1, 1, 0, 0, 0));
|
||||||
|
return NSDate.FromTimeIntervalSinceReferenceDate(
|
||||||
|
(date - reference).TotalSeconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
src/iOS.Core/Models/AppExtensionContext.cs
Normal file
47
src/iOS.Core/Models/AppExtensionContext.cs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Bit.iOS.Core.Models
|
||||||
|
{
|
||||||
|
public class AppExtensionContext
|
||||||
|
{
|
||||||
|
private string _uriString;
|
||||||
|
|
||||||
|
public Uri Uri
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
Uri uri;
|
||||||
|
if(string.IsNullOrWhiteSpace(UrlString) || !Uri.TryCreate(UrlString, UriKind.Absolute, out uri))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public string UrlString
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _uriString;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_uriString = value;
|
||||||
|
if(_uriString != null && !_uriString.StartsWith(App.Constants.iOSAppProtocol) && _uriString.Contains("."))
|
||||||
|
{
|
||||||
|
if(!_uriString.Contains("://") && !_uriString.Contains(" "))
|
||||||
|
{
|
||||||
|
_uriString = string.Concat("http://", _uriString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!_uriString.StartsWith("http") && !_uriString.StartsWith(App.Constants.iOSAppProtocol))
|
||||||
|
{
|
||||||
|
_uriString = string.Concat(App.Constants.iOSAppProtocol, _uriString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public PasswordGenerationOptions PasswordOptions { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Bit.iOS.Extension.Models
|
namespace Bit.iOS.Core.Models
|
||||||
{
|
{
|
||||||
public class PasswordGenerationOptions
|
public class PasswordGenerationOptions
|
||||||
{
|
{
|
|
@ -38,13 +38,19 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Resources\" />
|
<Folder Include="Resources\" />
|
||||||
<Folder Include="Models\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Constants.cs" />
|
<Compile Include="Constants.cs" />
|
||||||
<Compile Include="Controllers\ExtendedUITableViewController.cs" />
|
<Compile Include="Controllers\ExtendedUITableViewController.cs" />
|
||||||
<Compile Include="Controllers\ExtendedUIViewController.cs" />
|
<Compile Include="Controllers\ExtendedUIViewController.cs" />
|
||||||
|
<Compile Include="Controllers\LockFingerprintViewController.cs" />
|
||||||
|
<Compile Include="Controllers\LockPasswordViewController.cs" />
|
||||||
|
<Compile Include="Controllers\LockPinViewController.cs" />
|
||||||
|
<Compile Include="Controllers\LoginAddViewController.cs" />
|
||||||
|
<Compile Include="Controllers\PasswordGeneratorViewController.cs" />
|
||||||
<Compile Include="HockeyAppCrashManagerDelegate.cs" />
|
<Compile Include="HockeyAppCrashManagerDelegate.cs" />
|
||||||
|
<Compile Include="Models\AppExtensionContext.cs" />
|
||||||
|
<Compile Include="Models\PasswordGenerationOptions.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="Services\AppInfoService.cs" />
|
<Compile Include="Services\AppInfoService.cs" />
|
||||||
<Compile Include="Services\NoopDeviceActionService.cs" />
|
<Compile Include="Services\NoopDeviceActionService.cs" />
|
||||||
|
|
|
@ -21,6 +21,7 @@ using Bit.iOS.Core.Controllers;
|
||||||
using SimpleInjector;
|
using SimpleInjector;
|
||||||
using XLabs.Ioc.SimpleInjectorContainer;
|
using XLabs.Ioc.SimpleInjectorContainer;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Bit.iOS.Core.Models;
|
||||||
|
|
||||||
namespace Bit.iOS.Extension
|
namespace Bit.iOS.Extension
|
||||||
{
|
{
|
||||||
|
@ -147,17 +148,14 @@ namespace Bit.iOS.Extension
|
||||||
}
|
}
|
||||||
else if(fingerprintViewController != null)
|
else if(fingerprintViewController != null)
|
||||||
{
|
{
|
||||||
fingerprintViewController.Context = _context;
|
|
||||||
fingerprintViewController.LoadingController = this;
|
fingerprintViewController.LoadingController = this;
|
||||||
}
|
}
|
||||||
else if(pinViewController != null)
|
else if(pinViewController != null)
|
||||||
{
|
{
|
||||||
pinViewController.Context = _context;
|
|
||||||
pinViewController.LoadingController = this;
|
pinViewController.LoadingController = this;
|
||||||
}
|
}
|
||||||
else if(passwordViewController != null)
|
else if(passwordViewController != null)
|
||||||
{
|
{
|
||||||
passwordViewController.Context = _context;
|
|
||||||
passwordViewController.LoadingController = this;
|
passwordViewController.LoadingController = this;
|
||||||
}
|
}
|
||||||
else if(setupViewController != null)
|
else if(setupViewController != null)
|
||||||
|
|
|
@ -1,68 +1,19 @@
|
||||||
using System;
|
using System;
|
||||||
using Bit.iOS.Extension.Models;
|
|
||||||
using UIKit;
|
using UIKit;
|
||||||
using XLabs.Ioc;
|
|
||||||
using Plugin.Fingerprint.Abstractions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Bit.iOS.Core.Controllers;
|
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
|
|
||||||
namespace Bit.iOS.Extension
|
namespace Bit.iOS.Extension
|
||||||
{
|
{
|
||||||
public partial class LockFingerprintViewController : ExtendedUIViewController
|
public partial class LockFingerprintViewController : Core.Controllers.LockFingerprintViewController
|
||||||
{
|
{
|
||||||
private IAppSettingsService _appSettingsService;
|
|
||||||
private IFingerprint _fingerprint;
|
|
||||||
private IDeviceInfoService _deviceInfo;
|
|
||||||
|
|
||||||
public LockFingerprintViewController(IntPtr handle) : base(handle)
|
public LockFingerprintViewController(IntPtr handle) : base(handle)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public Context Context { get; set; }
|
|
||||||
public LoadingViewController LoadingController { get; set; }
|
public LoadingViewController LoadingController { get; set; }
|
||||||
|
public override UINavigationItem BaseNavItem => NavItem;
|
||||||
public override void ViewWillAppear(bool animated)
|
public override UIBarButtonItem BaseCancelButton => CancelButton;
|
||||||
{
|
public override UIButton BaseUseButton => UseButton;
|
||||||
UINavigationBar.Appearance.ShadowImage = new UIImage();
|
public override UIButton BaseFingerprintButton => FingerprintButton;
|
||||||
UINavigationBar.Appearance.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
|
public override Action Success => () => LoadingController.DismissLockAndContinue();
|
||||||
base.ViewWillAppear(animated);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ViewDidLoad()
|
|
||||||
{
|
|
||||||
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
|
|
||||||
_fingerprint = Resolver.Resolve<IFingerprint>();
|
|
||||||
_deviceInfo = Resolver.Resolve<IDeviceInfoService>();
|
|
||||||
|
|
||||||
NavItem.Title = _deviceInfo.HasFaceIdSupport ? AppResources.VerifyFaceID : AppResources.VerifyFingerprint;
|
|
||||||
CancelButton.Title = AppResources.Cancel;
|
|
||||||
View.BackgroundColor = new UIColor(red: 0.94f, green: 0.94f, blue: 0.96f, alpha: 1.0f);
|
|
||||||
|
|
||||||
UseButton.SetTitle(_deviceInfo.HasFaceIdSupport ? AppResources.UseFaceIDToUnlock :
|
|
||||||
AppResources.UseFingerprintToUnlock, UIControlState.Normal);
|
|
||||||
var descriptor = UIFontDescriptor.PreferredBody;
|
|
||||||
UseButton.Font = UIFont.FromDescriptor(descriptor, descriptor.PointSize);
|
|
||||||
UseButton.BackgroundColor = new UIColor(red: 0.24f, green: 0.55f, blue: 0.74f, alpha: 1.0f);
|
|
||||||
UseButton.TintColor = UIColor.White;
|
|
||||||
UseButton.TouchUpInside += UseButton_TouchUpInside;
|
|
||||||
|
|
||||||
FingerprintButton.SetImage(new UIImage(_deviceInfo.HasFaceIdSupport ? "smile.png" : "fingerprint.png"),
|
|
||||||
UIControlState.Normal);
|
|
||||||
|
|
||||||
base.ViewDidLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UseButton_TouchUpInside(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
var task = CheckFingerprintAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ViewDidAppear(bool animated)
|
|
||||||
{
|
|
||||||
base.ViewDidAppear(animated);
|
|
||||||
var task = CheckFingerprintAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void CancelButton_Activated(UIBarButtonItem sender)
|
partial void CancelButton_Activated(UIBarButtonItem sender)
|
||||||
{
|
{
|
||||||
|
@ -73,22 +24,5 @@ namespace Bit.iOS.Extension
|
||||||
{
|
{
|
||||||
var task = CheckFingerprintAsync();
|
var task = CheckFingerprintAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CheckFingerprintAsync()
|
|
||||||
{
|
|
||||||
var fingerprintRequest = new AuthenticationRequestConfiguration(
|
|
||||||
_deviceInfo.HasFaceIdSupport ? AppResources.FaceIDDirection : AppResources.FingerprintDirection)
|
|
||||||
{
|
|
||||||
AllowAlternativeAuthentication = true,
|
|
||||||
CancelTitle = AppResources.Cancel,
|
|
||||||
FallbackTitle = AppResources.LogOut
|
|
||||||
};
|
|
||||||
var result = await _fingerprint.AuthenticateAsync(fingerprintRequest);
|
|
||||||
if(result.Authenticated)
|
|
||||||
{
|
|
||||||
_appSettingsService.Locked = false;
|
|
||||||
LoadingController.DismissLockAndContinue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,186 +1,27 @@
|
||||||
using System;
|
using System;
|
||||||
using Bit.iOS.Extension.Models;
|
|
||||||
using UIKit;
|
using UIKit;
|
||||||
using XLabs.Ioc;
|
|
||||||
using Foundation;
|
|
||||||
using Bit.iOS.Core.Views;
|
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.iOS.Core.Utilities;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using System.Linq;
|
|
||||||
using Bit.iOS.Core.Controllers;
|
|
||||||
|
|
||||||
namespace Bit.iOS.Extension
|
namespace Bit.iOS.Extension
|
||||||
{
|
{
|
||||||
public partial class LockPasswordViewController : ExtendedUITableViewController
|
public partial class LockPasswordViewController : Core.Controllers.LockPasswordViewController
|
||||||
{
|
{
|
||||||
private IAppSettingsService _appSettingsService;
|
|
||||||
private IAuthService _authService;
|
|
||||||
private ICryptoService _cryptoService;
|
|
||||||
|
|
||||||
public LockPasswordViewController(IntPtr handle) : base(handle)
|
public LockPasswordViewController(IntPtr handle) : base(handle)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public Context Context { get; set; }
|
|
||||||
public LoadingViewController LoadingController { get; set; }
|
public LoadingViewController LoadingController { get; set; }
|
||||||
public FormEntryTableViewCell MasterPasswordCell { get; set; } = new FormEntryTableViewCell(
|
public override UINavigationItem BaseNavItem => NavItem;
|
||||||
AppResources.MasterPassword, useLabelAsPlaceholder: true);
|
public override UIBarButtonItem BaseCancelButton => CancelButton;
|
||||||
|
public override UIBarButtonItem BaseSubmitButton => SubmitButton;
|
||||||
public override void ViewWillAppear(bool animated)
|
public override Action Success => () => LoadingController.DismissLockAndContinue();
|
||||||
{
|
|
||||||
UINavigationBar.Appearance.ShadowImage = new UIImage();
|
|
||||||
UINavigationBar.Appearance.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
|
|
||||||
base.ViewWillAppear(animated);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ViewDidLoad()
|
|
||||||
{
|
|
||||||
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
|
|
||||||
_authService = Resolver.Resolve<IAuthService>();
|
|
||||||
_cryptoService = Resolver.Resolve<ICryptoService>();
|
|
||||||
|
|
||||||
NavItem.Title = AppResources.VerifyMasterPassword;
|
|
||||||
CancelButton.Title = AppResources.Cancel;
|
|
||||||
SubmitButton.Title = AppResources.Submit;
|
|
||||||
View.BackgroundColor = new UIColor(red: 0.94f, green: 0.94f, blue: 0.96f, alpha: 1.0f);
|
|
||||||
|
|
||||||
var descriptor = UIFontDescriptor.PreferredBody;
|
|
||||||
|
|
||||||
MasterPasswordCell.TextField.SecureTextEntry = true;
|
|
||||||
MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go;
|
|
||||||
MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) =>
|
|
||||||
{
|
|
||||||
CheckPassword();
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
TableView.RowHeight = UITableView.AutomaticDimension;
|
|
||||||
TableView.EstimatedRowHeight = 70;
|
|
||||||
TableView.Source = new TableSource(this);
|
|
||||||
TableView.AllowsSelection = true;
|
|
||||||
|
|
||||||
base.ViewDidLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ViewDidAppear(bool animated)
|
|
||||||
{
|
|
||||||
base.ViewDidAppear(animated);
|
|
||||||
MasterPasswordCell.TextField.BecomeFirstResponder();
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void SubmitButton_Activated(UIBarButtonItem sender)
|
partial void SubmitButton_Activated(UIBarButtonItem sender)
|
||||||
{
|
{
|
||||||
CheckPassword();
|
CheckPassword();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckPassword()
|
|
||||||
{
|
|
||||||
if(string.IsNullOrWhiteSpace(MasterPasswordCell.TextField.Text))
|
|
||||||
{
|
|
||||||
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
|
|
||||||
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword), AppResources.Ok);
|
|
||||||
PresentViewController(alert, true, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var key = _cryptoService.MakeKeyFromPassword(MasterPasswordCell.TextField.Text, _authService.Email,
|
|
||||||
_authService.Kdf, _authService.KdfIterations);
|
|
||||||
if(key.Key.SequenceEqual(_cryptoService.Key.Key))
|
|
||||||
{
|
|
||||||
_appSettingsService.Locked = false;
|
|
||||||
MasterPasswordCell.TextField.ResignFirstResponder();
|
|
||||||
LoadingController.DismissLockAndContinue();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// TODO: keep track of invalid attempts and logout?
|
|
||||||
|
|
||||||
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
|
|
||||||
string.Format(null, AppResources.InvalidMasterPassword), AppResources.Ok, (a) =>
|
|
||||||
{
|
|
||||||
|
|
||||||
MasterPasswordCell.TextField.Text = string.Empty;
|
|
||||||
MasterPasswordCell.TextField.BecomeFirstResponder();
|
|
||||||
});
|
|
||||||
|
|
||||||
PresentViewController(alert, true, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void CancelButton_Activated(UIBarButtonItem sender)
|
partial void CancelButton_Activated(UIBarButtonItem sender)
|
||||||
{
|
{
|
||||||
LoadingController.CompleteRequest(null);
|
LoadingController.CompleteRequest(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TableSource : UITableViewSource
|
|
||||||
{
|
|
||||||
private LockPasswordViewController _controller;
|
|
||||||
|
|
||||||
public TableSource(LockPasswordViewController controller)
|
|
||||||
{
|
|
||||||
_controller = controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
|
|
||||||
{
|
|
||||||
if(indexPath.Section == 0)
|
|
||||||
{
|
|
||||||
if(indexPath.Row == 0)
|
|
||||||
{
|
|
||||||
return _controller.MasterPasswordCell;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new UITableViewCell();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
|
|
||||||
{
|
|
||||||
return UITableView.AutomaticDimension;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override nint NumberOfSections(UITableView tableView)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override nint RowsInSection(UITableView tableview, nint section)
|
|
||||||
{
|
|
||||||
if(section == 0)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override nfloat GetHeightForHeader(UITableView tableView, nint section)
|
|
||||||
{
|
|
||||||
return UITableView.AutomaticDimension;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string TitleForHeader(UITableView tableView, nint section)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
|
|
||||||
{
|
|
||||||
tableView.DeselectRow(indexPath, true);
|
|
||||||
tableView.EndEditing(true);
|
|
||||||
|
|
||||||
var cell = tableView.CellAt(indexPath);
|
|
||||||
if(cell == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var selectableCell = cell as ISelectable;
|
|
||||||
if(selectableCell != null)
|
|
||||||
{
|
|
||||||
selectableCell.Select();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,102 +1,20 @@
|
||||||
using System;
|
using System;
|
||||||
using Bit.iOS.Extension.Models;
|
|
||||||
using UIKit;
|
using UIKit;
|
||||||
using XLabs.Ioc;
|
|
||||||
using Plugin.Settings.Abstractions;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using Bit.iOS.Core.Utilities;
|
|
||||||
using Bit.App.Resources;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using Bit.App;
|
|
||||||
using Bit.iOS.Core.Controllers;
|
|
||||||
|
|
||||||
namespace Bit.iOS.Extension
|
namespace Bit.iOS.Extension
|
||||||
{
|
{
|
||||||
public partial class LockPinViewController : ExtendedUIViewController
|
public partial class LockPinViewController : Core.Controllers.LockPinViewController
|
||||||
{
|
{
|
||||||
private IAppSettingsService _appSettingsService;
|
|
||||||
private IAuthService _authService;
|
|
||||||
|
|
||||||
public LockPinViewController(IntPtr handle) : base(handle)
|
public LockPinViewController(IntPtr handle) : base(handle)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public Context Context { get; set; }
|
|
||||||
public LoadingViewController LoadingController { get; set; }
|
public LoadingViewController LoadingController { get; set; }
|
||||||
|
public override UINavigationItem BaseNavItem => NavItem;
|
||||||
public override void ViewWillAppear(bool animated)
|
public override UIBarButtonItem BaseCancelButton => CancelButton;
|
||||||
{
|
public override UILabel BasePinLabel => PinLabel;
|
||||||
UINavigationBar.Appearance.ShadowImage = new UIImage();
|
public override UILabel BaseInstructionLabel => InstructionLabel;
|
||||||
UINavigationBar.Appearance.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
|
public override UITextField BasePinTextField => PinTextField;
|
||||||
base.ViewWillAppear(animated);
|
public override Action Success => () => LoadingController.DismissLockAndContinue();
|
||||||
}
|
|
||||||
|
|
||||||
public override void ViewDidLoad()
|
|
||||||
{
|
|
||||||
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
|
|
||||||
_authService = Resolver.Resolve<IAuthService>();
|
|
||||||
|
|
||||||
NavItem.Title = AppResources.VerifyPIN;
|
|
||||||
CancelButton.Title = AppResources.Cancel;
|
|
||||||
View.BackgroundColor = new UIColor(red: 0.94f, green: 0.94f, blue: 0.96f, alpha: 1.0f);
|
|
||||||
|
|
||||||
var descriptor = UIFontDescriptor.PreferredBody;
|
|
||||||
PinLabel.Font = UIFont.FromName("Menlo-Regular", 35);
|
|
||||||
|
|
||||||
InstructionLabel.Text = AppResources.EnterPIN;
|
|
||||||
InstructionLabel.LineBreakMode = UILineBreakMode.WordWrap;
|
|
||||||
InstructionLabel.Lines = 0;
|
|
||||||
InstructionLabel.Font = UIFont.FromDescriptor(descriptor, descriptor.PointSize * 0.8f);
|
|
||||||
InstructionLabel.TextColor = new UIColor(red: 0.47f, green: 0.47f, blue: 0.47f, alpha: 1.0f);
|
|
||||||
|
|
||||||
PinTextField.EditingChanged += PinTextField_EditingChanged;
|
|
||||||
|
|
||||||
base.ViewDidLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ViewDidAppear(bool animated)
|
|
||||||
{
|
|
||||||
base.ViewDidAppear(animated);
|
|
||||||
PinTextField.BecomeFirstResponder();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PinTextField_EditingChanged(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
SetLabelText();
|
|
||||||
|
|
||||||
if(PinTextField.Text.Length >= 4)
|
|
||||||
{
|
|
||||||
if(PinTextField.Text == _authService.PIN)
|
|
||||||
{
|
|
||||||
Debug.WriteLine("BW Log, Start Dismiss PIN controller.");
|
|
||||||
_appSettingsService.Locked = false;
|
|
||||||
PinTextField.ResignFirstResponder();
|
|
||||||
LoadingController.DismissLockAndContinue();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// TODO: keep track of invalid attempts and logout?
|
|
||||||
|
|
||||||
var alert = Dialogs.CreateAlert(null, AppResources.InvalidPIN, AppResources.Ok, (a) =>
|
|
||||||
{
|
|
||||||
PinTextField.Text = string.Empty;
|
|
||||||
SetLabelText();
|
|
||||||
PinTextField.BecomeFirstResponder();
|
|
||||||
});
|
|
||||||
PresentViewController(alert, true, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetLabelText()
|
|
||||||
{
|
|
||||||
var newText = string.Empty;
|
|
||||||
for(int i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
newText += PinTextField.Text.Length <= i ? "- " : "• ";
|
|
||||||
}
|
|
||||||
|
|
||||||
PinLabel.Text = newText.TrimEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void CancelButton_Activated(UIBarButtonItem sender)
|
partial void CancelButton_Activated(UIBarButtonItem sender)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,118 +1,33 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using Bit.App.Models;
|
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.iOS.Core.Views;
|
|
||||||
using Bit.iOS.Extension.Models;
|
|
||||||
using Foundation;
|
using Foundation;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
using XLabs.Ioc;
|
|
||||||
using Bit.App;
|
|
||||||
using Plugin.Connectivity.Abstractions;
|
|
||||||
using Bit.iOS.Core.Utilities;
|
|
||||||
using Bit.iOS.Core.Controllers;
|
|
||||||
|
|
||||||
namespace Bit.iOS.Extension
|
namespace Bit.iOS.Extension
|
||||||
{
|
{
|
||||||
public partial class LoginAddViewController : ExtendedUITableViewController
|
public partial class LoginAddViewController : Core.Controllers.LoginAddViewController
|
||||||
{
|
{
|
||||||
private ICipherService _cipherService;
|
|
||||||
private IFolderService _folderService;
|
|
||||||
private IConnectivity _connectivity;
|
|
||||||
private IEnumerable<Folder> _folders;
|
|
||||||
private IGoogleAnalyticsService _googleAnalyticsService;
|
|
||||||
|
|
||||||
public LoginAddViewController(IntPtr handle) : base(handle)
|
public LoginAddViewController(IntPtr handle) : base(handle)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public Context Context { get; set; }
|
|
||||||
public LoginListViewController LoginListController { get; set; }
|
public LoginListViewController LoginListController { get; set; }
|
||||||
public LoadingViewController LoadingController { get; set; }
|
public LoadingViewController LoadingController { get; set; }
|
||||||
public FormEntryTableViewCell NameCell { get; set; } = new FormEntryTableViewCell(AppResources.Name);
|
|
||||||
public FormEntryTableViewCell UsernameCell { get; set; } = new FormEntryTableViewCell(AppResources.Username);
|
|
||||||
public FormEntryTableViewCell PasswordCell { get; set; } = new FormEntryTableViewCell(AppResources.Password);
|
|
||||||
public UITableViewCell GeneratePasswordCell { get; set; } = new UITableViewCell(UITableViewCellStyle.Subtitle, "GeneratePasswordCell");
|
|
||||||
public FormEntryTableViewCell UriCell { get; set; } = new FormEntryTableViewCell(AppResources.URI);
|
|
||||||
public SwitchTableViewCell FavoriteCell { get; set; } = new SwitchTableViewCell(AppResources.Favorite);
|
|
||||||
public FormEntryTableViewCell NotesCell { get; set; } = new FormEntryTableViewCell(useTextView: true, height: 180);
|
|
||||||
public PickerTableViewCell FolderCell { get; set; } = new PickerTableViewCell(AppResources.Folder);
|
|
||||||
|
|
||||||
public override void ViewWillAppear(bool animated)
|
public override UINavigationItem BaseNavItem => NavItem;
|
||||||
|
public override UIBarButtonItem BaseCancelButton => CancelBarButton;
|
||||||
|
public override UIBarButtonItem BaseSaveButton => SaveBarButton;
|
||||||
|
|
||||||
|
public override Action Success => () =>
|
||||||
{
|
{
|
||||||
UINavigationBar.Appearance.ShadowImage = new UIImage();
|
if(LoginListController != null)
|
||||||
UINavigationBar.Appearance.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
|
{
|
||||||
base.ViewWillAppear(animated);
|
LoginListController.DismissModal();
|
||||||
}
|
}
|
||||||
|
else if(LoadingController != null)
|
||||||
public override void ViewDidLoad()
|
|
||||||
{
|
{
|
||||||
_cipherService = Resolver.Resolve<ICipherService>();
|
LoadingController.CompleteUsernamePasswordRequest(UsernameCell.TextField.Text,
|
||||||
_connectivity = Resolver.Resolve<IConnectivity>();
|
PasswordCell.TextField.Text, null, null);
|
||||||
_folderService = Resolver.Resolve<IFolderService>();
|
|
||||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
|
||||||
|
|
||||||
NavItem.Title = AppResources.AddItem;
|
|
||||||
CancelBarButton.Title = AppResources.Cancel;
|
|
||||||
SaveBarButton.Title = AppResources.Save;
|
|
||||||
View.BackgroundColor = new UIColor(red: 0.94f, green: 0.94f, blue: 0.96f, alpha: 1.0f);
|
|
||||||
|
|
||||||
NameCell.TextField.Text = Context?.Uri?.Host ?? string.Empty;
|
|
||||||
NameCell.TextField.ReturnKeyType = UIReturnKeyType.Next;
|
|
||||||
NameCell.TextField.ShouldReturn += (UITextField tf) =>
|
|
||||||
{
|
|
||||||
UsernameCell.TextField.BecomeFirstResponder();
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
UsernameCell.TextField.AutocapitalizationType = UITextAutocapitalizationType.None;
|
|
||||||
UsernameCell.TextField.AutocorrectionType = UITextAutocorrectionType.No;
|
|
||||||
UsernameCell.TextField.SpellCheckingType = UITextSpellCheckingType.No;
|
|
||||||
UsernameCell.TextField.ReturnKeyType = UIReturnKeyType.Next;
|
|
||||||
UsernameCell.TextField.ShouldReturn += (UITextField tf) =>
|
|
||||||
{
|
|
||||||
PasswordCell.TextField.BecomeFirstResponder();
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
PasswordCell.TextField.SecureTextEntry = true;
|
|
||||||
PasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Next;
|
|
||||||
PasswordCell.TextField.ShouldReturn += (UITextField tf) =>
|
|
||||||
{
|
|
||||||
UriCell.TextField.BecomeFirstResponder();
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
GeneratePasswordCell.TextLabel.Text = AppResources.GeneratePassword;
|
|
||||||
GeneratePasswordCell.Accessory = UITableViewCellAccessory.DisclosureIndicator;
|
|
||||||
|
|
||||||
UriCell.TextField.Text = Context?.UrlString ?? string.Empty;
|
|
||||||
UriCell.TextField.KeyboardType = UIKeyboardType.Url;
|
|
||||||
UriCell.TextField.ReturnKeyType = UIReturnKeyType.Next;
|
|
||||||
UriCell.TextField.ShouldReturn += (UITextField tf) =>
|
|
||||||
{
|
|
||||||
NotesCell.TextView.BecomeFirstResponder();
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
_folders = _folderService.GetAllAsync().GetAwaiter().GetResult();
|
|
||||||
var folderNames = _folders.Select(s => s.Name.Decrypt()).OrderBy(s => s).ToList();
|
|
||||||
folderNames.Insert(0, AppResources.FolderNone);
|
|
||||||
FolderCell.Items = folderNames;
|
|
||||||
|
|
||||||
TableView.RowHeight = UITableView.AutomaticDimension;
|
|
||||||
TableView.EstimatedRowHeight = 70;
|
|
||||||
TableView.Source = new TableSource(this);
|
|
||||||
TableView.AllowsSelection = true;
|
|
||||||
|
|
||||||
base.ViewDidLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ViewDidAppear(bool animated)
|
|
||||||
{
|
|
||||||
base.ViewDidAppear(animated);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
partial void CancelBarButton_Activated(UIBarButtonItem sender)
|
partial void CancelBarButton_Activated(UIBarButtonItem sender)
|
||||||
{
|
{
|
||||||
|
@ -128,77 +43,7 @@ namespace Bit.iOS.Extension
|
||||||
|
|
||||||
async partial void SaveBarButton_Activated(UIBarButtonItem sender)
|
async partial void SaveBarButton_Activated(UIBarButtonItem sender)
|
||||||
{
|
{
|
||||||
if(!_connectivity.IsConnected)
|
await this.SaveAsync();
|
||||||
{
|
|
||||||
AlertNoConnection();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(string.IsNullOrWhiteSpace(PasswordCell.TextField.Text))
|
|
||||||
{
|
|
||||||
DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired, AppResources.Password), AppResources.Ok);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(string.IsNullOrWhiteSpace(NameCell.TextField.Text))
|
|
||||||
{
|
|
||||||
DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired, AppResources.Name), AppResources.Ok);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cipher = new Cipher
|
|
||||||
{
|
|
||||||
Name = string.IsNullOrWhiteSpace(NameCell.TextField.Text) ? null : NameCell.TextField.Text.Encrypt(),
|
|
||||||
Notes = string.IsNullOrWhiteSpace(NotesCell.TextView.Text) ? null : NotesCell.TextView.Text.Encrypt(),
|
|
||||||
Favorite = FavoriteCell.Switch.On,
|
|
||||||
FolderId = FolderCell.SelectedIndex == 0 ? null : _folders.ElementAtOrDefault(FolderCell.SelectedIndex - 1)?.Id,
|
|
||||||
Type = App.Enums.CipherType.Login,
|
|
||||||
Login = new Login
|
|
||||||
{
|
|
||||||
Uris = null,
|
|
||||||
Username = string.IsNullOrWhiteSpace(UsernameCell.TextField.Text) ? null : UsernameCell.TextField.Text.Encrypt(),
|
|
||||||
Password = string.IsNullOrWhiteSpace(PasswordCell.TextField.Text) ? null : PasswordCell.TextField.Text.Encrypt()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if(!string.IsNullOrWhiteSpace(UriCell.TextField.Text))
|
|
||||||
{
|
|
||||||
cipher.Login.Uris = new List<LoginUri>
|
|
||||||
{
|
|
||||||
new LoginUri
|
|
||||||
{
|
|
||||||
Uri = UriCell.TextField.Text.Encrypt()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var saveTask = _cipherService.SaveAsync(cipher);
|
|
||||||
var loadingAlert = Dialogs.CreateLoadingAlert(AppResources.Saving);
|
|
||||||
PresentViewController(loadingAlert, true, null);
|
|
||||||
await saveTask;
|
|
||||||
|
|
||||||
await loadingAlert.DismissViewControllerAsync(true);
|
|
||||||
if(saveTask.Result.Succeeded)
|
|
||||||
{
|
|
||||||
_googleAnalyticsService.TrackExtensionEvent("CreatedLogin");
|
|
||||||
if(LoginListController != null)
|
|
||||||
{
|
|
||||||
LoginListController.DismissModal();
|
|
||||||
}
|
|
||||||
else if(LoadingController != null)
|
|
||||||
{
|
|
||||||
LoadingController.CompleteUsernamePasswordRequest(UsernameCell.TextField.Text, PasswordCell.TextField.Text,
|
|
||||||
null, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(saveTask.Result.Errors.Count() > 0)
|
|
||||||
{
|
|
||||||
DisplayAlert(AppResources.AnErrorHasOccurred, saveTask.Result.Errors.First().Message, AppResources.Ok);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DisplayAlert(null, AppResources.AnErrorHasOccurred, AppResources.Ok);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
|
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
|
||||||
|
@ -209,147 +54,10 @@ namespace Bit.iOS.Extension
|
||||||
var passwordGeneratorController = navController.TopViewController as PasswordGeneratorViewController;
|
var passwordGeneratorController = navController.TopViewController as PasswordGeneratorViewController;
|
||||||
if(passwordGeneratorController != null)
|
if(passwordGeneratorController != null)
|
||||||
{
|
{
|
||||||
passwordGeneratorController.Context = Context;
|
passwordGeneratorController.PasswordOptions = Context.PasswordOptions;
|
||||||
passwordGeneratorController.Parent = this;
|
passwordGeneratorController.Parent = this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DisplayAlert(string title, string message, string accept)
|
|
||||||
{
|
|
||||||
var alert = Dialogs.CreateAlert(title, message, accept);
|
|
||||||
PresentViewController(alert, true, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AlertNoConnection()
|
|
||||||
{
|
|
||||||
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage, AppResources.Ok);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TableSource : UITableViewSource
|
|
||||||
{
|
|
||||||
private LoginAddViewController _controller;
|
|
||||||
|
|
||||||
public TableSource(LoginAddViewController controller)
|
|
||||||
{
|
|
||||||
_controller = controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
|
|
||||||
{
|
|
||||||
if(indexPath.Section == 0)
|
|
||||||
{
|
|
||||||
if(indexPath.Row == 0)
|
|
||||||
{
|
|
||||||
return _controller.NameCell;
|
|
||||||
}
|
|
||||||
else if(indexPath.Row == 1)
|
|
||||||
{
|
|
||||||
return _controller.UsernameCell;
|
|
||||||
}
|
|
||||||
else if(indexPath.Row == 2)
|
|
||||||
{
|
|
||||||
return _controller.PasswordCell;
|
|
||||||
}
|
|
||||||
else if(indexPath.Row == 3)
|
|
||||||
{
|
|
||||||
return _controller.GeneratePasswordCell;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(indexPath.Section == 1)
|
|
||||||
{
|
|
||||||
return _controller.UriCell;
|
|
||||||
}
|
|
||||||
else if(indexPath.Section == 2)
|
|
||||||
{
|
|
||||||
if(indexPath.Row == 0)
|
|
||||||
{
|
|
||||||
return _controller.FolderCell;
|
|
||||||
}
|
|
||||||
else if(indexPath.Row == 1)
|
|
||||||
{
|
|
||||||
return _controller.FavoriteCell;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(indexPath.Section == 3)
|
|
||||||
{
|
|
||||||
return _controller.NotesCell;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new UITableViewCell();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
|
|
||||||
{
|
|
||||||
return UITableView.AutomaticDimension;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override nint NumberOfSections(UITableView tableView)
|
|
||||||
{
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override nint RowsInSection(UITableView tableview, nint section)
|
|
||||||
{
|
|
||||||
if(section == 0)
|
|
||||||
{
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
else if(section == 1)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else if(section == 2)
|
|
||||||
{
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override nfloat GetHeightForHeader(UITableView tableView, nint section)
|
|
||||||
{
|
|
||||||
return section == 0 || section == 3 ? UITableView.AutomaticDimension : 0.00001f;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string TitleForHeader(UITableView tableView, nint section)
|
|
||||||
{
|
|
||||||
if(section == 0)
|
|
||||||
{
|
|
||||||
return AppResources.ItemInformation;
|
|
||||||
}
|
|
||||||
else if(section == 3)
|
|
||||||
{
|
|
||||||
return AppResources.Notes;
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
|
|
||||||
{
|
|
||||||
tableView.DeselectRow(indexPath, true);
|
|
||||||
tableView.EndEditing(true);
|
|
||||||
|
|
||||||
if(indexPath.Section == 0 && indexPath.Row == 3)
|
|
||||||
{
|
|
||||||
_controller.PerformSegue("passwordGeneratorSegue", this);
|
|
||||||
}
|
|
||||||
|
|
||||||
var cell = tableView.CellAt(indexPath);
|
|
||||||
if(cell == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var selectableCell = cell as ISelectable;
|
|
||||||
if(selectableCell != null)
|
|
||||||
{
|
|
||||||
selectableCell.Select();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,57 +1,19 @@
|
||||||
using System;
|
using Foundation;
|
||||||
using Foundation;
|
using Bit.iOS.Core.Models;
|
||||||
using Bit.App;
|
|
||||||
|
|
||||||
namespace Bit.iOS.Extension.Models
|
namespace Bit.iOS.Extension.Models
|
||||||
{
|
{
|
||||||
public class Context
|
public class Context : AppExtensionContext
|
||||||
{
|
{
|
||||||
private string _uriString;
|
private string _uriString;
|
||||||
|
|
||||||
public NSExtensionContext ExtContext { get; set; }
|
public NSExtensionContext ExtContext { get; set; }
|
||||||
public string ProviderType { get; set; }
|
public string ProviderType { get; set; }
|
||||||
public Uri Uri
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
Uri uri;
|
|
||||||
if(string.IsNullOrWhiteSpace(UrlString) || !Uri.TryCreate(UrlString, UriKind.Absolute, out uri))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public string UrlString
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _uriString;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_uriString = value;
|
|
||||||
if(_uriString != null && !_uriString.StartsWith(Constants.iOSAppProtocol) && _uriString.Contains("."))
|
|
||||||
{
|
|
||||||
if(!_uriString.Contains("://") && !_uriString.Contains(" "))
|
|
||||||
{
|
|
||||||
_uriString = string.Concat("http://", _uriString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!_uriString.StartsWith("http") && !_uriString.StartsWith(Constants.iOSAppProtocol))
|
|
||||||
{
|
|
||||||
_uriString = string.Concat(Constants.iOSAppProtocol, _uriString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public string LoginTitle { get; set; }
|
public string LoginTitle { get; set; }
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
public string OldPassword { get; set; }
|
public string OldPassword { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
public PasswordGenerationOptions PasswordOptions { get; set; }
|
|
||||||
public PageDetails Details { get; set; }
|
public PageDetails Details { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,171 +1,22 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using Bit.iOS.Core.Views;
|
|
||||||
using Bit.iOS.Extension.Models;
|
|
||||||
using Foundation;
|
|
||||||
using UIKit;
|
using UIKit;
|
||||||
using XLabs.Ioc;
|
|
||||||
using Plugin.Settings.Abstractions;
|
|
||||||
using CoreGraphics;
|
|
||||||
using Bit.App;
|
|
||||||
using Bit.iOS.Core.Utilities;
|
|
||||||
using Bit.iOS.Core.Controllers;
|
|
||||||
using Bit.App.Resources;
|
|
||||||
|
|
||||||
namespace Bit.iOS.Extension
|
namespace Bit.iOS.Extension
|
||||||
{
|
{
|
||||||
public partial class PasswordGeneratorViewController : ExtendedUIViewController
|
public partial class PasswordGeneratorViewController : Core.Controllers.PasswordGeneratorViewController
|
||||||
{
|
{
|
||||||
private IPasswordGenerationService _passwordGenerationService;
|
|
||||||
private ISettings _settings;
|
|
||||||
private IGoogleAnalyticsService _googleAnalyticsService;
|
|
||||||
|
|
||||||
public PasswordGeneratorViewController(IntPtr handle) : base(handle)
|
public PasswordGeneratorViewController(IntPtr handle) : base(handle)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public Context Context { get; set; }
|
|
||||||
public LoginAddViewController Parent { get; set; }
|
public LoginAddViewController Parent { get; set; }
|
||||||
public UITableViewController OptionsTableViewController { get; set; }
|
public override UINavigationItem BaseNavItem => NavItem;
|
||||||
public SwitchTableViewCell UppercaseCell { get; set; } = new SwitchTableViewCell("A-Z");
|
public override UIBarButtonItem BaseCancelButton => CancelBarButton;
|
||||||
public SwitchTableViewCell LowercaseCell { get; set; } = new SwitchTableViewCell("a-z");
|
public override UIBarButtonItem BaseSelectBarButton => SelectBarButton;
|
||||||
public SwitchTableViewCell NumbersCell { get; set; } = new SwitchTableViewCell("0-9");
|
public override UILabel BasePasswordLabel => PasswordLabel;
|
||||||
public SwitchTableViewCell SpecialCell { get; set; } = new SwitchTableViewCell("!@#$%^&*");
|
|
||||||
public StepperTableViewCell MinNumbersCell { get; set; } = new StepperTableViewCell(AppResources.MinNumbers, 1, 0, 5, 1);
|
|
||||||
public StepperTableViewCell MinSpecialCell { get; set; } = new StepperTableViewCell(AppResources.MinSpecial, 1, 0, 5, 1);
|
|
||||||
public SliderTableViewCell LengthCell { get; set; } = new SliderTableViewCell(AppResources.Length, 10, 5, 64);
|
|
||||||
|
|
||||||
public override void ViewWillAppear(bool animated)
|
|
||||||
{
|
|
||||||
UINavigationBar.Appearance.ShadowImage = new UIImage();
|
|
||||||
UINavigationBar.Appearance.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
|
|
||||||
base.ViewWillAppear(animated);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ViewDidLoad()
|
|
||||||
{
|
|
||||||
_passwordGenerationService = Resolver.Resolve<IPasswordGenerationService>();
|
|
||||||
_settings = Resolver.Resolve<ISettings>();
|
|
||||||
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
|
||||||
|
|
||||||
NavItem.Title = AppResources.PasswordGenerator;
|
|
||||||
CancelBarButton.Title = AppResources.Cancel;
|
|
||||||
SelectBarButton.Title = AppResources.Select;
|
|
||||||
View.BackgroundColor = new UIColor(red: 0.94f, green: 0.94f, blue: 0.96f, alpha: 1.0f);
|
|
||||||
|
|
||||||
var descriptor = UIFontDescriptor.PreferredBody;
|
|
||||||
PasswordLabel.Font = UIFont.FromName("Menlo-Regular", descriptor.PointSize * 1.3f);
|
|
||||||
PasswordLabel.LineBreakMode = UILineBreakMode.TailTruncation;
|
|
||||||
PasswordLabel.Lines = 1;
|
|
||||||
PasswordLabel.AdjustsFontSizeToFitWidth = false;
|
|
||||||
|
|
||||||
var controller = ChildViewControllers.LastOrDefault();
|
|
||||||
if(controller != null)
|
|
||||||
{
|
|
||||||
OptionsTableViewController = controller as UITableViewController;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(OptionsTableViewController != null)
|
|
||||||
{
|
|
||||||
OptionsTableViewController.TableView.RowHeight = UITableView.AutomaticDimension;
|
|
||||||
OptionsTableViewController.TableView.EstimatedRowHeight = 70;
|
|
||||||
OptionsTableViewController.TableView.Source = new TableSource(this);
|
|
||||||
OptionsTableViewController.TableView.AllowsSelection = true;
|
|
||||||
OptionsTableViewController.View.BackgroundColor = new UIColor(red: 0.94f, green: 0.94f, blue: 0.96f, alpha: 1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
UppercaseCell.Switch.On = _settings.GetValueOrDefault(Constants.PasswordGeneratorUppercase, true);
|
|
||||||
LowercaseCell.Switch.On = _settings.GetValueOrDefault(Constants.PasswordGeneratorLowercase, true);
|
|
||||||
SpecialCell.Switch.On = _settings.GetValueOrDefault(Constants.PasswordGeneratorSpecial, true);
|
|
||||||
NumbersCell.Switch.On = _settings.GetValueOrDefault(Constants.PasswordGeneratorNumbers, true);
|
|
||||||
MinNumbersCell.Value = _settings.GetValueOrDefault(Constants.PasswordGeneratorMinNumbers, 1);
|
|
||||||
MinSpecialCell.Value = _settings.GetValueOrDefault(Constants.PasswordGeneratorMinSpecial, 1);
|
|
||||||
LengthCell.Value = _settings.GetValueOrDefault(Constants.PasswordGeneratorLength, 10);
|
|
||||||
|
|
||||||
UppercaseCell.ValueChanged += Options_ValueChanged;
|
|
||||||
LowercaseCell.ValueChanged += Options_ValueChanged;
|
|
||||||
NumbersCell.ValueChanged += Options_ValueChanged;
|
|
||||||
SpecialCell.ValueChanged += Options_ValueChanged;
|
|
||||||
MinNumbersCell.ValueChanged += Options_ValueChanged;
|
|
||||||
MinSpecialCell.ValueChanged += Options_ValueChanged;
|
|
||||||
LengthCell.ValueChanged += Options_ValueChanged;
|
|
||||||
|
|
||||||
// Adjust based on context password options
|
|
||||||
if(Context.PasswordOptions != null)
|
|
||||||
{
|
|
||||||
if(Context.PasswordOptions.RequireDigits)
|
|
||||||
{
|
|
||||||
NumbersCell.Switch.On = true;
|
|
||||||
NumbersCell.Switch.Enabled = false;
|
|
||||||
|
|
||||||
if(MinNumbersCell.Value < 1)
|
|
||||||
{
|
|
||||||
MinNumbersCell.Value = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
MinNumbersCell.Stepper.MinimumValue = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Context.PasswordOptions.RequireSymbols)
|
|
||||||
{
|
|
||||||
SpecialCell.Switch.On = true;
|
|
||||||
SpecialCell.Switch.Enabled = false;
|
|
||||||
|
|
||||||
if(MinSpecialCell.Value < 1)
|
|
||||||
{
|
|
||||||
MinSpecialCell.Value = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
MinSpecialCell.Stepper.MinimumValue = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Context.PasswordOptions.MinLength < Context.PasswordOptions.MaxLength)
|
|
||||||
{
|
|
||||||
if(Context.PasswordOptions.MinLength > 0 && Context.PasswordOptions.MinLength > LengthCell.Slider.MinValue)
|
|
||||||
{
|
|
||||||
if(LengthCell.Value < Context.PasswordOptions.MinLength)
|
|
||||||
{
|
|
||||||
LengthCell.Slider.Value = Context.PasswordOptions.MinLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
LengthCell.Slider.MinValue = Context.PasswordOptions.MinLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Context.PasswordOptions.MaxLength > 5 && Context.PasswordOptions.MaxLength < LengthCell.Slider.MaxValue)
|
|
||||||
{
|
|
||||||
if(LengthCell.Value > Context.PasswordOptions.MaxLength)
|
|
||||||
{
|
|
||||||
LengthCell.Slider.Value = Context.PasswordOptions.MaxLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
LengthCell.Slider.MaxValue = Context.PasswordOptions.MaxLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GeneratePassword();
|
|
||||||
_googleAnalyticsService.TrackExtensionEvent("GeneratedPassword");
|
|
||||||
base.ViewDidLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Options_ValueChanged(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if(InvalidState())
|
|
||||||
{
|
|
||||||
LowercaseCell.Switch.On = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
GeneratePassword();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool InvalidState()
|
|
||||||
{
|
|
||||||
return !LowercaseCell.Switch.On && !UppercaseCell.Switch.On && !NumbersCell.Switch.On && !SpecialCell.Switch.On;
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void SelectBarButton_Activated(UIBarButtonItem sender)
|
partial void SelectBarButton_Activated(UIBarButtonItem sender)
|
||||||
{
|
{
|
||||||
_googleAnalyticsService.TrackExtensionEvent("SelectedGeneratedPassword");
|
GoogleAnalyticsService.TrackExtensionEvent("SelectedGeneratedPassword");
|
||||||
DismissViewController(true, () =>
|
DismissViewController(true, () =>
|
||||||
{
|
{
|
||||||
Parent.PasswordCell.TextField.Text = PasswordLabel.Text;
|
Parent.PasswordCell.TextField.Text = PasswordLabel.Text;
|
||||||
|
@ -176,173 +27,5 @@ namespace Bit.iOS.Extension
|
||||||
{
|
{
|
||||||
DismissViewController(true, null);
|
DismissViewController(true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GeneratePassword()
|
|
||||||
{
|
|
||||||
PasswordLabel.Text = _passwordGenerationService.GeneratePassword(
|
|
||||||
length: LengthCell.Value,
|
|
||||||
uppercase: UppercaseCell.Switch.On,
|
|
||||||
lowercase: LowercaseCell.Switch.On,
|
|
||||||
numbers: NumbersCell.Switch.On,
|
|
||||||
special: SpecialCell.Switch.On,
|
|
||||||
minSpecial: MinSpecialCell.Value,
|
|
||||||
minNumbers: MinNumbersCell.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TableSource : UITableViewSource
|
|
||||||
{
|
|
||||||
private PasswordGeneratorViewController _controller;
|
|
||||||
|
|
||||||
public TableSource(PasswordGeneratorViewController controller)
|
|
||||||
{
|
|
||||||
_controller = controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
|
|
||||||
{
|
|
||||||
if(indexPath.Section == 0)
|
|
||||||
{
|
|
||||||
var cell = new UITableViewCell();
|
|
||||||
cell.TextLabel.TextColor = new UIColor(red: 0.24f, green: 0.55f, blue: 0.74f, alpha: 1.0f);
|
|
||||||
if(indexPath.Row == 0)
|
|
||||||
{
|
|
||||||
cell.TextLabel.Text = AppResources.RegeneratePassword;
|
|
||||||
}
|
|
||||||
else if(indexPath.Row == 1)
|
|
||||||
{
|
|
||||||
cell.TextLabel.Text = AppResources.CopyPassword;
|
|
||||||
}
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(indexPath.Row == 0)
|
|
||||||
{
|
|
||||||
return _controller.LengthCell;
|
|
||||||
}
|
|
||||||
else if(indexPath.Row == 1)
|
|
||||||
{
|
|
||||||
return _controller.UppercaseCell;
|
|
||||||
}
|
|
||||||
else if(indexPath.Row == 2)
|
|
||||||
{
|
|
||||||
return _controller.LowercaseCell;
|
|
||||||
}
|
|
||||||
else if(indexPath.Row == 3)
|
|
||||||
{
|
|
||||||
return _controller.NumbersCell;
|
|
||||||
}
|
|
||||||
else if(indexPath.Row == 4)
|
|
||||||
{
|
|
||||||
return _controller.SpecialCell;
|
|
||||||
}
|
|
||||||
else if(indexPath.Row == 5)
|
|
||||||
{
|
|
||||||
return _controller.MinNumbersCell;
|
|
||||||
}
|
|
||||||
else if(indexPath.Row == 6)
|
|
||||||
{
|
|
||||||
return _controller.MinSpecialCell;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new UITableViewCell();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
|
|
||||||
{
|
|
||||||
return UITableView.AutomaticDimension;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override nint NumberOfSections(UITableView tableView)
|
|
||||||
{
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override nint RowsInSection(UITableView tableview, nint section)
|
|
||||||
{
|
|
||||||
if(section == 0)
|
|
||||||
{
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override nfloat GetHeightForHeader(UITableView tableView, nint section)
|
|
||||||
{
|
|
||||||
if(section == 0)
|
|
||||||
{
|
|
||||||
return 0.00001f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return UITableView.AutomaticDimension;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override UIView GetViewForHeader(UITableView tableView, nint section)
|
|
||||||
{
|
|
||||||
if(section == 0)
|
|
||||||
{
|
|
||||||
return new UIView(CGRect.Empty)
|
|
||||||
{
|
|
||||||
Hidden = true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string TitleForHeader(UITableView tableView, nint section)
|
|
||||||
{
|
|
||||||
if(section == 1)
|
|
||||||
{
|
|
||||||
return AppResources.Options;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string TitleForFooter(UITableView tableView, nint section)
|
|
||||||
{
|
|
||||||
if(section == 1)
|
|
||||||
{
|
|
||||||
return AppResources.OptionDefaults;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
|
|
||||||
{
|
|
||||||
if(indexPath.Section == 0)
|
|
||||||
{
|
|
||||||
if(indexPath.Row == 0)
|
|
||||||
{
|
|
||||||
_controller._googleAnalyticsService.TrackExtensionEvent("RegeneratedPassword");
|
|
||||||
_controller.GeneratePassword();
|
|
||||||
}
|
|
||||||
else if(indexPath.Row == 1)
|
|
||||||
{
|
|
||||||
_controller._googleAnalyticsService.TrackExtensionEvent("CopiedGeneratedPassword");
|
|
||||||
UIPasteboard clipboard = UIPasteboard.General;
|
|
||||||
clipboard.String = _controller.PasswordLabel.Text;
|
|
||||||
var alert = Dialogs.CreateMessageAlert(AppResources.Copied);
|
|
||||||
_controller.PresentViewController(alert, true, () =>
|
|
||||||
{
|
|
||||||
_controller.DismissViewController(true, null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tableView.DeselectRow(indexPath, true);
|
|
||||||
tableView.EndEditing(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public NSDate DateTimeToNSDate(DateTime date)
|
|
||||||
{
|
|
||||||
DateTime reference = TimeZone.CurrentTimeZone.ToLocalTime(
|
|
||||||
new DateTime(2001, 1, 1, 0, 0, 0));
|
|
||||||
return NSDate.FromTimeIntervalSinceReferenceDate(
|
|
||||||
(date - reference).TotalSeconds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -232,7 +232,6 @@
|
||||||
<Compile Include="Models\Context.cs" />
|
<Compile Include="Models\Context.cs" />
|
||||||
<Compile Include="Models\FillScript.cs" />
|
<Compile Include="Models\FillScript.cs" />
|
||||||
<Compile Include="Models\PageDetails.cs" />
|
<Compile Include="Models\PageDetails.cs" />
|
||||||
<Compile Include="Models\PasswordGenerationOptions.cs" />
|
|
||||||
<Compile Include="LoadingViewController.cs" />
|
<Compile Include="LoadingViewController.cs" />
|
||||||
<Compile Include="LoadingViewController.designer.cs">
|
<Compile Include="LoadingViewController.designer.cs">
|
||||||
<DependentUpon>LoadingViewController.cs</DependentUpon>
|
<DependentUpon>LoadingViewController.cs</DependentUpon>
|
||||||
|
|
Loading…
Reference in a new issue