mirror of
https://github.com/bitwarden/mobile
synced 2025-12-15 07:43:37 +00:00
add shared controllers and view to ios core
This commit is contained in:
12
src/iOS.Core/Controllers/ExtendedUITableViewController.cs
Normal file
12
src/iOS.Core/Controllers/ExtendedUITableViewController.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using UIKit;
|
||||||
|
|
||||||
|
namespace Bit.iOS.Core.Controllers
|
||||||
|
{
|
||||||
|
public class ExtendedUITableViewController : UITableViewController
|
||||||
|
{
|
||||||
|
public ExtendedUITableViewController(IntPtr handle)
|
||||||
|
: base(handle)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/iOS.Core/Controllers/ExtendedUIViewController.cs
Normal file
12
src/iOS.Core/Controllers/ExtendedUIViewController.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using UIKit;
|
||||||
|
|
||||||
|
namespace Bit.iOS.Core.Controllers
|
||||||
|
{
|
||||||
|
public class ExtendedUIViewController : UIViewController
|
||||||
|
{
|
||||||
|
public ExtendedUIViewController(IntPtr handle)
|
||||||
|
: base(handle)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
}
|
||||||
177
src/iOS.Core/Controllers/LockPasswordViewController.cs
Normal file
177
src/iOS.Core/Controllers/LockPasswordViewController.cs
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
using System;
|
||||||
|
using UIKit;
|
||||||
|
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 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()
|
||||||
|
{
|
||||||
|
// _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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
335
src/iOS.Core/Controllers/LoginAddViewController.cs
Normal file
335
src/iOS.Core/Controllers/LoginAddViewController.cs
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.iOS.Core.Views;
|
||||||
|
using Foundation;
|
||||||
|
using UIKit;
|
||||||
|
using Bit.iOS.Core.Utilities;
|
||||||
|
using Bit.iOS.Core.Models;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using AuthenticationServices;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Models.View;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
|
|
||||||
|
namespace Bit.iOS.Core.Controllers
|
||||||
|
{
|
||||||
|
public abstract class LoginAddViewController : ExtendedUITableViewController
|
||||||
|
{
|
||||||
|
private ICipherService _cipherService;
|
||||||
|
private IFolderService _folderService;
|
||||||
|
private IEnumerable<FolderView> _folders;
|
||||||
|
|
||||||
|
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 = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||||
|
_folderService = ServiceContainer.Resolve<IFolderService>("folderService");
|
||||||
|
|
||||||
|
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.GetAllDecryptedAsync().GetAwaiter().GetResult();
|
||||||
|
var folderNames = _folders.Select(s => s.Name).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 CipherView
|
||||||
|
{
|
||||||
|
Name = string.IsNullOrWhiteSpace(NameCell.TextField.Text) ? null : NameCell.TextField.Text,
|
||||||
|
Notes = string.IsNullOrWhiteSpace(NotesCell.TextView.Text) ? null : NotesCell.TextView.Text,
|
||||||
|
Favorite = FavoriteCell.Switch.On,
|
||||||
|
FolderId = FolderCell.SelectedIndex == 0 ? null : _folders.ElementAtOrDefault(FolderCell.SelectedIndex - 1)?.Id,
|
||||||
|
Type = Bit.Core.Enums.CipherType.Login,
|
||||||
|
Login = new LoginView
|
||||||
|
{
|
||||||
|
Uris = null,
|
||||||
|
Username = string.IsNullOrWhiteSpace(UsernameCell.TextField.Text) ? null : UsernameCell.TextField.Text,
|
||||||
|
Password = string.IsNullOrWhiteSpace(PasswordCell.TextField.Text) ? null : PasswordCell.TextField.Text,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!string.IsNullOrWhiteSpace(UriCell.TextField.Text))
|
||||||
|
{
|
||||||
|
cipher.Login.Uris = new List<LoginUriView>
|
||||||
|
{
|
||||||
|
new LoginUriView
|
||||||
|
{
|
||||||
|
Uri = UriCell.TextField.Text
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var loadingAlert = Dialogs.CreateLoadingAlert(AppResources.Saving);
|
||||||
|
PresentViewController(loadingAlert, true, null);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cipherDomain = await _cipherService.EncryptAsync(cipher);
|
||||||
|
await _cipherService.SaveWithServerAsync(cipherDomain);
|
||||||
|
await loadingAlert.DismissViewControllerAsync(true);
|
||||||
|
if(await ASHelpers.IdentitiesCanIncremental())
|
||||||
|
{
|
||||||
|
var identity = await ASHelpers.GetCipherIdentityAsync(cipherDomain.Id, _cipherService);
|
||||||
|
if(identity != null)
|
||||||
|
{
|
||||||
|
await ASCredentialIdentityStore.SharedStore.SaveCredentialIdentitiesAsync(
|
||||||
|
new ASPasswordCredentialIdentity[] { identity });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await ASHelpers.ReplaceAllIdentities(_cipherService);
|
||||||
|
}
|
||||||
|
Success();
|
||||||
|
}
|
||||||
|
catch(ApiException e)
|
||||||
|
{
|
||||||
|
DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
335
src/iOS.Core/Controllers/PasswordGeneratorViewController.cs
Normal file
335
src/iOS.Core/Controllers/PasswordGeneratorViewController.cs
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Bit.iOS.Core.Views;
|
||||||
|
using Bit.iOS.Core.Models;
|
||||||
|
using Foundation;
|
||||||
|
using UIKit;
|
||||||
|
using CoreGraphics;
|
||||||
|
using Bit.iOS.Core.Utilities;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Bit.iOS.Core.Controllers
|
||||||
|
{
|
||||||
|
public abstract class PasswordGeneratorViewController : ExtendedUIViewController
|
||||||
|
{
|
||||||
|
private IPasswordGenerationService _passwordGenerationService;
|
||||||
|
|
||||||
|
public PasswordGeneratorViewController(IntPtr handle)
|
||||||
|
: base(handle)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
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 async override void ViewDidLoad()
|
||||||
|
{
|
||||||
|
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
|
||||||
|
"passwordGenerationService");
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = await _passwordGenerationService.GetOptionsAsync();
|
||||||
|
UppercaseCell.Switch.On = options.Uppercase.GetValueOrDefault();
|
||||||
|
LowercaseCell.Switch.On = options.Lowercase.GetValueOrDefault(true);
|
||||||
|
SpecialCell.Switch.On = options.Special.GetValueOrDefault();
|
||||||
|
NumbersCell.Switch.On = options.Number.GetValueOrDefault();
|
||||||
|
MinNumbersCell.Value = options.MinNumber.GetValueOrDefault(1);
|
||||||
|
MinSpecialCell.Value = options.MinSpecial.GetValueOrDefault(1);
|
||||||
|
LengthCell.Value = options.Length.GetValueOrDefault(14);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var task = GeneratePasswordAsync();
|
||||||
|
base.ViewDidLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Options_ValueChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if(InvalidState())
|
||||||
|
{
|
||||||
|
LowercaseCell.Switch.On = true;
|
||||||
|
}
|
||||||
|
var task = GeneratePasswordAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool InvalidState()
|
||||||
|
{
|
||||||
|
return !LowercaseCell.Switch.On && !UppercaseCell.Switch.On && !NumbersCell.Switch.On && !SpecialCell.Switch.On;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GeneratePasswordAsync()
|
||||||
|
{
|
||||||
|
BasePasswordLabel.Text = await _passwordGenerationService.GeneratePasswordAsync(
|
||||||
|
new Bit.Core.Models.Domain.PasswordGenerationOptions
|
||||||
|
{
|
||||||
|
Length = LengthCell.Value,
|
||||||
|
Uppercase = UppercaseCell.Switch.On,
|
||||||
|
Lowercase = LowercaseCell.Switch.On,
|
||||||
|
Number = NumbersCell.Switch.On,
|
||||||
|
Special = SpecialCell.Switch.On,
|
||||||
|
MinSpecial = MinSpecialCell.Value,
|
||||||
|
MinNumber = 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)
|
||||||
|
{
|
||||||
|
var task = _controller.GeneratePasswordAsync();
|
||||||
|
}
|
||||||
|
else if(indexPath.Row == 1)
|
||||||
|
{
|
||||||
|
UIPasteboard clipboard = UIPasteboard.General;
|
||||||
|
clipboard.String = _controller.BasePasswordLabel.Text;
|
||||||
|
var alert = Dialogs.CreateMessageAlert(
|
||||||
|
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
|
||||||
|
_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ namespace Bit.iOS.Core.Models
|
|||||||
{
|
{
|
||||||
public CipherViewModel(CipherView cipher)
|
public CipherViewModel(CipherView cipher)
|
||||||
{
|
{
|
||||||
|
CipherView = cipher;
|
||||||
Id = cipher.Id;
|
Id = cipher.Id;
|
||||||
Name = cipher.Name;
|
Name = cipher.Name;
|
||||||
Username = cipher.Login?.Username;
|
Username = cipher.Login?.Username;
|
||||||
@@ -26,6 +27,7 @@ namespace Bit.iOS.Core.Models
|
|||||||
public List<LoginUriModel> Uris { get; set; }
|
public List<LoginUriModel> Uris { get; set; }
|
||||||
public string Totp { get; set; }
|
public string Totp { get; set; }
|
||||||
public List<Tuple<string, string>> Fields { get; set; }
|
public List<Tuple<string, string>> Fields { get; set; }
|
||||||
|
public CipherView CipherView { get; set; }
|
||||||
|
|
||||||
public class LoginUriModel
|
public class LoginUriModel
|
||||||
{
|
{
|
||||||
|
|||||||
49
src/iOS.Core/Views/ExtensionSearchDelegate.cs
Normal file
49
src/iOS.Core/Views/ExtensionSearchDelegate.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
148
src/iOS.Core/Views/ExtensionTableSource.cs
Normal file
148
src/iOS.Core/Views/ExtensionTableSource.cs
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Models.View;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Bit.iOS.Core.Models;
|
||||||
|
using Foundation;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UIKit;
|
||||||
|
|
||||||
|
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 ITotpService _totpService;
|
||||||
|
protected IUserService _userService;
|
||||||
|
private AppExtensionContext _context;
|
||||||
|
private UIViewController _controller;
|
||||||
|
|
||||||
|
public ExtensionTableSource(AppExtensionContext context, UIViewController controller)
|
||||||
|
{
|
||||||
|
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||||
|
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
||||||
|
_userService = ServiceContainer.Resolve<IUserService>("userService");
|
||||||
|
_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<CipherView>();
|
||||||
|
|
||||||
|
if(urlFilter)
|
||||||
|
{
|
||||||
|
var logins = await _cipherService.GetAllDecryptedByUrlAsync(_context.UrlString);
|
||||||
|
if(logins?.Item1 != null)
|
||||||
|
{
|
||||||
|
combinedLogins.AddRange(logins.Item1);
|
||||||
|
}
|
||||||
|
if(logins?.Item2 != null)
|
||||||
|
{
|
||||||
|
combinedLogins.AddRange(logins.Item2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var logins = await _cipherService.GetAllDecryptedAsync();
|
||||||
|
combinedLogins.AddRange(logins);
|
||||||
|
}
|
||||||
|
|
||||||
|
_allItems = combinedLogins
|
||||||
|
.Where(c => c.Type == Bit.Core.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 async Task<string> GetTotpAsync(CipherViewModel item)
|
||||||
|
{
|
||||||
|
string totp = null;
|
||||||
|
var accessPremium = await _userService.CanAccessPremiumAsync();
|
||||||
|
if(accessPremium || (item?.CipherView.OrganizationUseTotp ?? false))
|
||||||
|
{
|
||||||
|
if(item != null && !string.IsNullOrWhiteSpace(item.Totp))
|
||||||
|
{
|
||||||
|
totp = await _totpService.GetCodeAsync(item.Totp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return totp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
129
src/iOS.Core/Views/FormEntryTableViewCell.cs
Normal file
129
src/iOS.Core/Views/FormEntryTableViewCell.cs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/iOS.Core/Views/ISelectable.cs
Normal file
7
src/iOS.Core/Views/ISelectable.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Bit.iOS.Core.Views
|
||||||
|
{
|
||||||
|
public interface ISelectable
|
||||||
|
{
|
||||||
|
void Select();
|
||||||
|
}
|
||||||
|
}
|
||||||
195
src/iOS.Core/Views/PickerTableViewCell.cs
Normal file
195
src/iOS.Core/Views/PickerTableViewCell.cs
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/iOS.Core/Views/SliderTableViewCell.cs
Normal file
56
src/iOS.Core/Views/SliderTableViewCell.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/iOS.Core/Views/StepperTableViewCell.cs
Normal file
51
src/iOS.Core/Views/StepperTableViewCell.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/iOS.Core/Views/SwitchTableViewCell.cs
Normal file
25
src/iOS.Core/Views/SwitchTableViewCell.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,6 +54,11 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Constants.cs" />
|
<Compile Include="Constants.cs" />
|
||||||
|
<Compile Include="Controllers\ExtendedUITableViewController.cs" />
|
||||||
|
<Compile Include="Controllers\ExtendedUIViewController.cs" />
|
||||||
|
<Compile Include="Controllers\LockPasswordViewController.cs" />
|
||||||
|
<Compile Include="Controllers\LoginAddViewController.cs" />
|
||||||
|
<Compile Include="Controllers\PasswordGeneratorViewController.cs" />
|
||||||
<Compile Include="Models\AppExtensionContext.cs" />
|
<Compile Include="Models\AppExtensionContext.cs" />
|
||||||
<Compile Include="Models\CipherViewModel.cs" />
|
<Compile Include="Models\CipherViewModel.cs" />
|
||||||
<Compile Include="Models\PasswordGenerationOptions.cs" />
|
<Compile Include="Models\PasswordGenerationOptions.cs" />
|
||||||
@@ -66,6 +71,14 @@
|
|||||||
<Compile Include="Services\KeyChainStorageService.cs" />
|
<Compile Include="Services\KeyChainStorageService.cs" />
|
||||||
<Compile Include="Services\LocalizeService.cs" />
|
<Compile Include="Services\LocalizeService.cs" />
|
||||||
<Compile Include="Utilities\ThemeHelpers.cs" />
|
<Compile Include="Utilities\ThemeHelpers.cs" />
|
||||||
|
<Compile Include="Views\ExtensionSearchDelegate.cs" />
|
||||||
|
<Compile Include="Views\ExtensionTableSource.cs" />
|
||||||
|
<Compile Include="Views\FormEntryTableViewCell.cs" />
|
||||||
|
<Compile Include="Views\ISelectable.cs" />
|
||||||
|
<Compile Include="Views\PickerTableViewCell.cs" />
|
||||||
|
<Compile Include="Views\SliderTableViewCell.cs" />
|
||||||
|
<Compile Include="Views\StepperTableViewCell.cs" />
|
||||||
|
<Compile Include="Views\SwitchTableViewCell.cs" />
|
||||||
<Compile Include="Views\Toast.cs" />
|
<Compile Include="Views\Toast.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
Reference in New Issue
Block a user