mirror of
https://github.com/bitwarden/mobile
synced 2025-12-23 03:33:59 +00:00
reset for v2
This commit is contained in:
@@ -1,32 +0,0 @@
|
||||
namespace Bit.iOS.Core
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public const string AppExtensionVersionNumberKey = "version_number";
|
||||
public const string AppExtensionUrlStringKey = "url_string";
|
||||
public const string AppExtensionUsernameKey = "username";
|
||||
public const string AppExtensionPasswordKey = "password";
|
||||
public const string AppExtensionTotpKey = "totp";
|
||||
public const string AppExtensionTitleKey = "login_title";
|
||||
public const string AppExtensionNotesKey = "notes";
|
||||
public const string AppExtensionSectionTitleKey = "section_title";
|
||||
public const string AppExtensionFieldsKey = "fields";
|
||||
public const string AppExtensionReturnedFieldsKey = "returned_fields";
|
||||
public const string AppExtensionOldPasswordKey = "old_password";
|
||||
public const string AppExtensionPasswordGeneratorOptionsKey = "password_generator_options";
|
||||
public const string AppExtensionGeneratedPasswordMinLengthKey = "password_min_length";
|
||||
public const string AppExtensionGeneratedPasswordMaxLengthKey = "password_max_length";
|
||||
public const string AppExtensionGeneratedPasswordRequireDigitsKey = "password_require_digits";
|
||||
public const string AppExtensionGeneratedPasswordRequireSymbolsKey = "password_require_symbols";
|
||||
public const string AppExtensionGeneratedPasswordForbiddenCharactersKey = "password_forbidden_characters";
|
||||
public const string AppExtensionWebViewPageFillScript = "fillScript";
|
||||
public const string AppExtensionWebViewPageDetails = "pageDetails";
|
||||
|
||||
public const string UTTypeAppExtensionFindLoginAction = "org.appextension.find-login-action";
|
||||
public const string UTTypeAppExtensionSaveLoginAction = "org.appextension.save-login-action";
|
||||
public const string UTTypeAppExtensionChangePasswordAction = "org.appextension.change-password-action";
|
||||
public const string UTTypeAppExtensionFillWebViewAction = "org.appextension.fill-webview-action";
|
||||
public const string UTTypeAppExtensionFillBrowserAction = "org.appextension.fill-browser-action";
|
||||
public const string UTTypeAppExtensionSetup = "com.8bit.bitwarden.extension-setup";
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Bit.App.Abstractions;
|
||||
using System;
|
||||
using UIKit;
|
||||
using XLabs.Ioc;
|
||||
|
||||
namespace Bit.iOS.Core.Controllers
|
||||
{
|
||||
public class ExtendedUITableViewController : UITableViewController
|
||||
{
|
||||
public ExtendedUITableViewController(IntPtr handle)
|
||||
: base(handle)
|
||||
{ }
|
||||
|
||||
public override void ViewDidAppear(bool animated)
|
||||
{
|
||||
var googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
googleAnalyticsService.TrackPage(GetType().Name);
|
||||
base.ViewDidAppear(animated);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using System;
|
||||
using UIKit;
|
||||
using Bit.App.Abstractions;
|
||||
using XLabs.Ioc;
|
||||
|
||||
namespace Bit.iOS.Core.Controllers
|
||||
{
|
||||
public class ExtendedUIViewController : UIViewController
|
||||
{
|
||||
public ExtendedUIViewController(IntPtr handle)
|
||||
: base(handle)
|
||||
{ }
|
||||
|
||||
public override void ViewDidAppear(bool animated)
|
||||
{
|
||||
var googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
|
||||
googleAnalyticsService.TrackPage(GetType().Name);
|
||||
base.ViewDidAppear(animated);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,338 +0,0 @@
|
||||
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;
|
||||
using AuthenticationServices;
|
||||
|
||||
namespace Bit.iOS.Core.Controllers
|
||||
{
|
||||
public abstract class LoginAddViewController : ExtendedUITableViewController
|
||||
{
|
||||
private ICipherService _cipherService;
|
||||
private IFolderService _folderService;
|
||||
private IConnectivity _connectivity;
|
||||
private IEnumerable<Folder> _folders;
|
||||
protected 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)
|
||||
{
|
||||
if (await ASHelpers.IdentitiesCanIncremental())
|
||||
{
|
||||
var identity = await ASHelpers.GetCipherIdentityAsync(saveTask.Result.Result.Id, _cipherService);
|
||||
if (identity != null)
|
||||
{
|
||||
await ASCredentialIdentityStore.SharedStore.SaveCredentialIdentitiesAsync(
|
||||
new ASPasswordCredentialIdentity[] { identity });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ASHelpers.ReplaceAllIdentities(_cipherService);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,360 +0,0 @@
|
||||
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;
|
||||
private bool _isAutofill;
|
||||
|
||||
public PasswordGeneratorViewController(IntPtr handle, bool autofill) : base(handle)
|
||||
{
|
||||
_isAutofill = autofill;
|
||||
}
|
||||
|
||||
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();
|
||||
if(_isAutofill)
|
||||
{
|
||||
GoogleAnalyticsService.TrackAutofillExtensionEvent("GeneratedPassword");
|
||||
}
|
||||
else
|
||||
{
|
||||
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)
|
||||
{
|
||||
if(_controller._isAutofill)
|
||||
{
|
||||
_controller.GoogleAnalyticsService.TrackAutofillExtensionEvent("RegeneratedPassword");
|
||||
}
|
||||
else
|
||||
{
|
||||
_controller.GoogleAnalyticsService.TrackExtensionEvent("RegeneratedPassword");
|
||||
}
|
||||
_controller.GeneratePassword();
|
||||
}
|
||||
else if(indexPath.Row == 1)
|
||||
{
|
||||
if(_controller._isAutofill)
|
||||
{
|
||||
_controller.GoogleAnalyticsService.TrackAutofillExtensionEvent("CopiedGeneratedPassword");
|
||||
}
|
||||
else
|
||||
{
|
||||
_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using Bit.App.Abstractions;
|
||||
using HockeyApp.iOS;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Bit.iOS.Core
|
||||
{
|
||||
public class HockeyAppCrashManagerDelegate : BITCrashManagerDelegate
|
||||
{
|
||||
private readonly IAppIdService _appIdService;
|
||||
private readonly IAuthService _authService;
|
||||
|
||||
public HockeyAppCrashManagerDelegate(
|
||||
IAppIdService appIdService,
|
||||
IAuthService authService)
|
||||
{
|
||||
_appIdService = appIdService;
|
||||
_authService = authService;
|
||||
}
|
||||
|
||||
public override string ApplicationLogForCrashManager(BITCrashManager crashManager)
|
||||
{
|
||||
var log = new
|
||||
{
|
||||
AppId = _appIdService.AppId,
|
||||
UserId = _authService.UserId
|
||||
};
|
||||
|
||||
return JsonConvert.SerializeObject(log, Formatting.Indented);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
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(string.IsNullOrWhiteSpace(_uriString))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!_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,57 +0,0 @@
|
||||
using Bit.App.Enums;
|
||||
using Bit.App.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.iOS.Core.Models
|
||||
{
|
||||
public class CipherViewModel
|
||||
{
|
||||
public CipherViewModel(Cipher cipher)
|
||||
{
|
||||
Id = cipher.Id;
|
||||
Name = cipher.Name?.Decrypt(cipher.OrganizationId);
|
||||
Username = cipher.Login?.Username?.Decrypt(cipher.OrganizationId);
|
||||
Password = cipher.Login?.Password?.Decrypt(cipher.OrganizationId);
|
||||
Uris = cipher.Login?.Uris?.Select(u => new LoginUriModel(u, cipher.OrganizationId));
|
||||
Totp = new Lazy<string>(() => cipher.Login?.Totp?.Decrypt(cipher.OrganizationId));
|
||||
Fields = new Lazy<List<Tuple<string, string>>>(() =>
|
||||
{
|
||||
if(!cipher.Fields?.Any() ?? true)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var fields = new List<Tuple<string, string>>();
|
||||
foreach(var field in cipher.Fields)
|
||||
{
|
||||
fields.Add(new Tuple<string, string>(
|
||||
field.Name?.Decrypt(cipher.OrganizationId),
|
||||
field.Value?.Decrypt(cipher.OrganizationId)));
|
||||
}
|
||||
return fields;
|
||||
});
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public IEnumerable<LoginUriModel> Uris { get; set; }
|
||||
public Lazy<string> Totp { get; set; }
|
||||
public Lazy<List<Tuple<string, string>>> Fields { get; set; }
|
||||
|
||||
public class LoginUriModel
|
||||
{
|
||||
public LoginUriModel(LoginUri data, string orgId)
|
||||
{
|
||||
Uri = data?.Uri?.Decrypt(orgId);
|
||||
Match = data?.Match;
|
||||
}
|
||||
|
||||
public string Uri { get; set; }
|
||||
public UriMatchType? Match { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Bit.iOS.Core.Models
|
||||
{
|
||||
public class PasswordGenerationOptions
|
||||
{
|
||||
public int MinLength { get; set; }
|
||||
public int MaxLength { get; set; }
|
||||
public bool RequireDigits { get; set; }
|
||||
public bool RequireSymbols { get; set; }
|
||||
public string ForbiddenCharacters { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("BitwardeniOSCore")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("8bit Solutions LLC")]
|
||||
[assembly: AssemblyProduct("Bitwarden")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("b2538ada-b605-4d6f-acd2-62a409680f84")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Foundation;
|
||||
|
||||
namespace Bit.iOS.Core.Services
|
||||
{
|
||||
public class AppInfoService : IAppInfoService
|
||||
{
|
||||
public string Build => NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString();
|
||||
public string Version => NSBundle.MainBundle.InfoDictionary["CFBundleShortVersionString"].ToString();
|
||||
public bool AutofillAccessibilityServiceEnabled => false;
|
||||
public bool AutofillServiceEnabled => false;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using Bit.App.Abstractions;
|
||||
using Foundation;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Bit.iOS.Core.Services
|
||||
{
|
||||
public class CommonCryptoKeyDerivationService : IKeyDerivationService
|
||||
{
|
||||
private const uint PBKDFAlgorithm = 2; // PBKDF2
|
||||
private const uint PseudoRandomAlgorithm = 3; // SHA256
|
||||
private const uint KeyLength = 32; // 256 bit
|
||||
|
||||
public byte[] DeriveKey(byte[] password, byte[] salt, uint rounds)
|
||||
{
|
||||
var passwordData = NSData.FromArray(password);
|
||||
var saltData = NSData.FromArray(salt);
|
||||
|
||||
var keyData = new NSMutableData();
|
||||
keyData.Length = KeyLength;
|
||||
var result = CCKeyCerivationPBKDF(PBKDFAlgorithm, passwordData.Bytes, passwordData.Length, saltData.Bytes,
|
||||
saltData.Length, PseudoRandomAlgorithm, rounds, keyData.MutableBytes, keyData.Length);
|
||||
|
||||
byte[] keyBytes = new byte[keyData.Length];
|
||||
Marshal.Copy(keyData.Bytes, keyBytes, 0, Convert.ToInt32(keyData.Length));
|
||||
return keyBytes;
|
||||
}
|
||||
|
||||
// ref: http://opensource.apple.com//source/CommonCrypto/CommonCrypto-55010/CommonCrypto/CommonKeyDerivation.h
|
||||
[DllImport(ObjCRuntime.Constants.libSystemLibrary, EntryPoint = "CCKeyDerivationPBKDF")]
|
||||
private extern static int CCKeyCerivationPBKDF(uint algorithm, IntPtr password, nuint passwordLen,
|
||||
IntPtr salt, nuint saltLen, uint prf, nuint rounds, IntPtr derivedKey, nuint derivedKeyLength);
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using Bit.App.Abstractions;
|
||||
using Foundation;
|
||||
using LocalAuthentication;
|
||||
using UIKit;
|
||||
|
||||
namespace Bit.iOS.Core.Services
|
||||
{
|
||||
public class DeviceInfoService : IDeviceInfoService
|
||||
{
|
||||
public DeviceInfoService()
|
||||
: this(false)
|
||||
{ }
|
||||
|
||||
public DeviceInfoService(bool isExtension)
|
||||
{
|
||||
IsExtension = isExtension;
|
||||
}
|
||||
|
||||
public string Type => Xamarin.Forms.Device.iOS;
|
||||
public string Model => UIDevice.CurrentDevice.Model;
|
||||
public int Version
|
||||
{
|
||||
get
|
||||
{
|
||||
var versionParts = UIDevice.CurrentDevice.SystemVersion.Split('.');
|
||||
if(versionParts.Length > 0 && int.TryParse(versionParts[0], out int version))
|
||||
{
|
||||
return version;
|
||||
}
|
||||
|
||||
// unable to determine version
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
public float Scale => (float)UIScreen.MainScreen.Scale;
|
||||
public bool NfcEnabled => CoreNFC.NFCNdefReaderSession.ReadingAvailable;
|
||||
public bool HasCamera => true;
|
||||
public bool AutofillServiceSupported => false;
|
||||
public bool HasFaceIdSupport
|
||||
{
|
||||
get
|
||||
{
|
||||
if(Version < 11)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var context = new LAContext();
|
||||
if(!context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out NSError e))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return context.BiometryType == LABiometryType.FaceId;
|
||||
}
|
||||
}
|
||||
public bool IsExtension { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using Plugin.Settings.Abstractions;
|
||||
|
||||
namespace Bit.iOS.Core.Services
|
||||
{
|
||||
public class GoogleAnalyticsService : IGoogleAnalyticsService
|
||||
{
|
||||
public GoogleAnalyticsService(
|
||||
IAppIdService appIdService,
|
||||
ISettings settings)
|
||||
{}
|
||||
|
||||
public void TrackAppEvent(string eventName, string label = null)
|
||||
{
|
||||
}
|
||||
|
||||
public void TrackExtensionEvent(string eventName, string label = null)
|
||||
{
|
||||
}
|
||||
|
||||
public void TrackAutofillExtensionEvent(string eventName, string label = null)
|
||||
{
|
||||
}
|
||||
|
||||
public void TrackEvent(string category, string eventName, string label = null)
|
||||
{
|
||||
}
|
||||
|
||||
public void TrackException(string message, bool fatal)
|
||||
{
|
||||
}
|
||||
|
||||
public void TrackPage(string pageName)
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispatch(Action completionHandler = null)
|
||||
{
|
||||
completionHandler?.Invoke();
|
||||
}
|
||||
|
||||
public void SetAppOptOut(bool optOut)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
using Bit.App;
|
||||
using Bit.App.Abstractions;
|
||||
|
||||
namespace Bit.iOS.Core.Services
|
||||
{
|
||||
public class HttpService : IHttpService
|
||||
{
|
||||
public ApiHttpClient ApiClient => new ApiHttpClient();
|
||||
public IdentityHttpClient IdentityClient => new IdentityHttpClient();
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Bit.App.Abstractions;
|
||||
using Foundation;
|
||||
using Security;
|
||||
|
||||
namespace Bit.iOS.Core.Services
|
||||
{
|
||||
public class KeyChainStorageService : ISecureStorageService
|
||||
{
|
||||
public void Store(string key, byte[] dataBytes)
|
||||
{
|
||||
using(var data = NSData.FromArray(dataBytes))
|
||||
using(var newRecord = GetKeyRecord(key, data))
|
||||
{
|
||||
Delete(key);
|
||||
CheckError(SecKeyChain.Add(newRecord));
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Retrieve(string key)
|
||||
{
|
||||
SecStatusCode resultCode;
|
||||
|
||||
using(var existingRecord = GetKeyRecord(key))
|
||||
using(var record = SecKeyChain.QueryAsRecord(existingRecord, out resultCode))
|
||||
{
|
||||
if(resultCode == SecStatusCode.ItemNotFound)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
CheckError(resultCode);
|
||||
return record.Generic.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete(string key)
|
||||
{
|
||||
using(var record = GetExistingRecord(key))
|
||||
{
|
||||
if(record != null)
|
||||
{
|
||||
CheckError(SecKeyChain.Remove(record));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(string key)
|
||||
{
|
||||
using(var existingRecord = GetExistingRecord(key))
|
||||
{
|
||||
return existingRecord != null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckError(SecStatusCode resultCode, [CallerMemberName] string caller = null)
|
||||
{
|
||||
if(resultCode != SecStatusCode.Success)
|
||||
{
|
||||
throw new Exception(string.Format("Failed to execute {0}. Result code: {1}", caller, resultCode));
|
||||
}
|
||||
}
|
||||
|
||||
private static SecRecord GetKeyRecord(string key, NSData data = null)
|
||||
{
|
||||
var record = new SecRecord(SecKind.GenericPassword)
|
||||
{
|
||||
Service = "com.8bit.bitwarden",
|
||||
Account = key,
|
||||
AccessGroup = "LTZ2PFU5D6.com.8bit.bitwarden"
|
||||
};
|
||||
|
||||
if(data != null)
|
||||
{
|
||||
record.Generic = data;
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
private static SecRecord GetExistingRecord(string key)
|
||||
{
|
||||
var existingRecord = GetKeyRecord(key);
|
||||
|
||||
SecStatusCode resultCode;
|
||||
SecKeyChain.QueryAsRecord(existingRecord, out resultCode);
|
||||
|
||||
return resultCode == SecStatusCode.Success ? existingRecord : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using Foundation;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models;
|
||||
|
||||
namespace Bit.iOS.Core.Services
|
||||
{
|
||||
public class LocalizeService : ILocalizeService
|
||||
{
|
||||
public void SetLocale(CultureInfo ci)
|
||||
{
|
||||
Thread.CurrentThread.CurrentCulture = ci;
|
||||
Thread.CurrentThread.CurrentUICulture = ci;
|
||||
Console.WriteLine("CurrentCulture set: " + ci.Name);
|
||||
}
|
||||
|
||||
public CultureInfo GetCurrentCultureInfo()
|
||||
{
|
||||
var netLanguage = "en";
|
||||
if(NSLocale.PreferredLanguages.Length > 0)
|
||||
{
|
||||
var pref = NSLocale.PreferredLanguages[0];
|
||||
|
||||
netLanguage = iOSToDotnetLanguage(pref);
|
||||
}
|
||||
|
||||
// this gets called a lot - try/catch can be expensive so consider caching or something
|
||||
CultureInfo ci = null;
|
||||
try
|
||||
{
|
||||
ci = new CultureInfo(netLanguage);
|
||||
}
|
||||
catch(CultureNotFoundException e1)
|
||||
{
|
||||
// iOS locale not valid .NET culture (eg. "en-ES" : English in Spain)
|
||||
// fallback to first characters, in this case "en"
|
||||
try
|
||||
{
|
||||
var fallback = ToDotnetFallbackLanguage(new PlatformCulture(netLanguage));
|
||||
Console.WriteLine(netLanguage + " failed, trying " + fallback + " (" + e1.Message + ")");
|
||||
ci = new CultureInfo(fallback);
|
||||
}
|
||||
catch(CultureNotFoundException e2)
|
||||
{
|
||||
// iOS language not valid .NET culture, falling back to English
|
||||
Console.WriteLine(netLanguage + " couldn't be set, using 'en' (" + e2.Message + ")");
|
||||
ci = new CultureInfo("en");
|
||||
}
|
||||
}
|
||||
|
||||
return ci;
|
||||
}
|
||||
|
||||
private string iOSToDotnetLanguage(string iOSLanguage)
|
||||
{
|
||||
Console.WriteLine("iOS Language:" + iOSLanguage);
|
||||
var netLanguage = iOSLanguage;
|
||||
|
||||
if(iOSLanguage.StartsWith("zh-Hant") || iOSLanguage.StartsWith("zh-HK"))
|
||||
{
|
||||
netLanguage = "zh-Hant";
|
||||
}
|
||||
else if(iOSLanguage.StartsWith("zh"))
|
||||
{
|
||||
netLanguage = "zh-Hans";
|
||||
}
|
||||
else
|
||||
{
|
||||
//certain languages need to be converted to CultureInfo equivalent
|
||||
switch(iOSLanguage)
|
||||
{
|
||||
case "ms-MY": // "Malaysian (Malaysia)" not supported .NET culture
|
||||
case "ms-SG": // "Malaysian (Singapore)" not supported .NET culture
|
||||
netLanguage = "ms"; // closest supported
|
||||
break;
|
||||
case "gsw-CH": // "Schwiizertüütsch (Swiss German)" not supported .NET culture
|
||||
netLanguage = "de-CH"; // closest supported
|
||||
break;
|
||||
// add more application-specific cases here (if required)
|
||||
// ONLY use cultures that have been tested and known to work
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine(".NET Language/Locale:" + netLanguage);
|
||||
return netLanguage;
|
||||
}
|
||||
|
||||
private string ToDotnetFallbackLanguage(PlatformCulture platCulture)
|
||||
{
|
||||
Console.WriteLine(".NET Fallback Language:" + platCulture.LanguageCode);
|
||||
var netLanguage = platCulture.LanguageCode; // use the first part of the identifier (two chars, usually);
|
||||
|
||||
switch(platCulture.LanguageCode)
|
||||
{
|
||||
case "pt":
|
||||
netLanguage = "pt-PT"; // fallback to Portuguese (Portugal)
|
||||
break;
|
||||
case "gsw":
|
||||
netLanguage = "de-CH"; // equivalent to German (Switzerland) for this app
|
||||
break;
|
||||
// add more application-specific cases here (if required)
|
||||
// ONLY use cultures that have been tested and known to work
|
||||
}
|
||||
|
||||
Console.WriteLine(".NET Fallback Language/Locale:" + netLanguage + " (application-specific)");
|
||||
return netLanguage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
|
||||
namespace Bit.iOS.Core.Services
|
||||
{
|
||||
public class LogService : ILogService
|
||||
{
|
||||
public void WriteLine(string message)
|
||||
{
|
||||
Console.WriteLine(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
using System;
|
||||
using Bit.App.Abstractions;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Models.Page;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.iOS.Core.Services
|
||||
{
|
||||
public class NoopDeviceActionService : IDeviceActionService
|
||||
{
|
||||
public void Autofill(VaultListPageModel.Cipher cipher)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public void Background()
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public bool CanOpenFile(string fileName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ClearCache()
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public void CloseAutofill()
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public void CopyToClipboard(string text)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public void DismissKeyboard()
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons)
|
||||
{
|
||||
return Task.FromResult<string>(null);
|
||||
}
|
||||
|
||||
public Task<string> DisplayPromptAync(string title = null, string description = null, string text = null)
|
||||
{
|
||||
return Task.FromResult<string>(null);
|
||||
}
|
||||
|
||||
public Task HideLoadingAsync()
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task LaunchAppAsync(string appName, Page page)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void OpenAccessibilitySettings()
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public void OpenAutofillSettings()
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public bool OpenFile(byte[] fileData, string id, string fileName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void RateApp()
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public Task SelectFileAsync()
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task ShowLoadingAsync(string text)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void Toast(string text, bool longDuration = false)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,495 +0,0 @@
|
||||
using System;
|
||||
using Foundation;
|
||||
#if __IOS__
|
||||
using UIKit;
|
||||
#endif
|
||||
using Plugin.Settings.Abstractions;
|
||||
|
||||
namespace Bit.iOS.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Main implementation for ISettings
|
||||
/// </summary>
|
||||
[Preserve(AllMembers = true)]
|
||||
public class Settings : ISettings
|
||||
{
|
||||
private readonly object locker = new object();
|
||||
private readonly string _defaultsName;
|
||||
|
||||
public Settings(string defaultsName)
|
||||
{
|
||||
_defaultsName = defaultsName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value or the default that you specify.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Vaue of t (bool, int, float, long, string)</typeparam>
|
||||
/// <param name="key">Key for settings</param>
|
||||
/// <param name="defaultValue">default value if not set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>Value or default</returns>
|
||||
T GetValueOrDefaultInternal<T>(string key, T defaultValue = default(T), string fileName = null)
|
||||
{
|
||||
lock(locker)
|
||||
{
|
||||
var defaults = GetUserDefaults(fileName);
|
||||
|
||||
if(defaults[key] == null)
|
||||
return defaultValue;
|
||||
|
||||
Type typeOf = typeof(T);
|
||||
if(typeOf.IsGenericType && typeOf.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
typeOf = Nullable.GetUnderlyingType(typeOf);
|
||||
}
|
||||
object value = null;
|
||||
var typeCode = Type.GetTypeCode(typeOf);
|
||||
switch(typeCode)
|
||||
{
|
||||
case TypeCode.Decimal:
|
||||
var savedDecimal = defaults.StringForKey(key);
|
||||
value = Convert.ToDecimal(savedDecimal, System.Globalization.CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case TypeCode.Boolean:
|
||||
value = defaults.BoolForKey(key);
|
||||
break;
|
||||
case TypeCode.Int64:
|
||||
var savedInt64 = defaults.StringForKey(key);
|
||||
value = Convert.ToInt64(savedInt64, System.Globalization.CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case TypeCode.Double:
|
||||
value = defaults.DoubleForKey(key);
|
||||
break;
|
||||
case TypeCode.String:
|
||||
value = defaults.StringForKey(key);
|
||||
break;
|
||||
case TypeCode.Int32:
|
||||
value = (Int32)defaults.IntForKey(key);
|
||||
break;
|
||||
case TypeCode.Single:
|
||||
value = defaults.FloatForKey(key);
|
||||
break;
|
||||
|
||||
case TypeCode.DateTime:
|
||||
var savedTime = defaults.StringForKey(key);
|
||||
if(string.IsNullOrWhiteSpace(savedTime))
|
||||
{
|
||||
value = defaultValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
var ticks = Convert.ToInt64(savedTime, System.Globalization.CultureInfo.InvariantCulture);
|
||||
if(ticks >= 0)
|
||||
{
|
||||
//Old value, stored before update to UTC values
|
||||
value = new DateTime(ticks);
|
||||
}
|
||||
else
|
||||
{
|
||||
//New value, UTC
|
||||
value = new DateTime(-ticks, DateTimeKind.Utc);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
if(defaultValue is Guid)
|
||||
{
|
||||
var outGuid = Guid.Empty;
|
||||
var savedGuid = defaults.StringForKey(key);
|
||||
if(string.IsNullOrWhiteSpace(savedGuid))
|
||||
{
|
||||
value = outGuid;
|
||||
}
|
||||
else
|
||||
{
|
||||
Guid.TryParse(savedGuid, out outGuid);
|
||||
value = outGuid;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Value of type {typeCode} is not supported.");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return null != value ? (T)value : defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds or updates a value
|
||||
/// </summary>
|
||||
/// <param name="key">key to update</param>
|
||||
/// <param name="value">value to set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>True if added or update and you need to save</returns>
|
||||
bool AddOrUpdateValueInternal<T>(string key, T value, string fileName = null)
|
||||
{
|
||||
if(value == null)
|
||||
{
|
||||
Remove(key, fileName);
|
||||
return true;
|
||||
}
|
||||
|
||||
Type typeOf = typeof(T);
|
||||
if(typeOf.IsGenericType && typeOf.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
typeOf = Nullable.GetUnderlyingType(typeOf);
|
||||
}
|
||||
var typeCode = Type.GetTypeCode(typeOf);
|
||||
return AddOrUpdateValueCore(key, value, typeCode, fileName);
|
||||
}
|
||||
|
||||
bool AddOrUpdateValueCore(string key, object value, TypeCode typeCode, string fileName)
|
||||
{
|
||||
lock(locker)
|
||||
{
|
||||
var defaults = GetUserDefaults(fileName);
|
||||
switch(typeCode)
|
||||
{
|
||||
case TypeCode.Decimal:
|
||||
defaults.SetString(Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture), key);
|
||||
break;
|
||||
case TypeCode.Boolean:
|
||||
defaults.SetBool(Convert.ToBoolean(value), key);
|
||||
break;
|
||||
case TypeCode.Int64:
|
||||
defaults.SetString(Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture), key);
|
||||
break;
|
||||
case TypeCode.Double:
|
||||
defaults.SetDouble(Convert.ToDouble(value, System.Globalization.CultureInfo.InvariantCulture), key);
|
||||
break;
|
||||
case TypeCode.String:
|
||||
defaults.SetString(Convert.ToString(value), key);
|
||||
break;
|
||||
case TypeCode.Int32:
|
||||
defaults.SetInt(Convert.ToInt32(value, System.Globalization.CultureInfo.InvariantCulture), key);
|
||||
break;
|
||||
case TypeCode.Single:
|
||||
defaults.SetFloat(Convert.ToSingle(value, System.Globalization.CultureInfo.InvariantCulture), key);
|
||||
break;
|
||||
case TypeCode.DateTime:
|
||||
defaults.SetString(Convert.ToString(-(Convert.ToDateTime(value)).ToUniversalTime().Ticks), key);
|
||||
break;
|
||||
default:
|
||||
if(value is Guid)
|
||||
{
|
||||
if(value == null)
|
||||
value = Guid.Empty;
|
||||
|
||||
defaults.SetString(((Guid)value).ToString(), key);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Value of type {typeCode} is not supported.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
try
|
||||
{
|
||||
defaults.Synchronize();
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Console.WriteLine("Unable to save: " + key, " Message: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a desired key from the settings
|
||||
/// </summary>
|
||||
/// <param name="key">Key for setting</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
public void Remove(string key, string fileName = null)
|
||||
{
|
||||
lock(locker)
|
||||
{
|
||||
var defaults = GetUserDefaults(fileName);
|
||||
try
|
||||
{
|
||||
if(defaults[key] != null)
|
||||
{
|
||||
defaults.RemoveObject(key);
|
||||
defaults.Synchronize();
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Console.WriteLine("Unable to remove: " + key, " Message: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all keys from settings
|
||||
/// </summary>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
public void Clear(string fileName = null)
|
||||
{
|
||||
lock(locker)
|
||||
{
|
||||
var defaults = GetUserDefaults(fileName);
|
||||
try
|
||||
{
|
||||
var items = defaults.ToDictionary();
|
||||
|
||||
foreach(var item in items.Keys)
|
||||
{
|
||||
if(item is NSString nsString)
|
||||
defaults.RemoveObject(nsString);
|
||||
}
|
||||
defaults.Synchronize();
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Console.WriteLine("Unable to clear all defaults. Message: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the key has been added.
|
||||
/// </summary>
|
||||
/// <param name="key">Key to check</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>True if contains key, else false</returns>
|
||||
public bool Contains(string key, string fileName = null)
|
||||
{
|
||||
lock(locker)
|
||||
{
|
||||
var defaults = GetUserDefaults(fileName);
|
||||
try
|
||||
{
|
||||
var setting = defaults[key];
|
||||
return setting != null;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Console.WriteLine("Unable to clear all defaults. Message: " + ex.Message);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
NSUserDefaults GetUserDefaults(string fileName = null)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(fileName) && !string.IsNullOrWhiteSpace(_defaultsName))
|
||||
{
|
||||
return new NSUserDefaults(_defaultsName, NSUserDefaultsType.SuiteName);
|
||||
}
|
||||
|
||||
return string.IsNullOrWhiteSpace(fileName) ?
|
||||
NSUserDefaults.StandardUserDefaults :
|
||||
new NSUserDefaults(fileName, NSUserDefaultsType.SuiteName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#region GetValueOrDefault
|
||||
/// <summary>
|
||||
/// Gets the current value or the default that you specify.
|
||||
/// </summary>
|
||||
/// <param name="key">Key for settings</param>
|
||||
/// <param name="defaultValue">default value if not set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>Value or default</returns>
|
||||
public decimal GetValueOrDefault(string key, decimal defaultValue, string fileName = null) =>
|
||||
GetValueOrDefaultInternal(key, defaultValue, fileName);
|
||||
/// <summary>
|
||||
/// Gets the current value or the default that you specify.
|
||||
/// </summary>
|
||||
/// <param name="key">Key for settings</param>
|
||||
/// <param name="defaultValue">default value if not set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>Value or default</returns>
|
||||
public bool GetValueOrDefault(string key, bool defaultValue, string fileName = null) =>
|
||||
GetValueOrDefaultInternal(key, defaultValue, fileName);
|
||||
/// <summary>
|
||||
/// Gets the current value or the default that you specify.
|
||||
/// </summary>
|
||||
/// <param name="key">Key for settings</param>
|
||||
/// <param name="defaultValue">default value if not set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>Value or default</returns>
|
||||
public long GetValueOrDefault(string key, long defaultValue, string fileName = null) =>
|
||||
GetValueOrDefaultInternal(key, defaultValue, fileName);
|
||||
/// <summary>
|
||||
/// Gets the current value or the default that you specify.
|
||||
/// </summary>
|
||||
/// <param name="key">Key for settings</param>
|
||||
/// <param name="defaultValue">default value if not set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>Value or default</returns>
|
||||
public string GetValueOrDefault(string key, string defaultValue, string fileName = null) =>
|
||||
GetValueOrDefaultInternal(key, defaultValue, fileName);
|
||||
/// <summary>
|
||||
/// Gets the current value or the default that you specify.
|
||||
/// </summary>
|
||||
/// <param name="key">Key for settings</param>
|
||||
/// <param name="defaultValue">default value if not set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>Value or default</returns>
|
||||
public int GetValueOrDefault(string key, int defaultValue, string fileName = null) =>
|
||||
GetValueOrDefaultInternal(key, defaultValue, fileName);
|
||||
/// <summary>
|
||||
/// Gets the current value or the default that you specify.
|
||||
/// </summary>
|
||||
/// <param name="key">Key for settings</param>
|
||||
/// <param name="defaultValue">default value if not set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>Value or default</returns>
|
||||
public float GetValueOrDefault(string key, float defaultValue, string fileName = null) =>
|
||||
GetValueOrDefaultInternal(key, defaultValue, fileName);
|
||||
/// <summary>
|
||||
/// Gets the current value or the default that you specify.
|
||||
/// </summary>
|
||||
/// <param name="key">Key for settings</param>
|
||||
/// <param name="defaultValue">default value if not set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>Value or default</returns>
|
||||
public DateTime GetValueOrDefault(string key, DateTime defaultValue, string fileName = null) =>
|
||||
GetValueOrDefaultInternal(key, defaultValue, fileName);
|
||||
/// <summary>
|
||||
/// Gets the current value or the default that you specify.
|
||||
/// </summary>
|
||||
/// <param name="key">Key for settings</param>
|
||||
/// <param name="defaultValue">default value if not set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>Value or default</returns>
|
||||
public Guid GetValueOrDefault(string key, Guid defaultValue, string fileName = null) =>
|
||||
GetValueOrDefaultInternal(key, defaultValue, fileName);
|
||||
/// <summary>
|
||||
/// Gets the current value or the default that you specify.
|
||||
/// </summary>
|
||||
/// <param name="key">Key for settings</param>
|
||||
/// <param name="defaultValue">default value if not set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>Value or default</returns>
|
||||
public double GetValueOrDefault(string key, double defaultValue, string fileName = null) =>
|
||||
GetValueOrDefaultInternal(key, defaultValue, fileName);
|
||||
#endregion
|
||||
|
||||
#region AddOrUpdateValue
|
||||
/// <summary>
|
||||
/// Adds or updates the value
|
||||
/// </summary>
|
||||
/// <param name="key">Key for settting</param>
|
||||
/// <param name="value">Value to set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>True of was added or updated and you need to save it.</returns>
|
||||
public bool AddOrUpdateValue(string key, decimal value, string fileName = null) =>
|
||||
AddOrUpdateValueInternal(key, value, fileName);
|
||||
/// <summary>
|
||||
/// Adds or updates the value
|
||||
/// </summary>
|
||||
/// <param name="key">Key for settting</param>
|
||||
/// <param name="value">Value to set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>True of was added or updated and you need to save it.</returns>
|
||||
public bool AddOrUpdateValue(string key, bool value, string fileName = null) =>
|
||||
AddOrUpdateValueInternal(key, value, fileName);
|
||||
/// <summary>
|
||||
/// Adds or updates the value
|
||||
/// </summary>
|
||||
/// <param name="key">Key for settting</param>
|
||||
/// <param name="value">Value to set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>True of was added or updated and you need to save it.</returns>
|
||||
public bool AddOrUpdateValue(string key, long value, string fileName = null) =>
|
||||
AddOrUpdateValueInternal(key, value, fileName);
|
||||
/// <summary>
|
||||
/// Adds or updates the value
|
||||
/// </summary>
|
||||
/// <param name="key">Key for settting</param>
|
||||
/// <param name="value">Value to set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>True of was added or updated and you need to save it.</returns>
|
||||
public bool AddOrUpdateValue(string key, string value, string fileName = null) =>
|
||||
AddOrUpdateValueInternal(key, value, fileName);
|
||||
/// <summary>
|
||||
/// Adds or updates the value
|
||||
/// </summary>
|
||||
/// <param name="key">Key for settting</param>
|
||||
/// <param name="value">Value to set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>True of was added or updated and you need to save it.</returns>
|
||||
public bool AddOrUpdateValue(string key, int value, string fileName = null) =>
|
||||
AddOrUpdateValueInternal(key, value, fileName);
|
||||
/// <summary>
|
||||
/// Adds or updates the value
|
||||
/// </summary>
|
||||
/// <param name="key">Key for settting</param>
|
||||
/// <param name="value">Value to set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>True of was added or updated and you need to save it.</returns>
|
||||
public bool AddOrUpdateValue(string key, float value, string fileName = null) =>
|
||||
AddOrUpdateValueInternal(key, value, fileName);
|
||||
/// <summary>
|
||||
/// Adds or updates the value
|
||||
/// </summary>
|
||||
/// <param name="key">Key for settting</param>
|
||||
/// <param name="value">Value to set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>True of was added or updated and you need to save it.</returns>
|
||||
public bool AddOrUpdateValue(string key, DateTime value, string fileName = null) =>
|
||||
AddOrUpdateValueInternal(key, value, fileName);
|
||||
/// <summary>
|
||||
/// Adds or updates the value
|
||||
/// </summary>
|
||||
/// <param name="key">Key for settting</param>
|
||||
/// <param name="value">Value to set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>True of was added or updated and you need to save it.</returns>
|
||||
public bool AddOrUpdateValue(string key, Guid value, string fileName = null) =>
|
||||
AddOrUpdateValueInternal(key, value, fileName);
|
||||
/// <summary>
|
||||
/// Adds or updates the value
|
||||
/// </summary>
|
||||
/// <param name="key">Key for settting</param>
|
||||
/// <param name="value">Value to set</param>
|
||||
/// <param name="fileName">Name of file for settings to be stored and retrieved </param>
|
||||
/// <returns>True of was added or updated and you need to save it.</returns>
|
||||
public bool AddOrUpdateValue(string key, double value, string fileName = null) =>
|
||||
AddOrUpdateValueInternal(key, value, fileName);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to open the app settings page.
|
||||
/// </summary>
|
||||
/// <returns>true if success, else false and not supported</returns>
|
||||
public bool OpenAppSettings()
|
||||
{
|
||||
#if __IOS__
|
||||
//Opening settings only open in iOS 8+
|
||||
if(!UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
UIApplication.SharedApplication.OpenUrl(new NSUrl(UIApplication.OpenSettingsUrlString));
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Bit.App.Abstractions;
|
||||
using Foundation;
|
||||
using SQLite;
|
||||
|
||||
namespace Bit.iOS.Core.Services
|
||||
{
|
||||
public class SqlService : ISqlService
|
||||
{
|
||||
private SQLiteConnection _connection;
|
||||
|
||||
public SQLiteConnection GetConnection()
|
||||
{
|
||||
if(_connection != null)
|
||||
{
|
||||
return _connection;
|
||||
}
|
||||
|
||||
var sqliteFilename = "bitwarden.db3";
|
||||
var fileManager = new NSFileManager();
|
||||
var appGroupContainer = fileManager.GetContainerUrl("group.com.8bit.bitwarden");
|
||||
var libraryPath = Path.Combine(appGroupContainer.Path, "Library"); // Library folder
|
||||
var path = Path.Combine(libraryPath, sqliteFilename);
|
||||
Console.WriteLine(path);
|
||||
|
||||
_connection = new SQLiteConnection(path,
|
||||
SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create | SQLiteOpenFlags.FullMutex | SQLiteOpenFlags.SharedCache);
|
||||
return _connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AuthenticationServices;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models;
|
||||
|
||||
namespace Bit.iOS.Core.Utilities
|
||||
{
|
||||
public static class ASHelpers
|
||||
{
|
||||
public static async Task ReplaceAllIdentities(ICipherService cipherService)
|
||||
{
|
||||
if (await AutofillEnabled())
|
||||
{
|
||||
var identities = new List<ASPasswordCredentialIdentity>();
|
||||
var ciphers = await cipherService.GetAllAsync();
|
||||
foreach (var cipher in ciphers)
|
||||
{
|
||||
var identity = ToCredentialIdentity(cipher);
|
||||
if (identity != null)
|
||||
{
|
||||
identities.Add(identity);
|
||||
}
|
||||
}
|
||||
if (identities.Any())
|
||||
{
|
||||
await ASCredentialIdentityStore.SharedStore?.ReplaceCredentialIdentitiesAsync(identities.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<bool> IdentitiesCanIncremental()
|
||||
{
|
||||
var state = await ASCredentialIdentityStore.SharedStore?.GetCredentialIdentityStoreStateAsync();
|
||||
return state != null && state.Enabled && state.SupportsIncrementalUpdates;
|
||||
}
|
||||
|
||||
public static async Task<bool> AutofillEnabled()
|
||||
{
|
||||
var state = await ASCredentialIdentityStore.SharedStore?.GetCredentialIdentityStoreStateAsync();
|
||||
return state != null && state.Enabled;
|
||||
}
|
||||
|
||||
public static async Task<ASPasswordCredentialIdentity> GetCipherIdentityAsync(string cipherId, ICipherService cipherService)
|
||||
{
|
||||
var cipher = await cipherService.GetByIdAsync(cipherId);
|
||||
return ToCredentialIdentity(cipher);
|
||||
}
|
||||
|
||||
public static ASPasswordCredentialIdentity ToCredentialIdentity(Cipher cipher)
|
||||
{
|
||||
if (!cipher?.Login?.Uris?.Any() ?? true)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var uri = cipher.Login.Uris.FirstOrDefault()?.Uri?.Decrypt(cipher.OrganizationId);
|
||||
if (string.IsNullOrWhiteSpace(uri))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var username = cipher.Login.Username?.Decrypt(cipher.OrganizationId);
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var serviceId = new ASCredentialServiceIdentifier(uri, ASCredentialServiceIdentifierType.Url);
|
||||
return new ASPasswordCredentialIdentity(serviceId, username, cipher.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using CoreGraphics;
|
||||
using UIKit;
|
||||
|
||||
namespace Bit.iOS.Core.Utilities
|
||||
{
|
||||
public static class Dialogs
|
||||
{
|
||||
public static UIAlertController CreateLoadingAlert(string message)
|
||||
{
|
||||
var loadingIndicator = new UIActivityIndicatorView(new CGRect(10, 5, 50, 50));
|
||||
loadingIndicator.HidesWhenStopped = true;
|
||||
loadingIndicator.ActivityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray;
|
||||
loadingIndicator.StartAnimating();
|
||||
|
||||
var alert = UIAlertController.Create(null, message, UIAlertControllerStyle.Alert);
|
||||
alert.View.TintColor = UIColor.Black;
|
||||
alert.View.Add(loadingIndicator);
|
||||
return alert;
|
||||
}
|
||||
|
||||
public static UIAlertController CreateMessageAlert(string message)
|
||||
{
|
||||
var alert = UIAlertController.Create(null, message, UIAlertControllerStyle.Alert);
|
||||
alert.View.TintColor = UIColor.Black;
|
||||
return alert;
|
||||
}
|
||||
|
||||
public static UIAlertController CreateAlert(string title, string message, string accept, Action<UIAlertAction> acceptHandle = null)
|
||||
{
|
||||
var alert = UIAlertController.Create(title, message, UIAlertControllerStyle.Alert);
|
||||
var oldFrame = alert.View.Frame;
|
||||
alert.View.Frame = new RectangleF((float)oldFrame.X, (float)oldFrame.Y, (float)oldFrame.Width, (float)oldFrame.Height - 20);
|
||||
alert.AddAction(UIAlertAction.Create(accept, UIAlertActionStyle.Default, acceptHandle));
|
||||
return alert;
|
||||
}
|
||||
|
||||
public static UIAlertController CreateActionSheet(string title, UIViewController controller)
|
||||
{
|
||||
var sheet = UIAlertController.Create(title, null, UIAlertControllerStyle.ActionSheet);
|
||||
if(UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad)
|
||||
{
|
||||
var x = controller.View.Bounds.Width / 2;
|
||||
var y = controller.View.Bounds.Bottom;
|
||||
var rect = new CGRect(x, y, 0, 0);
|
||||
|
||||
sheet.PopoverPresentationController.SourceView = controller.View;
|
||||
sheet.PopoverPresentationController.SourceRect = rect;
|
||||
sheet.PopoverPresentationController.PermittedArrowDirections = UIPopoverArrowDirection.Unknown;
|
||||
}
|
||||
return sheet;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
|
||||
namespace Bit.iOS.Core.Views
|
||||
{
|
||||
public class ExtensionSearchDelegate : UISearchBarDelegate
|
||||
{
|
||||
private readonly UITableView _tableView;
|
||||
private CancellationTokenSource _filterResultsCancellationTokenSource;
|
||||
|
||||
public ExtensionSearchDelegate(UITableView tableView)
|
||||
{
|
||||
_tableView = tableView;
|
||||
}
|
||||
|
||||
public override void TextChanged(UISearchBar searchBar, string searchText)
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
Task.Run(() =>
|
||||
{
|
||||
NSRunLoop.Main.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(searchText))
|
||||
{
|
||||
await Task.Delay(300);
|
||||
if(searchText != searchBar.Text)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
_filterResultsCancellationTokenSource?.Cancel();
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
((ExtensionTableSource)_tableView.Source).FilterResults(searchText, cts.Token);
|
||||
_tableView.ReloadData();
|
||||
}
|
||||
catch(OperationCanceledException) { }
|
||||
_filterResultsCancellationTokenSource = cts;
|
||||
});
|
||||
}, cts.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.iOS.Core.Models;
|
||||
using Foundation;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UIKit;
|
||||
using XLabs.Ioc;
|
||||
|
||||
namespace Bit.iOS.Core.Views
|
||||
{
|
||||
public class ExtensionTableSource : UITableViewSource
|
||||
{
|
||||
private const string CellIdentifier = "TableCell";
|
||||
|
||||
private IEnumerable<CipherViewModel> _allItems = new List<CipherViewModel>();
|
||||
protected ICipherService _cipherService;
|
||||
protected ISettings _settings;
|
||||
private bool _accessPremium;
|
||||
private AppExtensionContext _context;
|
||||
private UIViewController _controller;
|
||||
|
||||
public ExtensionTableSource(AppExtensionContext context, UIViewController controller)
|
||||
{
|
||||
_accessPremium = Helpers.CanAccessPremium();
|
||||
_cipherService = Resolver.Resolve<ICipherService>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_context = context;
|
||||
_controller = controller;
|
||||
}
|
||||
|
||||
public IEnumerable<CipherViewModel> Items { get; private set; }
|
||||
|
||||
public async Task LoadItemsAsync(bool urlFilter = true, string searchFilter = null)
|
||||
{
|
||||
var combinedLogins = new List<Cipher>();
|
||||
|
||||
if(urlFilter)
|
||||
{
|
||||
var logins = await _cipherService.GetAllAsync(_context.UrlString);
|
||||
if(logins?.Item1 != null)
|
||||
{
|
||||
combinedLogins.AddRange(logins.Item1);
|
||||
}
|
||||
if(logins?.Item2 != null)
|
||||
{
|
||||
combinedLogins.AddRange(logins.Item2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var logins = await _cipherService.GetAllAsync();
|
||||
combinedLogins.AddRange(logins);
|
||||
}
|
||||
|
||||
_allItems = combinedLogins
|
||||
.Where(c => c.Type == App.Enums.CipherType.Login)
|
||||
.Select(s => new CipherViewModel(s))
|
||||
.OrderBy(s => s.Name)
|
||||
.ThenBy(s => s.Username)
|
||||
.ToList() ?? new List<CipherViewModel>();
|
||||
FilterResults(searchFilter, new CancellationToken());
|
||||
}
|
||||
|
||||
public void FilterResults(string searchFilter, CancellationToken ct)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
if(string.IsNullOrWhiteSpace(searchFilter))
|
||||
{
|
||||
Items = _allItems.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
searchFilter = searchFilter.ToLower();
|
||||
Items = _allItems
|
||||
.Where(s => s.Name?.ToLower().Contains(searchFilter) ?? false ||
|
||||
(s.Username?.ToLower().Contains(searchFilter) ?? false) ||
|
||||
(s.Uris?.FirstOrDefault()?.Uri?.ToLower().Contains(searchFilter) ?? false))
|
||||
.TakeWhile(s => !ct.IsCancellationRequested)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<CipherViewModel> TableItems { get; set; }
|
||||
|
||||
public override nint RowsInSection(UITableView tableview, nint section)
|
||||
{
|
||||
return Items == null || Items.Count() == 0 ? 1 : Items.Count();
|
||||
}
|
||||
|
||||
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
|
||||
{
|
||||
if(Items == null || Items.Count() == 0)
|
||||
{
|
||||
var noDataCell = new UITableViewCell(UITableViewCellStyle.Default, "NoDataCell");
|
||||
noDataCell.TextLabel.Text = AppResources.NoItemsTap;
|
||||
noDataCell.TextLabel.TextAlignment = UITextAlignment.Center;
|
||||
noDataCell.TextLabel.LineBreakMode = UILineBreakMode.WordWrap;
|
||||
noDataCell.TextLabel.Lines = 0;
|
||||
return noDataCell;
|
||||
}
|
||||
|
||||
var cell = tableView.DequeueReusableCell(CellIdentifier);
|
||||
|
||||
// if there are no cells to reuse, create a new one
|
||||
if(cell == null)
|
||||
{
|
||||
Debug.WriteLine("BW Log, Make new cell for list.");
|
||||
cell = new UITableViewCell(UITableViewCellStyle.Subtitle, CellIdentifier);
|
||||
cell.DetailTextLabel.TextColor = cell.DetailTextLabel.TintColor = new UIColor(red: 0.47f, green: 0.47f, blue: 0.47f, alpha: 1.0f);
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
public override void WillDisplay(UITableView tableView, UITableViewCell cell, NSIndexPath indexPath)
|
||||
{
|
||||
if(Items == null || Items.Count() == 0 || cell == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = Items.ElementAt(indexPath.Row);
|
||||
cell.TextLabel.Text = item.Name;
|
||||
cell.DetailTextLabel.Text = item.Username;
|
||||
}
|
||||
|
||||
public string GetTotp(CipherViewModel item)
|
||||
{
|
||||
string totp = null;
|
||||
if(_accessPremium)
|
||||
{
|
||||
if(item != null && !string.IsNullOrWhiteSpace(item.Totp.Value))
|
||||
{
|
||||
totp = Crypto.Totp(item.Totp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return totp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
using System;
|
||||
using UIKit;
|
||||
|
||||
namespace Bit.iOS.Core.Views
|
||||
{
|
||||
public class FormEntryTableViewCell : UITableViewCell, ISelectable
|
||||
{
|
||||
public FormEntryTableViewCell(
|
||||
string labelName = null,
|
||||
bool useTextView = false,
|
||||
nfloat? height = null,
|
||||
bool useLabelAsPlaceholder = false)
|
||||
: base(UITableViewCellStyle.Default, nameof(FormEntryTableViewCell))
|
||||
{
|
||||
var descriptor = UIFontDescriptor.PreferredBody;
|
||||
var pointSize = descriptor.PointSize;
|
||||
|
||||
if(labelName != null && !useLabelAsPlaceholder)
|
||||
{
|
||||
Label = new UILabel
|
||||
{
|
||||
Text = labelName,
|
||||
TranslatesAutoresizingMaskIntoConstraints = false,
|
||||
Font = UIFont.FromDescriptor(descriptor, 0.8f * pointSize),
|
||||
TextColor = new UIColor(red: 0.47f, green: 0.47f, blue: 0.47f, alpha: 1.0f)
|
||||
};
|
||||
|
||||
ContentView.Add(Label);
|
||||
}
|
||||
|
||||
if(useTextView)
|
||||
{
|
||||
TextView = new UITextView
|
||||
{
|
||||
TranslatesAutoresizingMaskIntoConstraints = false,
|
||||
Font = UIFont.FromDescriptor(descriptor, pointSize)
|
||||
};
|
||||
|
||||
ContentView.Add(TextView);
|
||||
ContentView.AddConstraints(new NSLayoutConstraint[] {
|
||||
NSLayoutConstraint.Create(TextView, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Leading, 1f, 15f),
|
||||
NSLayoutConstraint.Create(ContentView, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, TextView, NSLayoutAttribute.Trailing, 1f, 15f),
|
||||
NSLayoutConstraint.Create(ContentView, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, TextView, NSLayoutAttribute.Bottom, 1f, 10f)
|
||||
});
|
||||
|
||||
if(labelName != null && !useLabelAsPlaceholder)
|
||||
{
|
||||
ContentView.AddConstraint(
|
||||
NSLayoutConstraint.Create(TextView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, Label, NSLayoutAttribute.Bottom, 1f, 10f));
|
||||
}
|
||||
else
|
||||
{
|
||||
ContentView.AddConstraint(
|
||||
NSLayoutConstraint.Create(TextView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Top, 1f, 10f));
|
||||
}
|
||||
|
||||
if(height.HasValue)
|
||||
{
|
||||
ContentView.AddConstraint(
|
||||
NSLayoutConstraint.Create(TextView, NSLayoutAttribute.Height, NSLayoutRelation.Equal, null, NSLayoutAttribute.NoAttribute, 1f, height.Value));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TextField = new UITextField
|
||||
{
|
||||
TranslatesAutoresizingMaskIntoConstraints = false,
|
||||
BorderStyle = UITextBorderStyle.None,
|
||||
Font = UIFont.FromDescriptor(descriptor, pointSize),
|
||||
ClearButtonMode = UITextFieldViewMode.WhileEditing
|
||||
};
|
||||
|
||||
if(useLabelAsPlaceholder)
|
||||
{
|
||||
TextField.Placeholder = labelName;
|
||||
}
|
||||
|
||||
ContentView.Add(TextField);
|
||||
ContentView.AddConstraints(new NSLayoutConstraint[] {
|
||||
NSLayoutConstraint.Create(TextField, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Leading, 1f, 15f),
|
||||
NSLayoutConstraint.Create(ContentView, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, TextField, NSLayoutAttribute.Trailing, 1f, 15f),
|
||||
NSLayoutConstraint.Create(ContentView, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, TextField, NSLayoutAttribute.Bottom, 1f, 10f)
|
||||
});
|
||||
|
||||
if(labelName != null && !useLabelAsPlaceholder)
|
||||
{
|
||||
ContentView.AddConstraint(
|
||||
NSLayoutConstraint.Create(TextField, NSLayoutAttribute.Top, NSLayoutRelation.Equal, Label, NSLayoutAttribute.Bottom, 1f, 10f));
|
||||
}
|
||||
else
|
||||
{
|
||||
ContentView.AddConstraint(
|
||||
NSLayoutConstraint.Create(TextField, NSLayoutAttribute.Top, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Top, 1f, 10f));
|
||||
}
|
||||
|
||||
if(height.HasValue)
|
||||
{
|
||||
ContentView.AddConstraint(
|
||||
NSLayoutConstraint.Create(TextField, NSLayoutAttribute.Height, NSLayoutRelation.Equal, null, NSLayoutAttribute.NoAttribute, 1f, height.Value));
|
||||
}
|
||||
}
|
||||
|
||||
if(labelName != null && !useLabelAsPlaceholder)
|
||||
{
|
||||
ContentView.AddConstraints(new NSLayoutConstraint[] {
|
||||
NSLayoutConstraint.Create(Label, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Leading, 1f, 15f),
|
||||
NSLayoutConstraint.Create(Label, NSLayoutAttribute.Top, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Top, 1f, 10f),
|
||||
NSLayoutConstraint.Create(ContentView, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, Label, NSLayoutAttribute.Trailing, 1f, 15f)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public UILabel Label { get; set; }
|
||||
public UITextField TextField { get; set; }
|
||||
public UITextView TextView { get; set; }
|
||||
|
||||
public void Select()
|
||||
{
|
||||
if(TextView != null)
|
||||
{
|
||||
TextView.BecomeFirstResponder();
|
||||
}
|
||||
else if(TextField != null)
|
||||
{
|
||||
TextField.BecomeFirstResponder();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Bit.iOS.Core.Views
|
||||
{
|
||||
public interface ISelectable
|
||||
{
|
||||
void Select();
|
||||
}
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
using CoreGraphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using UIKit;
|
||||
|
||||
namespace Bit.iOS.Core.Views
|
||||
{
|
||||
public class PickerTableViewCell : UITableViewCell, ISelectable
|
||||
{
|
||||
private List<string> _items = new List<string>();
|
||||
private int _selectedIndex = 0;
|
||||
|
||||
public PickerTableViewCell(
|
||||
string labelName,
|
||||
nfloat? height = null)
|
||||
: base(UITableViewCellStyle.Default, nameof(PickerTableViewCell))
|
||||
{
|
||||
var descriptor = UIFontDescriptor.PreferredBody;
|
||||
var pointSize = descriptor.PointSize;
|
||||
|
||||
Label = new UILabel
|
||||
{
|
||||
Text = labelName,
|
||||
TranslatesAutoresizingMaskIntoConstraints = false,
|
||||
Font = UIFont.FromDescriptor(descriptor, 0.8f * pointSize),
|
||||
TextColor = new UIColor(red: 0.47f, green: 0.47f, blue: 0.47f, alpha: 1.0f)
|
||||
};
|
||||
|
||||
ContentView.Add(Label);
|
||||
|
||||
TextField = new NoCaretField
|
||||
{
|
||||
BorderStyle = UITextBorderStyle.None,
|
||||
TranslatesAutoresizingMaskIntoConstraints = false,
|
||||
Font = UIFont.FromDescriptor(descriptor, pointSize)
|
||||
};
|
||||
|
||||
var width = (float)UIScreen.MainScreen.Bounds.Width;
|
||||
var toolbar = new UIToolbar(new RectangleF(0, 0, width, 44))
|
||||
{
|
||||
BarStyle = UIBarStyle.Default,
|
||||
Translucent = true
|
||||
};
|
||||
var spacer = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace);
|
||||
var doneButton = new UIBarButtonItem(UIBarButtonSystemItem.Done, (o, a) =>
|
||||
{
|
||||
var s = (PickerSource)Picker.Model;
|
||||
if(s.SelectedIndex == -1 && Items != null && Items.Count > 0)
|
||||
{
|
||||
UpdatePickerSelectedIndex(0);
|
||||
}
|
||||
TextField.Text = s.SelectedItem;
|
||||
TextField.ResignFirstResponder();
|
||||
});
|
||||
|
||||
toolbar.SetItems(new[] { spacer, doneButton }, false);
|
||||
|
||||
TextField.InputView = Picker;
|
||||
TextField.InputAccessoryView = toolbar;
|
||||
|
||||
ContentView.Add(TextField);
|
||||
|
||||
ContentView.AddConstraints(new NSLayoutConstraint[] {
|
||||
NSLayoutConstraint.Create(TextField, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Leading, 1f, 15f),
|
||||
NSLayoutConstraint.Create(ContentView, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, TextField, NSLayoutAttribute.Trailing, 1f, 15f),
|
||||
NSLayoutConstraint.Create(ContentView, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, TextField, NSLayoutAttribute.Bottom, 1f, 10f),
|
||||
NSLayoutConstraint.Create(TextField, NSLayoutAttribute.Top, NSLayoutRelation.Equal, Label, NSLayoutAttribute.Bottom, 1f, 10f),
|
||||
NSLayoutConstraint.Create(Label, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Leading, 1f, 15f),
|
||||
NSLayoutConstraint.Create(Label, NSLayoutAttribute.Top, NSLayoutRelation.Equal, ContentView, NSLayoutAttribute.Top, 1f, 10f),
|
||||
NSLayoutConstraint.Create(ContentView, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, Label, NSLayoutAttribute.Trailing, 1f, 15f)
|
||||
});
|
||||
|
||||
if(height.HasValue)
|
||||
{
|
||||
ContentView.AddConstraint(
|
||||
NSLayoutConstraint.Create(TextField, NSLayoutAttribute.Height, NSLayoutRelation.Equal, null, NSLayoutAttribute.NoAttribute, 1f, height.Value));
|
||||
}
|
||||
|
||||
Picker.Model = new PickerSource(this);
|
||||
}
|
||||
|
||||
public UITextField TextField { get; set; }
|
||||
public UILabel Label { get; set; }
|
||||
public UIPickerView Picker { get; set; } = new UIPickerView();
|
||||
|
||||
public List<string> Items
|
||||
{
|
||||
get { return _items; }
|
||||
set
|
||||
{
|
||||
_items = value;
|
||||
UpdatePicker();
|
||||
}
|
||||
}
|
||||
|
||||
public int SelectedIndex
|
||||
{
|
||||
get { return _selectedIndex; }
|
||||
set
|
||||
{
|
||||
_selectedIndex = value;
|
||||
UpdatePicker();
|
||||
}
|
||||
}
|
||||
|
||||
public string SelectedItem => TextField.Text;
|
||||
|
||||
private void UpdatePicker()
|
||||
{
|
||||
TextField.Text = SelectedIndex == -1 || Items == null ? "" : Items[SelectedIndex];
|
||||
Picker.ReloadAllComponents();
|
||||
if(Items == null || Items.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdatePickerSelectedIndex(SelectedIndex);
|
||||
}
|
||||
|
||||
private void UpdatePickerFromModel(PickerSource s)
|
||||
{
|
||||
TextField.Text = s.SelectedItem;
|
||||
_selectedIndex = s.SelectedIndex;
|
||||
}
|
||||
|
||||
private void UpdatePickerSelectedIndex(int formsIndex)
|
||||
{
|
||||
var source = (PickerSource)Picker.Model;
|
||||
source.SelectedIndex = formsIndex;
|
||||
source.SelectedItem = formsIndex >= 0 ? Items[formsIndex] : null;
|
||||
Picker.Select(Math.Max(formsIndex, 0), 0, true);
|
||||
}
|
||||
|
||||
public void Select()
|
||||
{
|
||||
TextField?.BecomeFirstResponder();
|
||||
}
|
||||
|
||||
private class NoCaretField : UITextField
|
||||
{
|
||||
public NoCaretField() : base(default(CGRect))
|
||||
{ }
|
||||
|
||||
public override CGRect GetCaretRectForPosition(UITextPosition position)
|
||||
{
|
||||
return default(CGRect);
|
||||
}
|
||||
}
|
||||
|
||||
private class PickerSource : UIPickerViewModel
|
||||
{
|
||||
private readonly PickerTableViewCell _cell;
|
||||
|
||||
public PickerSource(PickerTableViewCell cell)
|
||||
{
|
||||
_cell = cell;
|
||||
}
|
||||
|
||||
public int SelectedIndex { get; internal set; }
|
||||
public string SelectedItem { get; internal set; }
|
||||
|
||||
public override nint GetComponentCount(UIPickerView picker)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public override nint GetRowsInComponent(UIPickerView pickerView, nint component)
|
||||
{
|
||||
return _cell.Items != null ? _cell.Items.Count : 0;
|
||||
}
|
||||
|
||||
public override string GetTitle(UIPickerView picker, nint row, nint component)
|
||||
{
|
||||
return _cell.Items[(int)row];
|
||||
}
|
||||
|
||||
public override void Selected(UIPickerView picker, nint row, nint component)
|
||||
{
|
||||
if(_cell.Items.Count == 0)
|
||||
{
|
||||
SelectedItem = null;
|
||||
SelectedIndex = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedItem = _cell.Items[(int)row];
|
||||
SelectedIndex = (int)row;
|
||||
}
|
||||
|
||||
_cell.UpdatePickerFromModel(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using System;
|
||||
using UIKit;
|
||||
|
||||
namespace Bit.iOS.Core.Views
|
||||
{
|
||||
public class SliderTableViewCell : UITableViewCell
|
||||
{
|
||||
private string _detailRightSpace = "\t";
|
||||
private int _value;
|
||||
|
||||
public SliderTableViewCell(string labelName, int value, int min, int max)
|
||||
: base(UITableViewCellStyle.Value1, nameof(SwitchTableViewCell))
|
||||
{
|
||||
TextLabel.Text = labelName;
|
||||
DetailTextLabel.TextColor = new UIColor(red: 0.47f, green: 0.47f, blue: 0.47f, alpha: 1.0f);
|
||||
|
||||
Slider = new UISlider
|
||||
{
|
||||
MinValue = min,
|
||||
MaxValue = max,
|
||||
TintColor = new UIColor(red: 0.24f, green: 0.55f, blue: 0.74f, alpha: 1.0f),
|
||||
Frame = new CoreGraphics.CGRect(0, 0, 180, 30)
|
||||
};
|
||||
Slider.ValueChanged += Slider_ValueChanged;
|
||||
Value = value;
|
||||
|
||||
AccessoryView = Slider;
|
||||
}
|
||||
|
||||
private void Slider_ValueChanged(object sender, EventArgs e)
|
||||
{
|
||||
var newValue = Convert.ToInt32(Math.Round(Slider.Value, 0));
|
||||
bool valueChanged = newValue != Value;
|
||||
|
||||
Value = newValue;
|
||||
|
||||
if(valueChanged)
|
||||
{
|
||||
ValueChanged?.Invoke(this, null);
|
||||
}
|
||||
}
|
||||
|
||||
public UISlider Slider { get; set; }
|
||||
public int Value
|
||||
{
|
||||
get { return _value; }
|
||||
set
|
||||
{
|
||||
_value = value;
|
||||
Slider.Value = value;
|
||||
DetailTextLabel.Text = string.Concat(value.ToString(), _detailRightSpace);
|
||||
}
|
||||
}
|
||||
public event EventHandler ValueChanged;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
using System;
|
||||
using UIKit;
|
||||
|
||||
namespace Bit.iOS.Core.Views
|
||||
{
|
||||
public class StepperTableViewCell : UITableViewCell
|
||||
{
|
||||
// Give some space to the right of the detail in between the spacer.
|
||||
// This is a bit of a hack, but I did not see a way to specify a margin on the
|
||||
// detaul DetailTextLabel or AccessoryView
|
||||
private string _detailRightSpace = "\t";
|
||||
private int _value;
|
||||
|
||||
public StepperTableViewCell(string labelName, int value, int min, int max, int increment)
|
||||
: base(UITableViewCellStyle.Value1, nameof(SwitchTableViewCell))
|
||||
{
|
||||
TextLabel.Text = labelName;
|
||||
DetailTextLabel.TextColor = new UIColor(red: 0.47f, green: 0.47f, blue: 0.47f, alpha: 1.0f);
|
||||
|
||||
Stepper = new UIStepper
|
||||
{
|
||||
TintColor = new UIColor(red: 0.47f, green: 0.47f, blue: 0.47f, alpha: 1.0f),
|
||||
MinimumValue = min,
|
||||
MaximumValue = max
|
||||
};
|
||||
Stepper.ValueChanged += Stepper_ValueChanged;
|
||||
Value = value;
|
||||
|
||||
AccessoryView = Stepper;
|
||||
}
|
||||
|
||||
private void Stepper_ValueChanged(object sender, EventArgs e)
|
||||
{
|
||||
Value = Convert.ToInt32(Stepper.Value);
|
||||
ValueChanged?.Invoke(this, null);
|
||||
}
|
||||
|
||||
public UIStepper Stepper { get; private set; }
|
||||
public int Value
|
||||
{
|
||||
get { return _value; }
|
||||
set
|
||||
{
|
||||
_value = value;
|
||||
Stepper.Value = value;
|
||||
DetailTextLabel.Text = string.Concat(value.ToString(), _detailRightSpace);
|
||||
}
|
||||
}
|
||||
public event EventHandler ValueChanged;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using System;
|
||||
using UIKit;
|
||||
|
||||
namespace Bit.iOS.Core.Views
|
||||
{
|
||||
public class SwitchTableViewCell : UITableViewCell
|
||||
{
|
||||
public SwitchTableViewCell(string labelName)
|
||||
: base(UITableViewCellStyle.Default, nameof(SwitchTableViewCell))
|
||||
{
|
||||
TextLabel.Text = labelName;
|
||||
AccessoryView = Switch;
|
||||
|
||||
Switch.ValueChanged += Switch_ValueChanged;
|
||||
}
|
||||
|
||||
private void Switch_ValueChanged(object sender, EventArgs e)
|
||||
{
|
||||
ValueChanged?.Invoke(this, null);
|
||||
}
|
||||
|
||||
public UISwitch Switch { get; set; } = new UISwitch();
|
||||
public event EventHandler ValueChanged;
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
using Foundation;
|
||||
using System;
|
||||
using UIKit;
|
||||
|
||||
namespace Bit.iOS.Core.Views
|
||||
{
|
||||
public class Toast : UIView
|
||||
{
|
||||
private NSTimer _dismissTimer;
|
||||
private NSLayoutConstraint _heightConstraint;
|
||||
private NSLayoutConstraint _leftMarginConstraint;
|
||||
private NSLayoutConstraint _rightMarginConstraint;
|
||||
private NSLayoutConstraint _bottomMarginConstraint;
|
||||
|
||||
public Toast(string text)
|
||||
: base(CoreGraphics.CGRect.FromLTRB(0, 0, 320, 38))
|
||||
{
|
||||
TranslatesAutoresizingMaskIntoConstraints = false;
|
||||
BackgroundColor = UIColor.DarkGray.ColorWithAlpha(0.9f);
|
||||
Layer.CornerRadius = 15;
|
||||
Layer.MasksToBounds = true;
|
||||
|
||||
MessageLabel = new UILabel
|
||||
{
|
||||
TranslatesAutoresizingMaskIntoConstraints = false,
|
||||
TextColor = UIColor.White,
|
||||
Font = UIFont.SystemFontOfSize(14),
|
||||
BackgroundColor = UIColor.Clear,
|
||||
LineBreakMode = UILineBreakMode.WordWrap,
|
||||
TextAlignment = UITextAlignment.Center,
|
||||
Lines = 0,
|
||||
Text = text
|
||||
};
|
||||
|
||||
AddSubview(MessageLabel);
|
||||
|
||||
var hMessageConstraints = NSLayoutConstraint.FromVisualFormat("H:|-5-[messageLabel]-5-|", 0, new NSDictionary(),
|
||||
NSDictionary.FromObjectsAndKeys(new NSObject[] { MessageLabel },
|
||||
new NSObject[] { new NSString("messageLabel") })
|
||||
);
|
||||
|
||||
var vMessageConstraints = NSLayoutConstraint.FromVisualFormat("V:|-0-[messageLabel]-0-|", 0, new NSDictionary(),
|
||||
NSDictionary.FromObjectsAndKeys(new NSObject[] { MessageLabel },
|
||||
new NSObject[] { new NSString("messageLabel") })
|
||||
);
|
||||
|
||||
AddConstraints(hMessageConstraints);
|
||||
AddConstraints(vMessageConstraints);
|
||||
|
||||
AddGestureRecognizer(new UITapGestureRecognizer(() => Dismiss(false)));
|
||||
}
|
||||
|
||||
public bool Dismissed { get; set; }
|
||||
public Action DismissCallback { get; set; }
|
||||
public TimeSpan Duration { get; set; } = TimeSpan.FromSeconds(3);
|
||||
public UILabel MessageLabel { get; set; }
|
||||
public nfloat LeftMargin { get; set; } = 5;
|
||||
public nfloat RightMargin { get; set; } = 5;
|
||||
public nfloat BottomMargin { get; set; } = 5;
|
||||
public nfloat Height { get; set; } = 38;
|
||||
|
||||
public void Show()
|
||||
{
|
||||
if(Superview != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_dismissTimer = NSTimer.CreateScheduledTimer(Duration, x => Dismiss());
|
||||
LayoutIfNeeded();
|
||||
|
||||
var localSuperView = UIApplication.SharedApplication.KeyWindow;
|
||||
if(localSuperView != null)
|
||||
{
|
||||
localSuperView.AddSubview(this);
|
||||
|
||||
_heightConstraint = NSLayoutConstraint.Create(this, NSLayoutAttribute.Height,
|
||||
NSLayoutRelation.GreaterThanOrEqual, null, NSLayoutAttribute.NoAttribute, 1, Height);
|
||||
|
||||
_leftMarginConstraint = NSLayoutConstraint.Create(this, NSLayoutAttribute.Left, NSLayoutRelation.Equal,
|
||||
localSuperView, NSLayoutAttribute.Left, 1, LeftMargin);
|
||||
|
||||
_rightMarginConstraint = NSLayoutConstraint.Create(this, NSLayoutAttribute.Right, NSLayoutRelation.Equal,
|
||||
localSuperView, NSLayoutAttribute.Right, 1, -RightMargin);
|
||||
|
||||
_bottomMarginConstraint = NSLayoutConstraint.Create(this, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal,
|
||||
localSuperView, NSLayoutAttribute.Bottom, 1, -BottomMargin);
|
||||
|
||||
// Avoid the "UIView-Encapsulated-Layout-Height" constraint conflicts
|
||||
// http://stackoverflow.com/questions/25059443/what-is-nslayoutconstraint-uiview-encapsulated-layout-height-and-how-should-i
|
||||
_leftMarginConstraint.Priority = 999;
|
||||
_rightMarginConstraint.Priority = 999;
|
||||
|
||||
AddConstraint(_heightConstraint);
|
||||
localSuperView.AddConstraint(_leftMarginConstraint);
|
||||
localSuperView.AddConstraint(_rightMarginConstraint);
|
||||
localSuperView.AddConstraint(_bottomMarginConstraint);
|
||||
|
||||
ShowWithAnimation();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Toast needs a keyWindows to display.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dismiss(bool animated = true)
|
||||
{
|
||||
if(Dismissed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dismissed = true;
|
||||
_dismissTimer?.Invalidate();
|
||||
_dismissTimer = null;
|
||||
|
||||
if(!animated)
|
||||
{
|
||||
RemoveFromSuperview();
|
||||
DismissCallback?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
SetNeedsLayout();
|
||||
Animate(0.3f, 0, UIViewAnimationOptions.CurveEaseIn, () => { Alpha = 0; }, () =>
|
||||
{
|
||||
RemoveFromSuperview();
|
||||
DismissCallback?.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
private void ShowWithAnimation()
|
||||
{
|
||||
Alpha = 0;
|
||||
SetNeedsLayout();
|
||||
_bottomMarginConstraint.Constant = -BottomMargin;
|
||||
_leftMarginConstraint.Constant = LeftMargin;
|
||||
_rightMarginConstraint.Constant = -RightMargin;
|
||||
AnimateNotify(0.3f, 0, 0.7f, 5f, UIViewAnimationOptions.CurveEaseInOut, () => { Alpha = 1; }, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{B2538ADA-B605-4D6F-ACD2-62A409680F84}</ProjectGuid>
|
||||
<ProjectTypeGuids>{FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>Bit.iOS.Core</RootNamespace>
|
||||
<IPhoneResourcePrefix>Resources</IPhoneResourcePrefix>
|
||||
<AssemblyName>BitwardeniOSCore</AssemblyName>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="Xamarin.iOS" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Constants.cs" />
|
||||
<Compile Include="Controllers\ExtendedUITableViewController.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="Models\AppExtensionContext.cs" />
|
||||
<Compile Include="Models\PasswordGenerationOptions.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Services\AppInfoService.cs" />
|
||||
<Compile Include="Services\NoopDeviceActionService.cs" />
|
||||
<Compile Include="Services\DeviceInfoService.cs" />
|
||||
<Compile Include="Services\KeyChainStorageService.cs" />
|
||||
<Compile Include="Services\CommonCryptoKeyDerivationService.cs" />
|
||||
<Compile Include="Services\HttpService.cs" />
|
||||
<Compile Include="Services\LogService.cs" />
|
||||
<Compile Include="Services\GoogleAnalyticsService.cs" />
|
||||
<Compile Include="Services\LocalizeService.cs" />
|
||||
<Compile Include="Services\Settings.cs" />
|
||||
<Compile Include="Services\SqlService.cs" />
|
||||
<Compile Include="Utilities\Dialogs.cs" />
|
||||
<Compile Include="Views\ExtensionTableSource.cs" />
|
||||
<Compile Include="Views\ISelectable.cs" />
|
||||
<Compile Include="Views\PickerTableViewCell.cs" />
|
||||
<Compile Include="Views\StepperTableViewCell.cs" />
|
||||
<Compile Include="Views\SliderTableViewCell.cs" />
|
||||
<Compile Include="Views\SwitchTableViewCell.cs" />
|
||||
<Compile Include="Views\FormEntryTableViewCell.cs" />
|
||||
<Compile Include="Views\ExtensionSearchDelegate.cs" />
|
||||
<Compile Include="Views\Toast.cs" />
|
||||
<Compile Include="Models\CipherViewModel.cs" />
|
||||
<Compile Include="Utilities\ASHelpers.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\App\App.csproj">
|
||||
<Project>{8a279ee4-4537-4656-9c93-44945e594556}</Project>
|
||||
<Name>App</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
|
||||
</Project>
|
||||
Reference in New Issue
Block a user