diff --git a/src/iOS.Autofill/CredentialProviderViewController.cs b/src/iOS.Autofill/CredentialProviderViewController.cs index 80b99a2ac..3d451e928 100644 --- a/src/iOS.Autofill/CredentialProviderViewController.cs +++ b/src/iOS.Autofill/CredentialProviderViewController.cs @@ -122,29 +122,23 @@ namespace Bit.iOS.Autofill public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender) { - var navController = segue.DestinationViewController as UINavigationController; - if(navController != null) + if(segue.DestinationViewController is UINavigationController navController) { - var listLoginController = navController.TopViewController as LoginListViewController; - var listSearchController = navController.TopViewController as LoginSearchViewController; - var passwordViewController = navController.TopViewController as LockPasswordViewController; - var setupViewController = navController.TopViewController as SetupViewController; - - if(listLoginController != null) + if(navController.TopViewController is LoginListViewController listLoginController) { listLoginController.Context = _context; listLoginController.CPViewController = this; } - else if(listSearchController != null) + else if(navController.TopViewController is LoginSearchViewController listSearchController) { listSearchController.Context = _context; listSearchController.CPViewController = this; } - else if(passwordViewController != null) + else if(navController.TopViewController is LockPasswordViewController passwordViewController) { passwordViewController.CPViewController = this; } - else if(setupViewController != null) + else if(navController.TopViewController is SetupViewController setupViewController) { setupViewController.CPViewController = this; } diff --git a/src/iOS.Extension/LoadingViewController.cs b/src/iOS.Extension/LoadingViewController.cs new file mode 100644 index 000000000..6809b3822 --- /dev/null +++ b/src/iOS.Extension/LoadingViewController.cs @@ -0,0 +1,398 @@ +using System; +using System.Diagnostics; +using Foundation; +using UIKit; +using Bit.iOS.Core; +using Newtonsoft.Json; +using Bit.iOS.Extension.Models; +using MobileCoreServices; +using Bit.iOS.Core.Utilities; +using Bit.App.Resources; +using Bit.iOS.Core.Controllers; +using System.Collections.Generic; +using Bit.iOS.Core.Models; +using Bit.Core.Utilities; +using Bit.Core.Abstractions; + +namespace Bit.iOS.Extension +{ + public partial class LoadingViewController : ExtendedUIViewController + { + private Context _context = new Context(); + private readonly JsonSerializerSettings _jsonSettings = + new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; + + public LoadingViewController(IntPtr handle) + : base(handle) + { } + + public override void ViewDidLoad() + { + InitApp(); + base.ViewDidLoad(); + View.BackgroundColor = new UIColor(red: 0.94f, green: 0.94f, blue: 0.96f, alpha: 1.0f); + _context.ExtContext = ExtensionContext; + foreach(var item in ExtensionContext.InputItems) + { + var processed = false; + foreach(var itemProvider in item.Attachments) + { + if(ProcessWebUrlProvider(itemProvider) + || ProcessFindLoginProvider(itemProvider) + || ProcessFindLoginBrowserProvider(itemProvider, Constants.UTTypeAppExtensionFillBrowserAction) + || ProcessFindLoginBrowserProvider(itemProvider, Constants.UTTypeAppExtensionFillWebViewAction) + || ProcessSaveLoginProvider(itemProvider) + || ProcessChangePasswordProvider(itemProvider) + || ProcessExtensionSetupProvider(itemProvider)) + { + processed = true; + break; + } + } + if(processed) + { + break; + } + } + } + + public override void ViewDidAppear(bool animated) + { + base.ViewDidAppear(animated); + if(!IsAuthed()) + { + var alert = Dialogs.CreateAlert(null, AppResources.MustLogInMainApp, AppResources.Ok, (a) => + { + CompleteRequest(null); + }); + PresentViewController(alert, true, null); + return; + } + if(_context.ProviderType == Constants.UTTypeAppExtensionSetup) + { + PerformSegue("setupSegue", this); + return; + } + if(IsLocked()) + { + PerformSegue("lockPasswordSegue", this); + } + else + { + ContinueOn(); + } + } + + public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender) + { + if(segue.DestinationViewController is UINavigationController navController) + { + if(navController.TopViewController is LoginListViewController listLoginController) + { + listLoginController.Context = _context; + listLoginController.LoadingController = this; + } + else if(navController.TopViewController is LoginAddViewController addLoginController) + { + addLoginController.Context = _context; + addLoginController.LoadingController = this; + } + else if(navController.TopViewController is LockPasswordViewController passwordViewController) + { + passwordViewController.LoadingController = this; + } + else if(navController.TopViewController is SetupViewController setupViewController) + { + setupViewController.Context = _context; + setupViewController.LoadingController = this; + } + } + } + + public void DismissLockAndContinue() + { + Debug.WriteLine("BW Log, Dismissing lock controller."); + DismissViewController(false, () => ContinueOn()); + } + + private void ContinueOn() + { + Debug.WriteLine("BW Log, Segue to setup, login add or list."); + if(_context.ProviderType == Constants.UTTypeAppExtensionSaveLoginAction) + { + PerformSegue("newLoginSegue", this); + } + else if(_context.ProviderType == Constants.UTTypeAppExtensionSetup) + { + PerformSegue("setupSegue", this); + } + else + { + PerformSegue("loginListSegue", this); + } + } + + public void CompleteUsernamePasswordRequest(string username, string password, + List> fields, string totp) + { + NSDictionary itemData = null; + if(_context.ProviderType == UTType.PropertyList) + { + var fillScript = new FillScript(_context.Details, username, password, fields); + var scriptJson = JsonConvert.SerializeObject(fillScript, _jsonSettings); + var scriptDict = new NSDictionary(Constants.AppExtensionWebViewPageFillScript, scriptJson); + itemData = new NSDictionary(NSJavaScriptExtension.FinalizeArgumentKey, scriptDict); + } + else if(_context.ProviderType == Constants.UTTypeAppExtensionFindLoginAction) + { + itemData = new NSDictionary( + Constants.AppExtensionUsernameKey, username, + Constants.AppExtensionPasswordKey, password); + } + else if(_context.ProviderType == Constants.UTTypeAppExtensionFillBrowserAction + || _context.ProviderType == Constants.UTTypeAppExtensionFillWebViewAction) + { + var fillScript = new FillScript(_context.Details, username, password, fields); + var scriptJson = JsonConvert.SerializeObject(fillScript, _jsonSettings); + itemData = new NSDictionary(Constants.AppExtensionWebViewPageFillScript, scriptJson); + } + else if(_context.ProviderType == Constants.UTTypeAppExtensionSaveLoginAction) + { + itemData = new NSDictionary( + Constants.AppExtensionUsernameKey, username, + Constants.AppExtensionPasswordKey, password); + } + else if(_context.ProviderType == Constants.UTTypeAppExtensionChangePasswordAction) + { + itemData = new NSDictionary( + Constants.AppExtensionPasswordKey, string.Empty, + Constants.AppExtensionOldPasswordKey, password); + } + + if(!string.IsNullOrWhiteSpace(totp)) + { + UIPasteboard.General.String = totp; + } + CompleteRequest(itemData); + } + + public void CompleteRequest(NSDictionary itemData) + { + Debug.WriteLine("BW LOG, itemData: " + itemData); + var resultsProvider = new NSItemProvider(itemData, UTType.PropertyList); + var resultsItem = new NSExtensionItem { Attachments = new NSItemProvider[] { resultsProvider } }; + var returningItems = new NSExtensionItem[] { resultsItem }; + NSRunLoop.Main.BeginInvokeOnMainThread(() => ExtensionContext?.CompleteRequest(returningItems, null)); + } + + private bool ProcessItemProvider(NSItemProvider itemProvider, string type, Action dictAction, + Action urlAction = null) + { + if(!itemProvider.HasItemConformingTo(type)) + { + return false; + } + + itemProvider.LoadItem(type, null, (NSObject list, NSError error) => + { + if(list == null) + { + return; + } + + _context.ProviderType = type; + if(list is NSDictionary dict && dictAction != null) + { + dictAction(dict); + } + else if(list is NSUrl && urlAction != null) + { + var url = list as NSUrl; + urlAction(url); + } + else + { + throw new Exception("Cannot parse list for action. List is " + + (list?.GetType().ToString() ?? "null")); + } + + Debug.WriteLine("BW LOG, ProviderType: " + _context.ProviderType); + Debug.WriteLine("BW LOG, Url: " + _context.UrlString); + Debug.WriteLine("BW LOG, Title: " + _context.LoginTitle); + Debug.WriteLine("BW LOG, Username: " + _context.Username); + Debug.WriteLine("BW LOG, Password: " + _context.Password); + Debug.WriteLine("BW LOG, Old Password: " + _context.OldPassword); + Debug.WriteLine("BW LOG, Notes: " + _context.Notes); + Debug.WriteLine("BW LOG, Details: " + _context.Details); + + if(_context.PasswordOptions != null) + { + Debug.WriteLine("BW LOG, PasswordOptions Min Length: " + _context.PasswordOptions.MinLength); + Debug.WriteLine("BW LOG, PasswordOptions Max Length: " + _context.PasswordOptions.MaxLength); + Debug.WriteLine("BW LOG, PasswordOptions Require Digits: " + _context.PasswordOptions.RequireDigits); + Debug.WriteLine("BW LOG, PasswordOptions Require Symbols: " + _context.PasswordOptions.RequireSymbols); + Debug.WriteLine("BW LOG, PasswordOptions Forbidden Chars: " + _context.PasswordOptions.ForbiddenCharacters); + } + }); + + return true; + } + + private bool ProcessWebUrlProvider(NSItemProvider itemProvider) + { + return ProcessItemProvider(itemProvider, UTType.PropertyList, dict => + { + var result = dict[NSJavaScriptExtension.PreprocessingResultsKey]; + if(result == null) + { + return; + } + _context.UrlString = result.ValueForKey(new NSString(Constants.AppExtensionUrlStringKey)) as NSString; + var jsonStr = result.ValueForKey(new NSString(Constants.AppExtensionWebViewPageDetails)) as NSString; + _context.Details = DeserializeString(jsonStr); + }); + } + + private bool ProcessFindLoginProvider(NSItemProvider itemProvider) + { + return ProcessItemProvider(itemProvider, Constants.UTTypeAppExtensionFindLoginAction, dict => + { + var version = dict[Constants.AppExtensionVersionNumberKey] as NSNumber; + var url = dict[Constants.AppExtensionUrlStringKey] as NSString; + if(url != null) + { + _context.UrlString = url; + } + }); + } + + private bool ProcessFindLoginBrowserProvider(NSItemProvider itemProvider, string action) + { + return ProcessItemProvider(itemProvider, action, dict => + { + var version = dict[Constants.AppExtensionVersionNumberKey] as NSNumber; + var url = dict[Constants.AppExtensionUrlStringKey] as NSString; + if(url != null) + { + _context.UrlString = url; + } + _context.Details = DeserializeDictionary(dict[Constants.AppExtensionWebViewPageDetails] as NSDictionary); + }, url => + { + if(url != null) + { + _context.UrlString = url.AbsoluteString; + } + }); + } + + private bool ProcessSaveLoginProvider(NSItemProvider itemProvider) + { + return ProcessItemProvider(itemProvider, Constants.UTTypeAppExtensionSaveLoginAction, dict => + { + var version = dict[Constants.AppExtensionVersionNumberKey] as NSNumber; + var url = dict[Constants.AppExtensionUrlStringKey] as NSString; + var title = dict[Constants.AppExtensionTitleKey] as NSString; + var sectionTitle = dict[Constants.AppExtensionSectionTitleKey] as NSString; + var username = dict[Constants.AppExtensionUsernameKey] as NSString; + var password = dict[Constants.AppExtensionPasswordKey] as NSString; + var notes = dict[Constants.AppExtensionNotesKey] as NSString; + var fields = dict[Constants.AppExtensionFieldsKey] as NSDictionary; + if(url != null) + { + _context.UrlString = url; + } + _context.LoginTitle = title; + _context.Username = username; + _context.Password = password; + _context.Notes = notes; + _context.PasswordOptions = DeserializeDictionary(dict[Constants.AppExtensionPasswordGeneratorOptionsKey] as NSDictionary); + }); + } + + private bool ProcessChangePasswordProvider(NSItemProvider itemProvider) + { + return ProcessItemProvider(itemProvider, Constants.UTTypeAppExtensionChangePasswordAction, dict => + { + var version = dict[Constants.AppExtensionVersionNumberKey] as NSNumber; + var url = dict[Constants.AppExtensionUrlStringKey] as NSString; + var title = dict[Constants.AppExtensionTitleKey] as NSString; + var sectionTitle = dict[Constants.AppExtensionSectionTitleKey] as NSString; + var username = dict[Constants.AppExtensionUsernameKey] as NSString; + var password = dict[Constants.AppExtensionPasswordKey] as NSString; + var oldPassword = dict[Constants.AppExtensionOldPasswordKey] as NSString; + var notes = dict[Constants.AppExtensionNotesKey] as NSString; + var fields = dict[Constants.AppExtensionFieldsKey] as NSDictionary; + if(url != null) + { + _context.UrlString = url; + } + _context.LoginTitle = title; + _context.Username = username; + _context.Password = password; + _context.OldPassword = oldPassword; + _context.Notes = notes; + _context.PasswordOptions = DeserializeDictionary(dict[Constants.AppExtensionPasswordGeneratorOptionsKey] as NSDictionary); + }); + } + + private bool ProcessExtensionSetupProvider(NSItemProvider itemProvider) + { + if(itemProvider.HasItemConformingTo(Constants.UTTypeAppExtensionSetup)) + { + _context.ProviderType = Constants.UTTypeAppExtensionSetup; + return true; + } + return false; + } + + private T DeserializeDictionary(NSDictionary dict) + { + if(dict != null) + { + var jsonData = NSJsonSerialization.Serialize( + dict, NSJsonWritingOptions.PrettyPrinted, out NSError jsonError); + if(jsonData != null) + { + var jsonString = new NSString(jsonData, NSStringEncoding.UTF8); + return DeserializeString(jsonString); + } + } + return default(T); + } + + private T DeserializeString(NSString jsonString) + { + if(jsonString != null) + { + var convertedObject = JsonConvert.DeserializeObject(jsonString.ToString()); + return convertedObject; + } + return default(T); + } + + private void InitApp() + { + if(ServiceContainer.RegisteredServices.Count > 0) + { + return; + } + iOSCoreHelpers.RegisterLocalServices(); + ServiceContainer.Init(); + iOSCoreHelpers.RegisterHockeyApp(); + iOSCoreHelpers.Bootstrap(); + } + + private bool IsLocked() + { + var lockService = ServiceContainer.Resolve("lockService"); + return lockService.IsLockedAsync().GetAwaiter().GetResult(); + } + + private bool IsAuthed() + { + var userService = ServiceContainer.Resolve("userService"); + return userService.IsAuthenticatedAsync().GetAwaiter().GetResult(); + } + } +} \ No newline at end of file diff --git a/src/iOS.Extension/LoadingViewController.designer.cs b/src/iOS.Extension/LoadingViewController.designer.cs new file mode 100644 index 000000000..100bc9ef8 --- /dev/null +++ b/src/iOS.Extension/LoadingViewController.designer.cs @@ -0,0 +1,21 @@ +// WARNING +// +// This file has been generated automatically by Visual Studio from the outlets and +// actions declared in your storyboard file. +// Manual changes to this file will not be maintained. +// +using Foundation; +using System; +using System.CodeDom.Compiler; +using UIKit; + +namespace Bit.iOS.Extension +{ + [Register ("LoadingViewController")] + partial class LoadingViewController + { + void ReleaseDesignerOutlets () + { + } + } +} \ No newline at end of file diff --git a/src/iOS.Extension/LockPasswordViewController.cs b/src/iOS.Extension/LockPasswordViewController.cs new file mode 100644 index 000000000..ebdb5f654 --- /dev/null +++ b/src/iOS.Extension/LockPasswordViewController.cs @@ -0,0 +1,28 @@ +using System; +using UIKit; + +namespace Bit.iOS.Extension +{ + public partial class LockPasswordViewController : Core.Controllers.LockPasswordViewController + { + public LockPasswordViewController(IntPtr handle) : base(handle) + { } + + public LoadingViewController LoadingController { get; set; } + public override UINavigationItem BaseNavItem => NavItem; + public override UIBarButtonItem BaseCancelButton => CancelButton; + public override UIBarButtonItem BaseSubmitButton => SubmitButton; + public override Action Success => () => LoadingController.DismissLockAndContinue(); + public override Action Cancel => () => LoadingController.CompleteRequest(null); + + partial void SubmitButton_Activated(UIBarButtonItem sender) + { + var task = CheckPasswordAsync(); + } + + partial void CancelButton_Activated(UIBarButtonItem sender) + { + Cancel(); + } + } +} diff --git a/src/iOS.Extension/LockPasswordViewController.designer.cs b/src/iOS.Extension/LockPasswordViewController.designer.cs new file mode 100644 index 000000000..169e6f49d --- /dev/null +++ b/src/iOS.Extension/LockPasswordViewController.designer.cs @@ -0,0 +1,64 @@ +// WARNING +// +// This file has been generated automatically by Visual Studio from the outlets and +// actions declared in your storyboard file. +// Manual changes to this file will not be maintained. +// +using Foundation; +using System; +using System.CodeDom.Compiler; +using UIKit; + +namespace Bit.iOS.Extension +{ + [Register ("LockPasswordViewController")] + partial class LockPasswordViewController + { + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UIBarButtonItem CancelButton { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UITableView MainTableView { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UINavigationItem NavItem { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UIBarButtonItem SubmitButton { get; set; } + + [Action ("CancelButton_Activated:")] + [GeneratedCode ("iOS Designer", "1.0")] + partial void CancelButton_Activated (UIKit.UIBarButtonItem sender); + + [Action ("SubmitButton_Activated:")] + [GeneratedCode ("iOS Designer", "1.0")] + partial void SubmitButton_Activated (UIKit.UIBarButtonItem sender); + + void ReleaseDesignerOutlets () + { + if (CancelButton != null) { + CancelButton.Dispose (); + CancelButton = null; + } + + if (MainTableView != null) { + MainTableView.Dispose (); + MainTableView = null; + } + + if (NavItem != null) { + NavItem.Dispose (); + NavItem = null; + } + + if (SubmitButton != null) { + SubmitButton.Dispose (); + SubmitButton = null; + } + } + } +} \ No newline at end of file diff --git a/src/iOS.Extension/LoginAddViewController.cs b/src/iOS.Extension/LoginAddViewController.cs new file mode 100644 index 000000000..c2739f604 --- /dev/null +++ b/src/iOS.Extension/LoginAddViewController.cs @@ -0,0 +1,62 @@ +using System; +using Foundation; +using UIKit; + +namespace Bit.iOS.Extension +{ + public partial class LoginAddViewController : Core.Controllers.LoginAddViewController + { + public LoginAddViewController(IntPtr handle) + : base(handle) + { } + + public LoginListViewController LoginListController { get; set; } + public LoadingViewController LoadingController { get; set; } + + public override UINavigationItem BaseNavItem => NavItem; + public override UIBarButtonItem BaseCancelButton => CancelBarButton; + public override UIBarButtonItem BaseSaveButton => SaveBarButton; + + public override Action Success => () => + { + if(LoginListController != null) + { + LoginListController.DismissModal(); + } + else if(LoadingController != null) + { + LoadingController.CompleteUsernamePasswordRequest(UsernameCell.TextField.Text, + PasswordCell.TextField.Text, null, null); + } + }; + + partial void CancelBarButton_Activated(UIBarButtonItem sender) + { + if(LoginListController != null) + { + DismissViewController(true, null); + } + else + { + LoadingController.CompleteRequest(null); + } + } + + async partial void SaveBarButton_Activated(UIBarButtonItem sender) + { + await this.SaveAsync(); + } + + public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender) + { + if(segue.DestinationViewController is UINavigationController navController) + { + if(navController.TopViewController is PasswordGeneratorViewController passwordGeneratorController) + { + passwordGeneratorController.PasswordOptions = Context.PasswordOptions; + passwordGeneratorController.Parent = this; + } + } + } + } +} diff --git a/src/iOS.Extension/LoginAddViewController.designer.cs b/src/iOS.Extension/LoginAddViewController.designer.cs new file mode 100644 index 000000000..9e93d228e --- /dev/null +++ b/src/iOS.Extension/LoginAddViewController.designer.cs @@ -0,0 +1,55 @@ +// WARNING +// +// This file has been generated automatically by Visual Studio from the outlets and +// actions declared in your storyboard file. +// Manual changes to this file will not be maintained. +// +using Foundation; +using System; +using System.CodeDom.Compiler; +using UIKit; + +namespace Bit.iOS.Extension +{ + [Register ("LoginAddViewController")] + partial class LoginAddViewController + { + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UIBarButtonItem CancelBarButton { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UINavigationItem NavItem { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UIBarButtonItem SaveBarButton { get; set; } + + [Action ("CancelBarButton_Activated:")] + [GeneratedCode ("iOS Designer", "1.0")] + partial void CancelBarButton_Activated (UIKit.UIBarButtonItem sender); + + [Action ("SaveBarButton_Activated:")] + [GeneratedCode ("iOS Designer", "1.0")] + partial void SaveBarButton_Activated (UIKit.UIBarButtonItem sender); + + void ReleaseDesignerOutlets () + { + if (CancelBarButton != null) { + CancelBarButton.Dispose (); + CancelBarButton = null; + } + + if (NavItem != null) { + NavItem.Dispose (); + NavItem = null; + } + + if (SaveBarButton != null) { + SaveBarButton.Dispose (); + SaveBarButton = null; + } + } + } +} \ No newline at end of file diff --git a/src/iOS.Extension/LoginListViewController.cs b/src/iOS.Extension/LoginListViewController.cs new file mode 100644 index 000000000..3d23aac44 --- /dev/null +++ b/src/iOS.Extension/LoginListViewController.cs @@ -0,0 +1,197 @@ +using System; +using System.Linq; +using Bit.iOS.Extension.Models; +using Foundation; +using UIKit; +using Bit.iOS.Core.Utilities; +using Bit.iOS.Core; +using MobileCoreServices; +using Bit.iOS.Core.Controllers; +using Bit.App.Resources; +using Bit.iOS.Core.Views; +using Bit.Core.Utilities; +using Bit.Core.Abstractions; + +namespace Bit.iOS.Extension +{ + public partial class LoginListViewController : ExtendedUITableViewController + { + public LoginListViewController(IntPtr handle) + : base(handle) + { } + + public Context Context { get; set; } + public LoadingViewController LoadingController { get; set; } + + 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() + { + base.ViewDidLoad(); + NavItem.Title = AppResources.Items; + if(!CanAutoFill()) + { + CancelBarButton.Title = AppResources.Close; + } + else + { + CancelBarButton.Title = AppResources.Cancel; + } + TableView.RowHeight = UITableView.AutomaticDimension; + TableView.EstimatedRowHeight = 44; + TableView.Source = new TableSource(this); + await ((TableSource)TableView.Source).LoadItemsAsync(); + } + + public bool CanAutoFill() + { + if(Context.ProviderType != Constants.UTTypeAppExtensionFillBrowserAction + && Context.ProviderType != Constants.UTTypeAppExtensionFillWebViewAction + && Context.ProviderType != UTType.PropertyList) + { + return true; + } + return Context.Details?.HasPasswordField ?? false; + + } + + partial void CancelBarButton_Activated(UIBarButtonItem sender) + { + LoadingController.CompleteRequest(null); + } + + partial void AddBarButton_Activated(UIBarButtonItem sender) + { + PerformSegue("loginAddSegue", this); + } + + public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender) + { + if(segue.DestinationViewController is UINavigationController navController) + { + if(navController.TopViewController is LoginAddViewController addLoginController) + { + addLoginController.Context = Context; + addLoginController.LoginListController = this; + } + } + } + + public void DismissModal() + { + DismissViewController(true, async () => + { + await ((TableSource)TableView.Source).LoadItemsAsync(); + TableView.ReloadData(); + }); + } + + public class TableSource : ExtensionTableSource + { + private LoginListViewController _controller; + + public TableSource(LoginListViewController controller) + : base(controller.Context, controller) + { + _controller = controller; + } + + public override void RowSelected(UITableView tableView, NSIndexPath indexPath) + { + tableView.DeselectRow(indexPath, true); + tableView.EndEditing(true); + + if(Items == null || Items.Count() == 0) + { + _controller.PerformSegue("loginAddSegue", this); + return; + } + + var item = Items.ElementAt(indexPath.Row); + if(item == null) + { + _controller.LoadingController.CompleteRequest(null); + return; + } + + if(_controller.CanAutoFill() && !string.IsNullOrWhiteSpace(item.Password)) + { + string totp = null; + var storageService = ServiceContainer.Resolve("storageService"); + var disableTotpCopy = storageService.GetAsync( + Bit.Core.Constants.DisableAutoTotpCopyKey).GetAwaiter().GetResult(); + if(!disableTotpCopy.GetValueOrDefault(false)) + { + totp = GetTotpAsync(item).GetAwaiter().GetResult(); + } + _controller.LoadingController.CompleteUsernamePasswordRequest( + item.Username, item.Password, item.Fields, totp); + } + else if(!string.IsNullOrWhiteSpace(item.Username) || !string.IsNullOrWhiteSpace(item.Password) || + !string.IsNullOrWhiteSpace(item.Totp)) + { + var sheet = Dialogs.CreateActionSheet(item.Name, _controller); + if(!string.IsNullOrWhiteSpace(item.Username)) + { + sheet.AddAction(UIAlertAction.Create(AppResources.CopyUsername, UIAlertActionStyle.Default, a => + { + UIPasteboard clipboard = UIPasteboard.General; + clipboard.String = item.Username; + var alert = Dialogs.CreateMessageAlert(AppResources.CopyUsername); + _controller.PresentViewController(alert, true, () => + { + _controller.DismissViewController(true, null); + }); + })); + } + if(!string.IsNullOrWhiteSpace(item.Password)) + { + sheet.AddAction(UIAlertAction.Create(AppResources.CopyPassword, UIAlertActionStyle.Default, a => + { + UIPasteboard clipboard = UIPasteboard.General; + clipboard.String = item.Password; + var alert = Dialogs.CreateMessageAlert( + string.Format(AppResources.ValueHasBeenCopied, AppResources.Password)); + _controller.PresentViewController(alert, true, () => + { + _controller.DismissViewController(true, null); + }); + })); + } + if(!string.IsNullOrWhiteSpace(item.Totp)) + { + sheet.AddAction(UIAlertAction.Create(AppResources.CopyTotp, UIAlertActionStyle.Default, + async a => + { + var totp = await GetTotpAsync(item); + if(string.IsNullOrWhiteSpace(totp)) + { + return; + } + UIPasteboard clipboard = UIPasteboard.General; + clipboard.String = totp; + var alert = Dialogs.CreateMessageAlert( + string.Format(AppResources.ValueHasBeenCopied, AppResources.VerificationCodeTotp)); + _controller.PresentViewController(alert, true, () => + { + _controller.DismissViewController(true, null); + }); + })); + } + sheet.AddAction(UIAlertAction.Create(AppResources.Cancel, UIAlertActionStyle.Cancel, null)); + _controller.PresentViewController(sheet, true, null); + } + else + { + var alert = Dialogs.CreateAlert(null, AppResources.NoUsernamePasswordConfigured, AppResources.Ok); + _controller.PresentViewController(alert, true, null); + } + } + } + } +} diff --git a/src/iOS.Extension/LoginListViewController.designer.cs b/src/iOS.Extension/LoginListViewController.designer.cs new file mode 100644 index 000000000..1a074213a --- /dev/null +++ b/src/iOS.Extension/LoginListViewController.designer.cs @@ -0,0 +1,55 @@ +// WARNING +// +// This file has been generated automatically by Visual Studio from the outlets and +// actions declared in your storyboard file. +// Manual changes to this file will not be maintained. +// +using Foundation; +using System; +using System.CodeDom.Compiler; +using UIKit; + +namespace Bit.iOS.Extension +{ + [Register ("LoginListViewController")] + partial class LoginListViewController + { + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UIBarButtonItem AddBarButton { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UIBarButtonItem CancelBarButton { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UINavigationItem NavItem { get; set; } + + [Action ("AddBarButton_Activated:")] + [GeneratedCode ("iOS Designer", "1.0")] + partial void AddBarButton_Activated (UIKit.UIBarButtonItem sender); + + [Action ("CancelBarButton_Activated:")] + [GeneratedCode ("iOS Designer", "1.0")] + partial void CancelBarButton_Activated (UIKit.UIBarButtonItem sender); + + void ReleaseDesignerOutlets () + { + if (AddBarButton != null) { + AddBarButton.Dispose (); + AddBarButton = null; + } + + if (CancelBarButton != null) { + CancelBarButton.Dispose (); + CancelBarButton = null; + } + + if (NavItem != null) { + NavItem.Dispose (); + NavItem = null; + } + } + } +} \ No newline at end of file diff --git a/src/iOS.Extension/MainInterface.storyboard b/src/iOS.Extension/MainInterface.storyboard index 76eede89d..afdb9215c 100644 --- a/src/iOS.Extension/MainInterface.storyboard +++ b/src/iOS.Extension/MainInterface.storyboard @@ -1,63 +1,459 @@ - - + + - + + - - + + - + - - + + - - - + + + - - + + - - - - - - - - - - - - - + - - - - - - - + + - - - - + + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + \ No newline at end of file diff --git a/src/iOS.Extension/PasswordGeneratorViewController.cs b/src/iOS.Extension/PasswordGeneratorViewController.cs new file mode 100644 index 000000000..074f3d7bf --- /dev/null +++ b/src/iOS.Extension/PasswordGeneratorViewController.cs @@ -0,0 +1,28 @@ +using System; +using UIKit; + +namespace Bit.iOS.Extension +{ + public partial class PasswordGeneratorViewController : Core.Controllers.PasswordGeneratorViewController + { + public PasswordGeneratorViewController(IntPtr handle) + : base(handle) + { } + + public LoginAddViewController Parent { get; set; } + public override UINavigationItem BaseNavItem => NavItem; + public override UIBarButtonItem BaseCancelButton => CancelBarButton; + public override UIBarButtonItem BaseSelectBarButton => SelectBarButton; + public override UILabel BasePasswordLabel => PasswordLabel; + + partial void SelectBarButton_Activated(UIBarButtonItem sender) + { + DismissViewController(true, () => Parent.PasswordCell.TextField.Text = PasswordLabel.Text); + } + + partial void CancelBarButton_Activated(UIBarButtonItem sender) + { + DismissViewController(true, null); + } + } +} diff --git a/src/iOS.Extension/PasswordGeneratorViewController.designer.cs b/src/iOS.Extension/PasswordGeneratorViewController.designer.cs new file mode 100644 index 000000000..2b3909286 --- /dev/null +++ b/src/iOS.Extension/PasswordGeneratorViewController.designer.cs @@ -0,0 +1,82 @@ +// WARNING +// +// This file has been generated automatically by Visual Studio from the outlets and +// actions declared in your storyboard file. +// Manual changes to this file will not be maintained. +// +using Foundation; +using System; +using System.CodeDom.Compiler; +using UIKit; + +namespace Bit.iOS.Extension +{ + [Register ("PasswordGeneratorViewController")] + partial class PasswordGeneratorViewController + { + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UIView BaseView { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UIBarButtonItem CancelBarButton { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UINavigationItem NavItem { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UIView OptionsContainer { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UILabel PasswordLabel { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UIBarButtonItem SelectBarButton { get; set; } + + [Action ("CancelBarButton_Activated:")] + [GeneratedCode ("iOS Designer", "1.0")] + partial void CancelBarButton_Activated (UIKit.UIBarButtonItem sender); + + [Action ("SelectBarButton_Activated:")] + [GeneratedCode ("iOS Designer", "1.0")] + partial void SelectBarButton_Activated (UIKit.UIBarButtonItem sender); + + void ReleaseDesignerOutlets () + { + if (BaseView != null) { + BaseView.Dispose (); + BaseView = null; + } + + if (CancelBarButton != null) { + CancelBarButton.Dispose (); + CancelBarButton = null; + } + + if (NavItem != null) { + NavItem.Dispose (); + NavItem = null; + } + + if (OptionsContainer != null) { + OptionsContainer.Dispose (); + OptionsContainer = null; + } + + if (PasswordLabel != null) { + PasswordLabel.Dispose (); + PasswordLabel = null; + } + + if (SelectBarButton != null) { + SelectBarButton.Dispose (); + SelectBarButton = null; + } + } + } +} \ No newline at end of file diff --git a/src/iOS.Extension/SetupViewController.cs b/src/iOS.Extension/SetupViewController.cs new file mode 100644 index 000000000..c955ba1b9 --- /dev/null +++ b/src/iOS.Extension/SetupViewController.cs @@ -0,0 +1,47 @@ +using System; +using Bit.iOS.Extension.Models; +using UIKit; +using Bit.iOS.Core.Controllers; +using Bit.App.Resources; + +namespace Bit.iOS.Extension +{ + public partial class SetupViewController : ExtendedUIViewController + { + public SetupViewController(IntPtr handle) + : base(handle) + { } + + public Context Context { get; set; } + public LoadingViewController LoadingController { get; set; } + + 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() + { + View.BackgroundColor = new UIColor(red: 0.94f, green: 0.94f, blue: 0.96f, alpha: 1.0f); + var descriptor = UIFontDescriptor.PreferredBody; + DescriptionLabel.Text = $@"{AppResources.ExtensionSetup} + +{AppResources.ExtensionSetup2}"; + DescriptionLabel.Font = UIFont.FromDescriptor(descriptor, descriptor.PointSize); + DescriptionLabel.TextColor = new UIColor(red: 0.47f, green: 0.47f, blue: 0.47f, alpha: 1.0f); + + ActivatedLabel.Text = AppResources.ExtensionActivated; + ActivatedLabel.Font = UIFont.FromDescriptor(descriptor, descriptor.PointSize * 1.3f); + + BackButton.Title = AppResources.Back; + base.ViewDidLoad(); + } + + partial void BackButton_Activated(UIBarButtonItem sender) + { + LoadingController.CompleteRequest(null); + } + } +} diff --git a/src/iOS.Extension/SetupViewController.designer.cs b/src/iOS.Extension/SetupViewController.designer.cs new file mode 100644 index 000000000..a0e900580 --- /dev/null +++ b/src/iOS.Extension/SetupViewController.designer.cs @@ -0,0 +1,69 @@ +// WARNING +// +// This file has been generated automatically by Visual Studio from the outlets and +// actions declared in your storyboard file. +// Manual changes to this file will not be maintained. +// +using Foundation; +using System; +using System.CodeDom.Compiler; +using UIKit; + +namespace Bit.iOS.Extension +{ + [Register ("SetupViewController")] + partial class SetupViewController + { + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UILabel ActivatedLabel { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UIBarButtonItem BackButton { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UILabel DescriptionLabel { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UIImageView IconImage { get; set; } + + [Outlet] + [GeneratedCode ("iOS Designer", "1.0")] + UIKit.UINavigationItem NavItem { get; set; } + + [Action ("BackButton_Activated:")] + [GeneratedCode ("iOS Designer", "1.0")] + partial void BackButton_Activated (UIKit.UIBarButtonItem sender); + + void ReleaseDesignerOutlets () + { + if (ActivatedLabel != null) { + ActivatedLabel.Dispose (); + ActivatedLabel = null; + } + + if (BackButton != null) { + BackButton.Dispose (); + BackButton = null; + } + + if (DescriptionLabel != null) { + DescriptionLabel.Dispose (); + DescriptionLabel = null; + } + + if (IconImage != null) { + IconImage.Dispose (); + IconImage = null; + } + + if (NavItem != null) { + NavItem.Dispose (); + NavItem = null; + } + } + } +} \ No newline at end of file diff --git a/src/iOS.Extension/iOS.Extension.csproj b/src/iOS.Extension/iOS.Extension.csproj index d82db32aa..b62353171 100644 --- a/src/iOS.Extension/iOS.Extension.csproj +++ b/src/iOS.Extension/iOS.Extension.csproj @@ -64,13 +64,37 @@ + + + LoadingViewController.cs + + + + LockPasswordViewController.cs + + + + LoginAddViewController.cs + + + + LoginListViewController.cs + + + + SetupViewController.cs + + + + PasswordGeneratorViewController.cs + ActionViewController.cs @@ -91,6 +115,10 @@ {ee44c6a1-2a85-45fe-8d9b-bf1d5f88809c} App + + {4b8a8c41-9820-4341-974c-41e65b7f4366} + Core + {e71f3053-056c-4381-9638-048ed73bdff6} iOS.Core