diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj
index 317078f61..bc82b71de 100644
--- a/src/Android/Android.csproj
+++ b/src/Android/Android.csproj
@@ -297,6 +297,21 @@
..\..\packages\XLabs.IoC.SimpleInjector.2.0.5782\lib\portable-net45+netcore45+wp8+MonoAndroid1+MonoTouch1\XLabs.Ioc.SimpleInjector.dll
+
+ ..\..\packages\ZXing.Net.Mobile.2.1.47\lib\MonoAndroid403\ZXing.Net.Mobile.Core.dll
+
+
+ ..\..\packages\ZXing.Net.Mobile.Forms.2.1.47\lib\MonoAndroid403\ZXing.Net.Mobile.Forms.dll
+
+
+ ..\..\packages\ZXing.Net.Mobile.Forms.2.1.47\lib\MonoAndroid403\ZXing.Net.Mobile.Forms.Android.dll
+
+
+ ..\..\packages\ZXing.Net.Mobile.2.1.47\lib\MonoAndroid403\zxing.portable.dll
+
+
+ ..\..\packages\ZXing.Net.Mobile.2.1.47\lib\MonoAndroid403\ZXingNetMobile.dll
+
diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs
index 50fe0b678..91817fd5d 100644
--- a/src/Android/MainActivity.cs
+++ b/src/Android/MainActivity.cs
@@ -199,6 +199,11 @@ namespace Bit.Android
ParseYubiKey(intent.DataString);
}
+ public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
+ {
+ ZXing.Net.Mobile.Forms.Android.PermissionsHandler.OnRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+
public void RateApp()
{
try
diff --git a/src/Android/MainApplication.cs b/src/Android/MainApplication.cs
index 485a2bca3..048da8bd5 100644
--- a/src/Android/MainApplication.cs
+++ b/src/Android/MainApplication.cs
@@ -188,6 +188,7 @@ namespace Bit.Android
{
UserDialogs.Init(application);
CachedImageRenderer.Init();
+ ZXing.Net.Mobile.Forms.Android.Platform.Init();
CrossFingerprint.SetCurrentActivityResolver(() => CrossCurrentActivity.Current.Activity);
//var container = new UnityContainer();
diff --git a/src/Android/Resources/Resource.Designer.cs b/src/Android/Resources/Resource.Designer.cs
index 696cd701a..7891cb06b 100644
--- a/src/Android/Resources/Resource.Designer.cs
+++ b/src/Android/Resources/Resource.Designer.cs
@@ -193,6 +193,12 @@ namespace Bit.Android
global::Plugin.Fingerprint.Resource.Layout.FingerprintDialog = global::Bit.Android.Resource.Layout.FingerprintDialog;
global::Splat.Resource.String.library_name = global::Bit.Android.Resource.String.library_name;
global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarSize = global::Bit.Android.Resource.Attribute.actionBarSize;
+ global::ZXing.Net.Mobile.Forms.Android.Resource.Layout.zxingscanneractivitylayout = global::Bit.Android.Resource.Layout.zxingscanneractivitylayout;
+ global::ZXing.Net.Mobile.Forms.Android.Resource.Layout.zxingscannerfragmentlayout = global::Bit.Android.Resource.Layout.zxingscannerfragmentlayout;
+ global::ZXing.Net.Mobile.Forms.Android.Resource.String.library_name = global::Bit.Android.Resource.String.library_name;
+ global::ZXing.Mobile.Resource.Id.contentFrame = global::Bit.Android.Resource.Id.contentFrame;
+ global::ZXing.Mobile.Resource.Layout.zxingscanneractivitylayout = global::Bit.Android.Resource.Layout.zxingscanneractivitylayout;
+ global::ZXing.Mobile.Resource.Layout.zxingscannerfragmentlayout = global::Bit.Android.Resource.Layout.zxingscannerfragmentlayout;
}
public partial class Animation
@@ -2928,6 +2934,9 @@ namespace Bit.Android
// aapt resource value: 0x7f0c0027
public const int collapseActionView = 2131492903;
+ // aapt resource value: 0x7f0c00c6
+ public const int contentFrame = 2131493062;
+
// aapt resource value: 0x7f0c0052
public const int contentPanel = 2131492946;
@@ -3684,6 +3693,12 @@ namespace Bit.Android
// aapt resource value: 0x7f030042
public const int toolbar = 2130903106;
+ // aapt resource value: 0x7f030043
+ public const int zxingscanneractivitylayout = 2130903107;
+
+ // aapt resource value: 0x7f030044
+ public const int zxingscannerfragmentlayout = 2130903108;
+
static Layout()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
diff --git a/src/Android/packages.config b/src/Android/packages.config
index 36bf933be..9a8c14f86 100644
--- a/src/Android/packages.config
+++ b/src/Android/packages.config
@@ -92,4 +92,6 @@
+
+
\ No newline at end of file
diff --git a/src/App/App.csproj b/src/App/App.csproj
index 31b1ae484..81fac9719 100644
--- a/src/App/App.csproj
+++ b/src/App/App.csproj
@@ -143,6 +143,7 @@
+
@@ -495,6 +496,18 @@
..\..\packages\XLabs.IoC.2.0.5782\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1+Xamarin.iOS10\XLabs.Ioc.dll
True
+
+ ..\..\packages\ZXing.Net.Mobile.2.1.47\lib\portable-net45+netcore45+wpa81+wp8\ZXing.Net.Mobile.Core.dll
+
+
+ ..\..\packages\ZXing.Net.Mobile.Forms.2.1.47\lib\portable-net45+netcore45+wpa81+wp8\ZXing.Net.Mobile.Forms.dll
+
+
+ ..\..\packages\ZXing.Net.Mobile.2.1.47\lib\portable-net45+netcore45+wpa81+wp8\zxing.portable.dll
+
+
+ ..\..\packages\ZXing.Net.Mobile.2.1.47\lib\portable-net45+netcore45+wpa81+wp8\ZXingNetMobile.dll
+
diff --git a/src/App/Pages/ScanPage.cs b/src/App/Pages/ScanPage.cs
new file mode 100644
index 000000000..51bff9218
--- /dev/null
+++ b/src/App/Pages/ScanPage.cs
@@ -0,0 +1,148 @@
+using Bit.App.Controls;
+using Bit.App.Resources;
+using System;
+using System.Collections.Generic;
+using Xamarin.Forms;
+using ZXing.Net.Mobile.Forms;
+
+namespace Bit.App.Pages
+{
+ public class ScanPage : ExtendedContentPage
+ {
+ private readonly ZXingScannerView _zxing;
+ private readonly OverlayGrid _overlay;
+
+ public ScanPage(Action callback)
+ : base(updateActivity: false)
+ {
+ _zxing = new ZXingScannerView
+ {
+ HorizontalOptions = LayoutOptions.FillAndExpand,
+ VerticalOptions = LayoutOptions.FillAndExpand,
+ AutomationId = "zxingScannerView",
+ Options = new ZXing.Mobile.MobileBarcodeScanningOptions
+ {
+ UseNativeScanning = true,
+ PossibleFormats = new List { ZXing.BarcodeFormat.QR_CODE },
+ AutoRotate = false
+ }
+ };
+
+ _zxing.OnScanResult += (result) =>
+ {
+ // Stop analysis until we navigate away so we don't keep reading barcodes
+ _zxing.IsAnalyzing = false;
+ _zxing.IsScanning = false;
+
+ Uri uri;
+ if(!string.IsNullOrWhiteSpace(result.Text) && Uri.TryCreate(result.Text, UriKind.Absolute, out uri) &&
+ !string.IsNullOrWhiteSpace(uri.Query))
+ {
+ var queryParts = uri.Query.Substring(1).ToLowerInvariant().Split('&');
+ foreach(var part in queryParts)
+ {
+ if(part.StartsWith("secret="))
+ {
+ callback(part.Substring(7)?.ToUpperInvariant());
+ return;
+ }
+ }
+ }
+
+ callback(null);
+ };
+
+ _overlay = new OverlayGrid
+ {
+ AutomationId = "zxingDefaultOverlay"
+ };
+
+ _overlay.TopLabel.Text = AppResources.CameraInstructionTop;
+ _overlay.BottomLabel.Text = AppResources.CameraInstructionBottom;
+
+ var grid = new Grid
+ {
+ VerticalOptions = LayoutOptions.FillAndExpand,
+ HorizontalOptions = LayoutOptions.FillAndExpand,
+ Children = { _zxing, _overlay }
+ };
+
+ if(Device.RuntimePlatform == Device.iOS)
+ {
+ ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close));
+ }
+
+ Title = AppResources.ScanQrTitle;
+ Content = grid;
+ }
+
+ protected override void OnAppearing()
+ {
+ base.OnAppearing();
+ _zxing.IsScanning = true;
+ }
+
+ protected override void OnDisappearing()
+ {
+ _zxing.IsScanning = false;
+ base.OnDisappearing();
+ }
+
+ public class OverlayGrid : Grid
+ {
+ public OverlayGrid()
+ {
+ VerticalOptions = LayoutOptions.FillAndExpand;
+ HorizontalOptions = LayoutOptions.FillAndExpand;
+
+ RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
+ RowDefinitions.Add(new RowDefinition { Height = new GridLength(2, GridUnitType.Star) });
+ RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
+
+ Children.Add(new BoxView
+ {
+ VerticalOptions = LayoutOptions.Fill,
+ HorizontalOptions = LayoutOptions.FillAndExpand,
+ BackgroundColor = Color.Black,
+ Opacity = 0.7,
+ }, 0, 0);
+
+ Children.Add(new BoxView
+ {
+ VerticalOptions = LayoutOptions.Center,
+ HorizontalOptions = LayoutOptions.FillAndExpand,
+ BackgroundColor = Color.Transparent
+ }, 0, 1);
+
+ Children.Add(new BoxView
+ {
+ VerticalOptions = LayoutOptions.Fill,
+ HorizontalOptions = LayoutOptions.FillAndExpand,
+ BackgroundColor = Color.Black,
+ Opacity = 0.7,
+ }, 0, 2);
+
+ TopLabel = new Label
+ {
+ VerticalOptions = LayoutOptions.Center,
+ HorizontalOptions = LayoutOptions.Center,
+ TextColor = Color.White,
+ AutomationId = "zxingDefaultOverlay_TopTextLabel",
+ };
+ Children.Add(TopLabel, 0, 0);
+
+ BottomLabel = new Label
+ {
+ VerticalOptions = LayoutOptions.Center,
+ HorizontalOptions = LayoutOptions.Center,
+ TextColor = Color.White,
+ AutomationId = "zxingDefaultOverlay_BottomTextLabel",
+ };
+ Children.Add(BottomLabel, 0, 2);
+ }
+
+ public Label TopLabel { get; set; }
+ public Label BottomLabel { get; set; }
+ }
+ }
+}
diff --git a/src/App/Pages/Vault/VaultAddLoginPage.cs b/src/App/Pages/Vault/VaultAddLoginPage.cs
index 296e1c95c..be92cf2a2 100644
--- a/src/App/Pages/Vault/VaultAddLoginPage.cs
+++ b/src/App/Pages/Vault/VaultAddLoginPage.cs
@@ -276,9 +276,26 @@ namespace Bit.App.Pages
PasswordCell.Button.Image = "eye" + (!PasswordCell.Entry.IsPasswordFromToggled ? "_slash" : string.Empty);
}
- private void TotpButton_Clicked(object sender, EventArgs e)
+ private async void TotpButton_Clicked(object sender, EventArgs e)
{
- // launch camera
+ var scanPage = new ScanPage((key) =>
+ {
+ Device.BeginInvokeOnMainThread(async () =>
+ {
+ await Navigation.PopModalAsync();
+ if(!string.IsNullOrWhiteSpace(key))
+ {
+ TotpCell.Entry.Text = key;
+ _userDialogs.Toast(AppResources.AuthenticatorKeyAdded);
+ }
+ else
+ {
+ _userDialogs.Alert(AppResources.AuthenticatorKeyReadError);
+ }
+ });
+ });
+
+ await Navigation.PushModalAsync(new ExtendedNavigationPage(scanPage));
}
private async void GenerateCell_Tapped(object sender, EventArgs e)
diff --git a/src/App/Pages/Vault/VaultEditLoginPage.cs b/src/App/Pages/Vault/VaultEditLoginPage.cs
index a945fed5b..83bce7d6d 100644
--- a/src/App/Pages/Vault/VaultEditLoginPage.cs
+++ b/src/App/Pages/Vault/VaultEditLoginPage.cs
@@ -298,9 +298,26 @@ namespace Bit.App.Pages
PasswordCell.Button.Image = "eye" + (!PasswordCell.Entry.IsPasswordFromToggled ? "_slash" : string.Empty);
}
- private void TotpButton_Clicked(object sender, EventArgs e)
+ private async void TotpButton_Clicked(object sender, EventArgs e)
{
- // launch camera
+ var scanPage = new ScanPage((key) =>
+ {
+ Device.BeginInvokeOnMainThread(async () =>
+ {
+ await Navigation.PopModalAsync();
+ if(!string.IsNullOrWhiteSpace(key))
+ {
+ TotpCell.Entry.Text = key;
+ _userDialogs.Toast(AppResources.AuthenticatorKeyAdded);
+ }
+ else
+ {
+ _userDialogs.Alert(AppResources.AuthenticatorKeyReadError);
+ }
+ });
+ });
+
+ await Navigation.PushModalAsync(new ExtendedNavigationPage(scanPage));
}
private async void GenerateCell_Tapped(object sender, EventArgs e)
diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs
index 585281f2b..6549689ee 100644
--- a/src/App/Resources/AppResources.Designer.cs
+++ b/src/App/Resources/AppResources.Designer.cs
@@ -187,6 +187,24 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Authenticator key added..
+ ///
+ public static string AuthenticatorKeyAdded {
+ get {
+ return ResourceManager.GetString("AuthenticatorKeyAdded", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Cannot read authenticator key..
+ ///
+ public static string AuthenticatorKeyReadError {
+ get {
+ return ResourceManager.GetString("AuthenticatorKeyReadError", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Auto-fill.
///
@@ -430,6 +448,24 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Scanning will happen automatically..
+ ///
+ public static string CameraInstructionBottom {
+ get {
+ return ResourceManager.GetString("CameraInstructionBottom", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Point your camera at the QR code..
+ ///
+ public static string CameraInstructionTop {
+ get {
+ return ResourceManager.GetString("CameraInstructionTop", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Cancel.
///
@@ -1771,6 +1807,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Scan QR Code.
+ ///
+ public static string ScanQrTitle {
+ get {
+ return ResourceManager.GetString("ScanQrTitle", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Search.
///
diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx
index b55e52299..c16f1b227 100644
--- a/src/App/Resources/AppResources.resx
+++ b/src/App/Resources/AppResources.resx
@@ -929,4 +929,19 @@
Verification Code (TOTP)
Totp code label
+
+ Authenticator key added.
+
+
+ Cannot read authenticator key.
+
+
+ Scanning will happen automatically.
+
+
+ Point your camera at the QR code.
+
+
+ Scan QR Code
+
\ No newline at end of file
diff --git a/src/App/packages.config b/src/App/packages.config
index d3d51f970..b3fc01737 100644
--- a/src/App/packages.config
+++ b/src/App/packages.config
@@ -24,4 +24,6 @@
+
+
\ No newline at end of file
diff --git a/src/iOS/AppDelegate.cs b/src/iOS/AppDelegate.cs
index 2553818ae..6e31ee15e 100644
--- a/src/iOS/AppDelegate.cs
+++ b/src/iOS/AppDelegate.cs
@@ -116,6 +116,7 @@ namespace Bit.iOS
UIApplication.SharedApplication.SetStatusBarHidden(!show, false);
});
+ ZXing.Net.Mobile.Forms.iOS.Platform.Init();
return base.FinishedLaunching(app, options);
}
diff --git a/src/iOS/Info.plist b/src/iOS/Info.plist
index e121e7a0b..f73fd681f 100644
--- a/src/iOS/Info.plist
+++ b/src/iOS/Info.plist
@@ -101,5 +101,7 @@
en
NSPhotoLibraryUsageDescription
This app does not require access to the photo library.
+ NSCameraUsageDescription
+ Scan QR codes
diff --git a/src/iOS/iOS.csproj b/src/iOS/iOS.csproj
index 4b869fbbe..c251a44e9 100644
--- a/src/iOS/iOS.csproj
+++ b/src/iOS/iOS.csproj
@@ -412,6 +412,21 @@
..\..\packages\XLabs.IoC.SimpleInjector.2.0.5782\lib\portable-net45+netcore45+wp8+MonoAndroid1+MonoTouch1\XLabs.Ioc.SimpleInjector.dll
+
+ ..\..\packages\ZXing.Net.Mobile.2.1.47\lib\Xamarin.iOS10\ZXing.Net.Mobile.Core.dll
+
+
+ ..\..\packages\ZXing.Net.Mobile.Forms.2.1.47\lib\Xamarin.iOS10\ZXing.Net.Mobile.Forms.dll
+
+
+ ..\..\packages\ZXing.Net.Mobile.Forms.2.1.47\lib\Xamarin.iOS10\ZXing.Net.Mobile.Forms.iOS.dll
+
+
+ ..\..\packages\ZXing.Net.Mobile.2.1.47\lib\Xamarin.iOS10\zxing.portable.dll
+
+
+ ..\..\packages\ZXing.Net.Mobile.2.1.47\lib\Xamarin.iOS10\ZXingNetMobile.dll
+
diff --git a/src/iOS/packages.config b/src/iOS/packages.config
index ab5e46578..18a9957a7 100644
--- a/src/iOS/packages.config
+++ b/src/iOS/packages.config
@@ -78,4 +78,6 @@
+
+
\ No newline at end of file