mirror of
https://github.com/bitwarden/mobile
synced 2025-12-22 19:23:58 +00:00
[Auto Logout] Final review of feature (#932)
* Initial commit of LockService name refactor (#831) * [Auto-Logout] Update Service layer logic (#835) * Initial commit of service logic update * Added default value for action * Updated ToggleTokensAsync conditional * Removed unused variables, updated action conditional * Initial commit: lockOption/lock refactor app layer (#840) * [Auto-Logout] Settings Refactor - Application Layer Part 2 (#844) * Initial commit of app layer part 2 * Updated biometrics position * Reverted resource name refactor * LockOptions refactor revert * Updated method casing :: Removed VaultTimeout prefix for timeouts * Fixed dupe string resource (#854) * Updated dependency to use VaultTimeoutService (#896) * [Auto Logout] Xamarin Forms in AutoFill flow (iOS) (#902) * fix typo in PINRequireMasterPasswordRestart (#900) * initial commit for xf usage in autofill * Fixed databinding for hint button * Updated Two Factor page launch - removed unused imports * First pass at broadcast/messenger implentation for autofill * setting theme in extension using theme manager * extension app resources * App resources from main app * fix ref to twoFactorPage * apply resources to page * load empty app for sytling in extension * move ios renderers to ios core * static ref to resources and GetResourceColor helper * fix method ref * move application.current.resources refs to helper * switch login page alerts to device action dialogs * run on main thread * showDialog with device action service * abstract action sheet to device action service * add support for yubikey * add yubikey iimages to extension * support close button action * add support to action extension * remove empty lines Co-authored-by: Jonas Kittner <54631600+theendlessriver13@users.noreply.github.com> Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com> * [Auto Logout] Update lock option to be default value (#929) * Initial commit - make lock action default * Removed extra whitespace Co-authored-by: Jonas Kittner <54631600+theendlessriver13@users.noreply.github.com> Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com> Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
This commit is contained in:
@@ -15,7 +15,7 @@ namespace Bit.iOS.Core.Controllers
|
||||
{
|
||||
public abstract class LockPasswordViewController : ExtendedUITableViewController
|
||||
{
|
||||
private ILockService _lockService;
|
||||
private IVaultTimeoutService _vaultTimeoutService;
|
||||
private ICryptoService _cryptoService;
|
||||
private IDeviceActionService _deviceActionService;
|
||||
private IUserService _userService;
|
||||
@@ -42,7 +42,7 @@ namespace Bit.iOS.Core.Controllers
|
||||
|
||||
public override void ViewDidLoad()
|
||||
{
|
||||
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_userService = ServiceContainer.Resolve<IUserService>("userService");
|
||||
@@ -50,9 +50,9 @@ namespace Bit.iOS.Core.Controllers
|
||||
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
|
||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||
|
||||
_pinSet = _lockService.IsPinLockSetAsync().GetAwaiter().GetResult();
|
||||
_pinLock = (_pinSet.Item1 && _lockService.PinProtectedKey != null) || _pinSet.Item2;
|
||||
_fingerprintLock = _lockService.IsFingerprintLockSetAsync().GetAwaiter().GetResult();
|
||||
_pinSet = _vaultTimeoutService.IsPinLockSetAsync().GetAwaiter().GetResult();
|
||||
_pinLock = (_pinSet.Item1 && _vaultTimeoutService.PinProtectedKey != null) || _pinSet.Item2;
|
||||
_fingerprintLock = _vaultTimeoutService.IsFingerprintLockSetAsync().GetAwaiter().GetResult();
|
||||
|
||||
BaseNavItem.Title = _pinLock ? AppResources.VerifyPIN : AppResources.VerifyMasterPassword;
|
||||
BaseCancelButton.Title = AppResources.Cancel;
|
||||
@@ -125,7 +125,7 @@ namespace Bit.iOS.Core.Controllers
|
||||
{
|
||||
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
|
||||
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000),
|
||||
_lockService.PinProtectedKey);
|
||||
_vaultTimeoutService.PinProtectedKey);
|
||||
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
||||
var protectedPin = await _storageService.GetAsync<string>(Bit.Core.Constants.ProtectedPin);
|
||||
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey);
|
||||
@@ -182,7 +182,7 @@ namespace Bit.iOS.Core.Controllers
|
||||
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey);
|
||||
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email,
|
||||
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
|
||||
_lockService.PinProtectedKey = await _cryptoService.EncryptAsync(key2.Key, pinKey);
|
||||
_vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key2.Key, pinKey);
|
||||
}
|
||||
await SetKeyAndContinueAsync(key2);
|
||||
}
|
||||
@@ -205,7 +205,7 @@ namespace Bit.iOS.Core.Controllers
|
||||
|
||||
private void DoContinue()
|
||||
{
|
||||
_lockService.FingerprintLocked = false;
|
||||
_vaultTimeoutService.FingerprintLocked = false;
|
||||
MasterPasswordCell.TextField.ResignFirstResponder();
|
||||
Success();
|
||||
}
|
||||
@@ -219,7 +219,7 @@ namespace Bit.iOS.Core.Controllers
|
||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||
_pinLock ? AppResources.PIN : AppResources.MasterPassword,
|
||||
() => MasterPasswordCell.TextField.BecomeFirstResponder());
|
||||
_lockService.FingerprintLocked = !success;
|
||||
_vaultTimeoutService.FingerprintLocked = !success;
|
||||
if (success)
|
||||
{
|
||||
DoContinue();
|
||||
|
||||
53
src/iOS.Core/NFCReaderDelegate.cs
Normal file
53
src/iOS.Core/NFCReaderDelegate.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using CoreNFC;
|
||||
using Foundation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Bit.iOS.Core
|
||||
{
|
||||
public class NFCReaderDelegate : NFCNdefReaderSessionDelegate
|
||||
{
|
||||
private Regex _otpPattern = new Regex("^.*?([cbdefghijklnrtuv]{32,64})$");
|
||||
private Action<bool, string> _callback;
|
||||
|
||||
public NFCReaderDelegate(Action<bool, string> callback)
|
||||
{
|
||||
_callback = callback;
|
||||
}
|
||||
|
||||
public override void DidDetect(NFCNdefReaderSession session, NFCNdefMessage[] messages)
|
||||
{
|
||||
var results = new List<string>();
|
||||
foreach (var message in messages)
|
||||
{
|
||||
foreach (var record in message.Records)
|
||||
{
|
||||
try
|
||||
{
|
||||
results.Add(new NSString(record.Payload, NSStringEncoding.UTF8));
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
var matches = _otpPattern.Matches(result);
|
||||
if (matches.Count > 0 && matches[0].Groups.Count > 1)
|
||||
{
|
||||
var otp = matches[0].Groups[1].ToString();
|
||||
_callback.Invoke(true, otp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_callback.Invoke(false, "No tags were read.");
|
||||
}
|
||||
|
||||
public override void DidInvalidate(NFCNdefReaderSession session, NSError error)
|
||||
{
|
||||
_callback.Invoke(false, error?.LocalizedDescription);
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/iOS.Core/Renderers/CustomButtonRenderer.cs
Normal file
40
src/iOS.Core/Renderers/CustomButtonRenderer.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.ComponentModel;
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using Bit.iOS.Core.Utilities;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(Button), typeof(CustomButtonRenderer))]
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class CustomButtonRenderer : ButtonRenderer
|
||||
{
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if (Control != null && e.NewElement is Button)
|
||||
{
|
||||
UpdateFont();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
if (e.PropertyName == Button.FontProperty.PropertyName)
|
||||
{
|
||||
UpdateFont();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFont()
|
||||
{
|
||||
var pointSize = iOSHelpers.GetAccessibleFont<Button>(Element.FontSize);
|
||||
if (pointSize != null)
|
||||
{
|
||||
Control.Font = UIFont.FromDescriptor(Element.Font.ToUIFont().FontDescriptor, pointSize.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/iOS.Core/Renderers/CustomContentPageRenderer.cs
Normal file
63
src/iOS.Core/Renderers/CustomContentPageRenderer.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(ContentPage), typeof(CustomContentPageRenderer))]
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class CustomContentPageRenderer : PageRenderer
|
||||
{
|
||||
public override void ViewWillAppear(bool animated)
|
||||
{
|
||||
base.ViewWillAppear(animated);
|
||||
if (!(Element is ContentPage contentPage) || NavigationController == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide bottom line under nav bar
|
||||
var navBar = NavigationController.NavigationBar;
|
||||
if (navBar != null)
|
||||
{
|
||||
navBar.SetValueForKey(FromObject(true), new Foundation.NSString("hidesShadow"));
|
||||
}
|
||||
|
||||
var navigationItem = NavigationController.TopViewController.NavigationItem;
|
||||
var leftNativeButtons = (navigationItem.LeftBarButtonItems ?? new UIBarButtonItem[] { }).ToList();
|
||||
var rightNativeButtons = (navigationItem.RightBarButtonItems ?? new UIBarButtonItem[] { }).ToList();
|
||||
var newLeftButtons = new List<UIBarButtonItem>();
|
||||
var newRightButtons = new List<UIBarButtonItem>();
|
||||
foreach (var nativeItem in rightNativeButtons)
|
||||
{
|
||||
// Use reflection to get Xamarin private field "_item"
|
||||
var field = nativeItem.GetType().GetField("_item", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (field == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!(field.GetValue(nativeItem) is ToolbarItem info))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (info.Priority < 0)
|
||||
{
|
||||
newLeftButtons.Add(nativeItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
newRightButtons.Add(nativeItem);
|
||||
}
|
||||
}
|
||||
foreach (var nativeItem in leftNativeButtons)
|
||||
{
|
||||
newLeftButtons.Add(nativeItem);
|
||||
}
|
||||
navigationItem.RightBarButtonItems = newRightButtons.ToArray();
|
||||
navigationItem.LeftBarButtonItems = newLeftButtons.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
49
src/iOS.Core/Renderers/CustomEditorRenderer.cs
Normal file
49
src/iOS.Core/Renderers/CustomEditorRenderer.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using System.ComponentModel;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(Editor), typeof(CustomEditorRenderer))]
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class CustomEditorRenderer : EditorRenderer
|
||||
{
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if (e.NewElement is Editor)
|
||||
{
|
||||
var descriptor = UIFontDescriptor.PreferredBody;
|
||||
Control.Font = UIFont.FromDescriptor(descriptor, descriptor.PointSize);
|
||||
// Remove padding
|
||||
Control.TextContainerInset = new UIEdgeInsets(0, 0, 0, 0);
|
||||
Control.TextContainer.LineFragmentPadding = 0;
|
||||
UpdateTintColor();
|
||||
UpdateKeyboardAppearance();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
if (e.PropertyName == Editor.TextColorProperty.PropertyName)
|
||||
{
|
||||
UpdateTintColor();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTintColor()
|
||||
{
|
||||
Control.TintColor = Element.TextColor.ToUIColor();
|
||||
}
|
||||
|
||||
private void UpdateKeyboardAppearance()
|
||||
{
|
||||
if (!Utilities.ThemeHelpers.LightTheme)
|
||||
{
|
||||
Control.KeyboardAppearance = UIKeyboardAppearance.Dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
70
src/iOS.Core/Renderers/CustomEntryRenderer.cs
Normal file
70
src/iOS.Core/Renderers/CustomEntryRenderer.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System.ComponentModel;
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using Bit.iOS.Core.Utilities;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(Entry), typeof(CustomEntryRenderer))]
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class CustomEntryRenderer : EntryRenderer
|
||||
{
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if (Control != null && e.NewElement is Entry)
|
||||
{
|
||||
Control.ClearButtonMode = UITextFieldViewMode.WhileEditing;
|
||||
UpdateTintColor();
|
||||
UpdateFontSize();
|
||||
UpdateKeyboardAppearance();
|
||||
iOSHelpers.SetBottomBorder(Control);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
if (e.PropertyName == Entry.FontAttributesProperty.PropertyName ||
|
||||
e.PropertyName == Entry.FontFamilyProperty.PropertyName ||
|
||||
e.PropertyName == Entry.FontSizeProperty.PropertyName)
|
||||
{
|
||||
UpdateFontSize();
|
||||
}
|
||||
else if (e.PropertyName == Entry.TextColorProperty.PropertyName)
|
||||
{
|
||||
UpdateTintColor();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFontSize()
|
||||
{
|
||||
var pointSize = iOSHelpers.GetAccessibleFont<Entry>(Element.FontSize);
|
||||
if (pointSize != null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(Element.FontFamily))
|
||||
{
|
||||
Control.Font = UIFont.FromName(Element.FontFamily, pointSize.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Control.Font = UIFont.FromDescriptor(UIFontDescriptor.PreferredBody, pointSize.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTintColor()
|
||||
{
|
||||
Control.TintColor = Element.TextColor.ToUIColor();
|
||||
}
|
||||
|
||||
private void UpdateKeyboardAppearance()
|
||||
{
|
||||
if (!ThemeHelpers.LightTheme)
|
||||
{
|
||||
Control.KeyboardAppearance = UIKeyboardAppearance.Dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/iOS.Core/Renderers/CustomLabelRenderer.cs
Normal file
42
src/iOS.Core/Renderers/CustomLabelRenderer.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.ComponentModel;
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using Bit.iOS.Core.Utilities;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(Label), typeof(CustomLabelRenderer))]
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class CustomLabelRenderer : LabelRenderer
|
||||
{
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if (Control != null && e.NewElement is Label)
|
||||
{
|
||||
UpdateFont();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
if (e.PropertyName == Label.FontProperty.PropertyName ||
|
||||
e.PropertyName == Label.TextProperty.PropertyName ||
|
||||
e.PropertyName == Label.FormattedTextProperty.PropertyName)
|
||||
{
|
||||
UpdateFont();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFont()
|
||||
{
|
||||
var pointSize = iOSHelpers.GetAccessibleFont<Label>(Element.FontSize);
|
||||
if (pointSize != null)
|
||||
{
|
||||
Control.Font = UIFont.FromDescriptor(Element.Font.ToUIFont().FontDescriptor, pointSize.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/iOS.Core/Renderers/CustomPickerRenderer.cs
Normal file
32
src/iOS.Core/Renderers/CustomPickerRenderer.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using Bit.iOS.Core.Utilities;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(Picker), typeof(CustomPickerRenderer))]
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class CustomPickerRenderer : PickerRenderer
|
||||
{
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if (e.NewElement is Picker)
|
||||
{
|
||||
var descriptor = UIFontDescriptor.PreferredBody;
|
||||
Control.Font = UIFont.FromDescriptor(descriptor, descriptor.PointSize);
|
||||
iOSHelpers.SetBottomBorder(Control);
|
||||
UpdateKeyboardAppearance();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateKeyboardAppearance()
|
||||
{
|
||||
if (!ThemeHelpers.LightTheme)
|
||||
{
|
||||
Control.KeyboardAppearance = UIKeyboardAppearance.Dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/iOS.Core/Renderers/CustomSearchBarRenderer.cs
Normal file
28
src/iOS.Core/Renderers/CustomSearchBarRenderer.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(SearchBar), typeof(CustomSearchBarRenderer))]
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class CustomSearchBarRenderer : SearchBarRenderer
|
||||
{
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if (e.NewElement is SearchBar)
|
||||
{
|
||||
UpdateKeyboardAppearance();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateKeyboardAppearance()
|
||||
{
|
||||
if (!Utilities.ThemeHelpers.LightTheme)
|
||||
{
|
||||
Control.KeyboardAppearance = UIKeyboardAppearance.Dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/iOS.Core/Renderers/CustomTabbedRenderer.cs
Normal file
17
src/iOS.Core/Renderers/CustomTabbedRenderer.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedRenderer))]
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class CustomTabbedRenderer : TabbedRenderer
|
||||
{
|
||||
protected override void OnElementChanged(VisualElementChangedEventArgs e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
TabBar.Translucent = false;
|
||||
TabBar.Opaque = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/iOS.Core/Renderers/CustomViewCellRenderer.cs
Normal file
29
src/iOS.Core/Renderers/CustomViewCellRenderer.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Bit.App.Utilities;
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
[assembly: ExportRenderer(typeof(ViewCell), typeof(CustomViewCellRenderer))]
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class CustomViewCellRenderer : ViewCellRenderer
|
||||
{
|
||||
private bool _noSelectionStyle = false;
|
||||
|
||||
public CustomViewCellRenderer()
|
||||
{
|
||||
_noSelectionStyle = ThemeManager.GetResourceColor("BackgroundColor") != Color.White;
|
||||
}
|
||||
|
||||
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
|
||||
{
|
||||
var cell = base.GetCell(item, reusableCell, tv);
|
||||
if (_noSelectionStyle)
|
||||
{
|
||||
cell.SelectionStyle = UITableViewCellSelectionStyle.None;
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
}
|
||||
64
src/iOS.Core/Renderers/HybridWebViewRenderer.cs
Normal file
64
src/iOS.Core/Renderers/HybridWebViewRenderer.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using Foundation;
|
||||
using WebKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
using Bit.App.Controls;
|
||||
using Bit.iOS.Core.Renderers;
|
||||
using System.ComponentModel;
|
||||
|
||||
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
|
||||
namespace Bit.iOS.Core.Renderers
|
||||
{
|
||||
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>, IWKScriptMessageHandler
|
||||
{
|
||||
private const string JSFunction =
|
||||
"function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
|
||||
|
||||
private WKUserContentController _userController;
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
if (Control == null)
|
||||
{
|
||||
_userController = new WKUserContentController();
|
||||
var script = new WKUserScript(new NSString(JSFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
|
||||
_userController.AddUserScript(script);
|
||||
_userController.AddScriptMessageHandler(this, "invokeAction");
|
||||
|
||||
var config = new WKWebViewConfiguration { UserContentController = _userController };
|
||||
var webView = new WKWebView(Frame, config);
|
||||
SetNativeControl(webView);
|
||||
}
|
||||
if (e.OldElement != null)
|
||||
{
|
||||
_userController.RemoveAllUserScripts();
|
||||
_userController.RemoveScriptMessageHandler("invokeAction");
|
||||
var hybridWebView = e.OldElement as HybridWebView;
|
||||
hybridWebView.Cleanup();
|
||||
}
|
||||
if (e.NewElement != null)
|
||||
{
|
||||
if (Element.Uri != null)
|
||||
{
|
||||
Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.Uri)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
if (e.PropertyName == HybridWebView.UriProperty.PropertyName && Element.Uri != null)
|
||||
{
|
||||
Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.Uri)));
|
||||
}
|
||||
}
|
||||
|
||||
public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
|
||||
{
|
||||
Element.InvokeAction(message.Body.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -347,6 +347,45 @@ namespace Bit.iOS.Core.Services
|
||||
return result.Task;
|
||||
}
|
||||
|
||||
public Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction,
|
||||
params string[] buttons)
|
||||
{
|
||||
if (Application.Current is App.App app && app.Options != null && !app.Options.EmptyApp)
|
||||
{
|
||||
return app.MainPage.DisplayActionSheet(title, cancel, destruction, buttons);
|
||||
}
|
||||
var result = new TaskCompletionSource<string>();
|
||||
var vc = GetPresentedViewController();
|
||||
var sheet = UIAlertController.Create(title, null, UIAlertControllerStyle.ActionSheet);
|
||||
if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad)
|
||||
{
|
||||
var x = vc.View.Bounds.Width / 2;
|
||||
var y = vc.View.Bounds.Bottom;
|
||||
var rect = new CGRect(x, y, 0, 0);
|
||||
|
||||
sheet.PopoverPresentationController.SourceView = vc.View;
|
||||
sheet.PopoverPresentationController.SourceRect = rect;
|
||||
sheet.PopoverPresentationController.PermittedArrowDirections = UIPopoverArrowDirection.Unknown;
|
||||
}
|
||||
foreach (var button in buttons)
|
||||
{
|
||||
sheet.AddAction(UIAlertAction.Create(button, UIAlertActionStyle.Default,
|
||||
x => result.TrySetResult(button)));
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(destruction))
|
||||
{
|
||||
sheet.AddAction(UIAlertAction.Create(destruction, UIAlertActionStyle.Destructive,
|
||||
x => result.TrySetResult(destruction)));
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(cancel))
|
||||
{
|
||||
sheet.AddAction(UIAlertAction.Create(cancel, UIAlertActionStyle.Cancel,
|
||||
x => result.TrySetResult(cancel)));
|
||||
}
|
||||
vc.PresentViewController(sheet, true, null);
|
||||
return result.Task;
|
||||
}
|
||||
|
||||
public void Autofill(CipherView cipher)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
@@ -15,8 +15,8 @@ namespace Bit.iOS.Core.Utilities
|
||||
if (await AutofillEnabled())
|
||||
{
|
||||
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
var lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
if (await lockService.IsLockedAsync())
|
||||
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
if (await vaultTimeoutService.IsLockedAsync())
|
||||
{
|
||||
await storageService.SaveAsync(Constants.AutofillNeedsIdentityReplacementKey, true);
|
||||
return;
|
||||
|
||||
@@ -27,12 +27,18 @@ namespace Bit.iOS.Core.Utilities
|
||||
return alert;
|
||||
}
|
||||
|
||||
public static UIAlertController CreateAlert(string title, string message, string accept, Action<UIAlertAction> acceptHandle = null)
|
||||
public static UIAlertController CreateAlert(string title, string message, string accept,
|
||||
Action<UIAlertAction> acceptHandle = null, string cancel = null, Action<UIAlertAction> cancelHandle = null)
|
||||
{
|
||||
var alert = UIAlertController.Create(title, message, UIAlertControllerStyle.Alert);
|
||||
var oldFrame = alert.View.Frame;
|
||||
alert.View.Frame = new RectangleF((float)oldFrame.X, (float)oldFrame.Y, (float)oldFrame.Width, (float)oldFrame.Height - 20);
|
||||
alert.View.Frame = new RectangleF((float)oldFrame.X, (float)oldFrame.Y, (float)oldFrame.Width,
|
||||
(float)oldFrame.Height - 20);
|
||||
alert.AddAction(UIAlertAction.Create(accept, UIAlertActionStyle.Default, acceptHandle));
|
||||
if (!string.IsNullOrWhiteSpace(cancel))
|
||||
{
|
||||
alert.AddAction(UIAlertAction.Create(cancel, UIAlertActionStyle.Default, cancelHandle));
|
||||
}
|
||||
return alert;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Services;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.iOS.Core.Services;
|
||||
using CoreNFC;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
|
||||
@@ -68,6 +71,8 @@ namespace Bit.iOS.Core.Utilities
|
||||
{
|
||||
(ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService).Init();
|
||||
ServiceContainer.Resolve<IAuthService>("authService").Init();
|
||||
(ServiceContainer.
|
||||
Resolve<IPlatformUtilsService>("platformUtilsService") as MobilePlatformUtilsService).Init();
|
||||
// Note: This is not awaited
|
||||
var bootstrapTask = BootstrapAsync(postBootstrapFunc);
|
||||
}
|
||||
@@ -79,6 +84,54 @@ namespace Bit.iOS.Core.Utilities
|
||||
UIApplication.SharedApplication.StatusBarStyle = UIStatusBarStyle.LightContent;
|
||||
}
|
||||
|
||||
public static void SubscribeBroadcastReceiver(UIViewController controller, NFCNdefReaderSession nfcSession,
|
||||
NFCReaderDelegate nfcDelegate)
|
||||
{
|
||||
var broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
broadcasterService.Subscribe(nameof(controller), (message) =>
|
||||
{
|
||||
if (message.Command == "showDialog")
|
||||
{
|
||||
var details = message.Data as DialogDetails;
|
||||
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
|
||||
AppResources.Ok : details.ConfirmText;
|
||||
|
||||
NSRunLoop.Main.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
var result = await deviceActionService.DisplayAlertAsync(details.Title, details.Text,
|
||||
details.CancelText, details.ConfirmText);
|
||||
var confirmed = result == details.ConfirmText;
|
||||
messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
|
||||
});
|
||||
}
|
||||
else if (message.Command == "listenYubiKeyOTP")
|
||||
{
|
||||
ListenYubiKey((bool)message.Data, deviceActionService, nfcSession, nfcDelegate);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void ListenYubiKey(bool listen, IDeviceActionService deviceActionService,
|
||||
NFCNdefReaderSession nfcSession, NFCReaderDelegate nfcDelegate)
|
||||
{
|
||||
if (deviceActionService.SupportsNfc())
|
||||
{
|
||||
nfcSession?.InvalidateSession();
|
||||
nfcSession?.Dispose();
|
||||
nfcSession = null;
|
||||
if (listen)
|
||||
{
|
||||
nfcSession = new NFCNdefReaderSession(nfcDelegate, null, true)
|
||||
{
|
||||
AlertMessage = AppResources.HoldYubikeyNearTop
|
||||
};
|
||||
nfcSession.BeginSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task BootstrapAsync(Func<Task> postBootstrapFunc = null)
|
||||
{
|
||||
var disableFavicon = await ServiceContainer.Resolve<IStorageService>("storageService").GetAsync<bool?>(
|
||||
|
||||
64
src/iOS.Core/Utilities/iOSHelpers.cs
Normal file
64
src/iOS.Core/Utilities/iOSHelpers.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using Bit.App.Utilities;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.iOS;
|
||||
|
||||
namespace Bit.iOS.Core.Utilities
|
||||
{
|
||||
public static class iOSHelpers
|
||||
{
|
||||
public static System.nfloat? GetAccessibleFont<T>(double size)
|
||||
{
|
||||
var pointSize = UIFontDescriptor.PreferredBody.PointSize;
|
||||
if (size == Device.GetNamedSize(NamedSize.Large, typeof(T)))
|
||||
{
|
||||
pointSize *= 1.3f;
|
||||
}
|
||||
else if (size == Device.GetNamedSize(NamedSize.Small, typeof(T)))
|
||||
{
|
||||
pointSize *= .8f;
|
||||
}
|
||||
else if (size == Device.GetNamedSize(NamedSize.Micro, typeof(T)))
|
||||
{
|
||||
pointSize *= .6f;
|
||||
}
|
||||
else if (size != Device.GetNamedSize(NamedSize.Default, typeof(T)))
|
||||
{
|
||||
// not using dynamic font sizes, return
|
||||
return null;
|
||||
}
|
||||
return pointSize;
|
||||
}
|
||||
|
||||
public static void SetBottomBorder(UITextField control)
|
||||
{
|
||||
control.BorderStyle = UITextBorderStyle.None;
|
||||
SetBottomBorder(control as UIView);
|
||||
}
|
||||
|
||||
public static void SetBottomBorder(UITextView control)
|
||||
{
|
||||
SetBottomBorder(control as UIView);
|
||||
}
|
||||
|
||||
private static void SetBottomBorder(UIView control)
|
||||
{
|
||||
var borderLine = new UIView
|
||||
{
|
||||
BackgroundColor = ThemeManager.GetResourceColor("BoxBorderColor").ToUIColor(),
|
||||
TranslatesAutoresizingMaskIntoConstraints = false
|
||||
};
|
||||
control.AddSubview(borderLine);
|
||||
control.AddConstraints(new NSLayoutConstraint[]
|
||||
{
|
||||
NSLayoutConstraint.Create(borderLine, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1, 1f),
|
||||
NSLayoutConstraint.Create(borderLine, NSLayoutAttribute.Leading, NSLayoutRelation.Equal,
|
||||
control, NSLayoutAttribute.Leading, 1, 0),
|
||||
NSLayoutConstraint.Create(borderLine, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal,
|
||||
control, NSLayoutAttribute.Trailing, 1, 0),
|
||||
NSLayoutConstraint.Create(borderLine, NSLayoutAttribute.Top, NSLayoutRelation.Equal,
|
||||
control, NSLayoutAttribute.Bottom, 1, 10f),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Foundation;
|
||||
using Bit.App.Utilities;
|
||||
using Foundation;
|
||||
using System;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
@@ -19,8 +20,8 @@ namespace Bit.iOS.Core.Views
|
||||
{
|
||||
TranslatesAutoresizingMaskIntoConstraints = false;
|
||||
var bgColor = UIColor.DarkGray;
|
||||
var nordTheme = Application.Current?.Resources != null &&
|
||||
((Color)Application.Current.Resources["BackgroundColor"]) == Color.FromHex("#3b4252");
|
||||
var nordTheme = ThemeManager.Resources() != null &&
|
||||
ThemeManager.GetResourceColor("BackgroundColor") == Color.FromHex("#3b4252");
|
||||
if (nordTheme)
|
||||
{
|
||||
bgColor = Color.FromHex("#4c566a").ToUIColor();
|
||||
|
||||
@@ -88,6 +88,17 @@
|
||||
<Compile Include="Models\FillScript.cs" />
|
||||
<Compile Include="Models\PageDetails.cs" />
|
||||
<Compile Include="Models\PasswordGenerationOptions.cs" />
|
||||
<Compile Include="NFCReaderDelegate.cs" />
|
||||
<Compile Include="Renderers\CustomButtonRenderer.cs" />
|
||||
<Compile Include="Renderers\CustomContentPageRenderer.cs" />
|
||||
<Compile Include="Renderers\CustomEditorRenderer.cs" />
|
||||
<Compile Include="Renderers\CustomEntryRenderer.cs" />
|
||||
<Compile Include="Renderers\CustomLabelRenderer.cs" />
|
||||
<Compile Include="Renderers\CustomPickerRenderer.cs" />
|
||||
<Compile Include="Renderers\CustomSearchBarRenderer.cs" />
|
||||
<Compile Include="Renderers\CustomTabbedRenderer.cs" />
|
||||
<Compile Include="Renderers\CustomViewCellRenderer.cs" />
|
||||
<Compile Include="Renderers\HybridWebViewRenderer.cs" />
|
||||
<Compile Include="Services\DeviceActionService.cs" />
|
||||
<Compile Include="Utilities\ASHelpers.cs" />
|
||||
<Compile Include="Utilities\Dialogs.cs" />
|
||||
@@ -97,6 +108,7 @@
|
||||
<Compile Include="Services\KeyChainStorageService.cs" />
|
||||
<Compile Include="Services\LocalizeService.cs" />
|
||||
<Compile Include="Utilities\iOSCoreHelpers.cs" />
|
||||
<Compile Include="Utilities\iOSHelpers.cs" />
|
||||
<Compile Include="Utilities\ThemeHelpers.cs" />
|
||||
<Compile Include="Views\ExtensionSearchDelegate.cs" />
|
||||
<Compile Include="Views\ExtensionTableSource.cs" />
|
||||
|
||||
Reference in New Issue
Block a user