diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj
index 320a23a47..3997e036b 100644
--- a/src/Android/Android.csproj
+++ b/src/Android/Android.csproj
@@ -245,6 +245,14 @@
+
+
+
+
+
+
+
+
diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs
index f4fc640d0..6792f3567 100644
--- a/src/Android/MainActivity.cs
+++ b/src/Android/MainActivity.cs
@@ -116,7 +116,7 @@ namespace Bit.Droid
{
ListenYubiKey((bool)message.Data);
}
- else if (message.Command == "updatedTheme")
+ else if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY)
{
Xamarin.Forms.Device.BeginInvokeOnMainThread(() => AppearanceAdjustments());
}
diff --git a/src/Android/MainApplication.cs b/src/Android/MainApplication.cs
index 287df60ed..4407c1c31 100644
--- a/src/Android/MainApplication.cs
+++ b/src/Android/MainApplication.cs
@@ -159,6 +159,7 @@ namespace Bit.Droid
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
var biometricService = new BiometricService(stateService, cryptoService);
+ var userPinService = new UserPinService(stateService, cryptoService);
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService, stateService);
ServiceContainer.Register(preferencesStorage);
@@ -182,6 +183,7 @@ namespace Bit.Droid
ServiceContainer.Register("cryptoService", cryptoService);
ServiceContainer.Register("passwordRepromptService", passwordRepromptService);
ServiceContainer.Register("avatarImageSourcePool", new AvatarImageSourcePool());
+ ServiceContainer.Register(userPinService);
// Push
#if FDROID
diff --git a/src/Android/Resources/drawable/empty_login_requests.xml b/src/Android/Resources/drawable/empty_login_requests.xml
new file mode 100644
index 000000000..4c5d399e7
--- /dev/null
+++ b/src/Android/Resources/drawable/empty_login_requests.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Android/Resources/drawable/empty_login_requests_dark.xml b/src/Android/Resources/drawable/empty_login_requests_dark.xml
new file mode 100644
index 000000000..b12d7af2b
--- /dev/null
+++ b/src/Android/Resources/drawable/empty_login_requests_dark.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Android/Services/DeviceActionService.cs b/src/Android/Services/DeviceActionService.cs
index dd1b8f1b6..70962a5bd 100644
--- a/src/Android/Services/DeviceActionService.cs
+++ b/src/Android/Services/DeviceActionService.cs
@@ -547,6 +547,12 @@ namespace Bit.Droid.Services
return true;
}
+ public bool SupportsAutofillServices() => Build.VERSION.SdkInt >= BuildVersionCodes.O;
+
+ public bool SupportsInlineAutofill() => Build.VERSION.SdkInt >= BuildVersionCodes.R;
+
+ public bool SupportsDrawOver() => Build.VERSION.SdkInt >= BuildVersionCodes.M;
+
private Intent RateIntentForUrl(string url, Activity activity)
{
var intent = new Intent(Intent.ActionView, Android.Net.Uri.Parse($"{url}?id={activity.PackageName}"));
@@ -601,6 +607,38 @@ namespace Bit.Droid.Services
throw new NotImplementedException();
}
+ public string GetAutofillAccessibilityDescription()
+ {
+ if (Build.VERSION.SdkInt <= BuildVersionCodes.LollipopMr1)
+ {
+ return AppResources.AccessibilityDescription;
+ }
+ if (Build.VERSION.SdkInt <= BuildVersionCodes.M)
+ {
+ return AppResources.AccessibilityDescription2;
+ }
+ if (Build.VERSION.SdkInt <= BuildVersionCodes.NMr1)
+ {
+ return AppResources.AccessibilityDescription3;
+ }
+
+ return AppResources.AccessibilityDescription4;
+ }
+
+ public string GetAutofillDrawOverDescription()
+ {
+ if (Build.VERSION.SdkInt <= BuildVersionCodes.M)
+ {
+ return AppResources.DrawOverDescription;
+ }
+ if (Build.VERSION.SdkInt <= BuildVersionCodes.NMr1)
+ {
+ return AppResources.DrawOverDescription2;
+ }
+
+ return AppResources.DrawOverDescription3;
+ }
+
private void SetNumericKeyboardTo(EditText editText)
{
editText.InputType = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal | InputTypes.NumberFlagSigned;
diff --git a/src/App/Abstractions/IDeviceActionService.cs b/src/App/Abstractions/IDeviceActionService.cs
index 56bf5d17f..98de7ec21 100644
--- a/src/App/Abstractions/IDeviceActionService.cs
+++ b/src/App/Abstractions/IDeviceActionService.cs
@@ -28,6 +28,9 @@ namespace Bit.App.Abstractions
bool SupportsNfc();
bool SupportsCamera();
bool SupportsFido2();
+ bool SupportsAutofillServices();
+ bool SupportsInlineAutofill();
+ bool SupportsDrawOver();
bool LaunchApp(string appName);
void RateApp();
@@ -41,5 +44,7 @@ namespace Bit.App.Abstractions
Task SetScreenCaptureAllowedAsync();
void OpenAppSettings();
void CloseExtensionPopUp();
+ string GetAutofillAccessibilityDescription();
+ string GetAutofillDrawOverDescription();
}
}
diff --git a/src/App/App.csproj b/src/App/App.csproj
index dac6725c8..0bc1ad4ad 100644
--- a/src/App/App.csproj
+++ b/src/App/App.csproj
@@ -59,9 +59,6 @@
ExtensionPage.xaml
-
- AutofillServicesPage.xaml
-
FolderAddEditPage.xaml
@@ -71,12 +68,6 @@
ExportVaultPage.xaml
-
- OptionsPage.xaml
-
-
- SyncPage.xaml
-
AttachmentsPage.xaml
@@ -147,6 +138,7 @@
+
@@ -444,5 +436,6 @@
+
diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs
index aa839370b..9ff35ec7a 100644
--- a/src/App/App.xaml.cs
+++ b/src/App/App.xaml.cs
@@ -91,7 +91,7 @@ namespace Bit.App
_messagingService.Send("showDialogResolve", new Tuple(details.DialogId, confirmed));
});
}
- else if (message.Command == "resumed")
+ else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND)
{
if (Device.RuntimePlatform == Device.iOS)
{
@@ -365,7 +365,7 @@ namespace Bit.App
await Device.InvokeOnMainThreadAsync(() =>
{
ThemeManager.SetTheme(Current.Resources);
- _messagingService.Send("updatedTheme");
+ _messagingService.Send(ThemeManager.UPDATED_THEME_MESSAGE_KEY);
});
}
diff --git a/src/App/Controls/ExternalLinkItemView.xaml b/src/App/Controls/ExternalLinkItemView.xaml
new file mode 100644
index 000000000..416367774
--- /dev/null
+++ b/src/App/Controls/ExternalLinkItemView.xaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/App/Controls/ExternalLinkItemView.xaml.cs b/src/App/Controls/ExternalLinkItemView.xaml.cs
new file mode 100644
index 000000000..3ce73090e
--- /dev/null
+++ b/src/App/Controls/ExternalLinkItemView.xaml.cs
@@ -0,0 +1,31 @@
+using System.Windows.Input;
+using Xamarin.Forms;
+
+namespace Bit.App.Controls
+{
+ public partial class ExternalLinkItemView : ContentView
+ {
+ public static readonly BindableProperty TitleProperty = BindableProperty.Create(
+ nameof(Title), typeof(string), typeof(ExternalLinkItemView), null, BindingMode.OneWay);
+
+ public static readonly BindableProperty GoToLinkCommandProperty = BindableProperty.Create(
+ nameof(GoToLinkCommand), typeof(ICommand), typeof(ExternalLinkItemView));
+
+ public ExternalLinkItemView()
+ {
+ InitializeComponent();
+ }
+
+ public string Title
+ {
+ get { return (string)GetValue(TitleProperty); }
+ set { SetValue(TitleProperty, value); }
+ }
+
+ public ICommand GoToLinkCommand
+ {
+ get => GetValue(GoToLinkCommandProperty) as ICommand;
+ set => SetValue(GoToLinkCommandProperty, value);
+ }
+ }
+}
diff --git a/src/App/Controls/Settings/BaseSettingControlView.cs b/src/App/Controls/Settings/BaseSettingControlView.cs
new file mode 100644
index 000000000..714ebb143
--- /dev/null
+++ b/src/App/Controls/Settings/BaseSettingControlView.cs
@@ -0,0 +1,25 @@
+using Xamarin.Forms;
+
+namespace Bit.App.Controls
+{
+ public class BaseSettingItemView : ContentView
+ {
+ public static readonly BindableProperty TitleProperty = BindableProperty.Create(
+ nameof(Title), typeof(string), typeof(SwitchItemView), null, BindingMode.OneWay);
+
+ public static readonly BindableProperty SubtitleProperty = BindableProperty.Create(
+ nameof(Subtitle), typeof(string), typeof(SwitchItemView), null, BindingMode.OneWay);
+
+ public string Title
+ {
+ get { return (string)GetValue(TitleProperty); }
+ set { SetValue(TitleProperty, value); }
+ }
+
+ public string Subtitle
+ {
+ get { return (string)GetValue(SubtitleProperty); }
+ set { SetValue(SubtitleProperty, value); }
+ }
+ }
+}
diff --git a/src/App/Controls/Settings/SettingChooserItemView.xaml b/src/App/Controls/Settings/SettingChooserItemView.xaml
new file mode 100644
index 000000000..1cc1fe0f0
--- /dev/null
+++ b/src/App/Controls/Settings/SettingChooserItemView.xaml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/App/Controls/Settings/SettingChooserItemView.xaml.cs b/src/App/Controls/Settings/SettingChooserItemView.xaml.cs
new file mode 100644
index 000000000..3ab7dadf5
--- /dev/null
+++ b/src/App/Controls/Settings/SettingChooserItemView.xaml.cs
@@ -0,0 +1,31 @@
+using System.Windows.Input;
+using Xamarin.Forms;
+
+namespace Bit.App.Controls
+{
+ public partial class SettingChooserItemView : BaseSettingItemView
+ {
+ public static readonly BindableProperty DisplayValueProperty = BindableProperty.Create(
+ nameof(DisplayValue), typeof(string), typeof(SettingChooserItemView), null, BindingMode.OneWay);
+
+ public static readonly BindableProperty ChooseCommandProperty = BindableProperty.Create(
+ nameof(ChooseCommand), typeof(ICommand), typeof(ExternalLinkItemView));
+
+ public string DisplayValue
+ {
+ get { return (string)GetValue(DisplayValueProperty); }
+ set { SetValue(DisplayValueProperty, value); }
+ }
+
+ public SettingChooserItemView()
+ {
+ InitializeComponent();
+ }
+
+ public ICommand ChooseCommand
+ {
+ get => GetValue(ChooseCommandProperty) as ICommand;
+ set => SetValue(ChooseCommandProperty, value);
+ }
+ }
+}
diff --git a/src/App/Controls/Settings/SwitchItemView.xaml b/src/App/Controls/Settings/SwitchItemView.xaml
new file mode 100644
index 000000000..6249ca35e
--- /dev/null
+++ b/src/App/Controls/Settings/SwitchItemView.xaml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
diff --git a/src/App/Controls/Settings/SwitchItemView.xaml.cs b/src/App/Controls/Settings/SwitchItemView.xaml.cs
new file mode 100644
index 000000000..dae79e3d6
--- /dev/null
+++ b/src/App/Controls/Settings/SwitchItemView.xaml.cs
@@ -0,0 +1,45 @@
+using System.Windows.Input;
+using Xamarin.Forms;
+
+namespace Bit.App.Controls
+{
+ public partial class SwitchItemView : BaseSettingItemView
+ {
+ public static readonly BindableProperty IsToggledProperty = BindableProperty.Create(
+ nameof(IsToggled), typeof(bool), typeof(SwitchItemView), null, BindingMode.TwoWay);
+
+ public static readonly BindableProperty SwitchAutomationIdProperty = BindableProperty.Create(
+ nameof(SwitchAutomationId), typeof(string), typeof(SwitchItemView), null, BindingMode.OneWay);
+
+ public static readonly BindableProperty ToggleSwitchCommandProperty = BindableProperty.Create(
+ nameof(ToggleSwitchCommand), typeof(ICommand), typeof(ExternalLinkItemView));
+
+ public SwitchItemView()
+ {
+ InitializeComponent();
+ }
+
+ public bool IsToggled
+ {
+ get { return (bool)GetValue(IsToggledProperty); }
+ set { SetValue(IsToggledProperty, value); }
+ }
+
+ public string SwitchAutomationId
+ {
+ get { return (string)GetValue(SwitchAutomationIdProperty); }
+ set { SetValue(SwitchAutomationIdProperty, value); }
+ }
+
+ public ICommand ToggleSwitchCommand
+ {
+ get => GetValue(ToggleSwitchCommandProperty) as ICommand;
+ set => SetValue(ToggleSwitchCommandProperty, value);
+ }
+
+ void ContentView_Tapped(System.Object sender, System.EventArgs e)
+ {
+ _switch.IsToggled = !_switch.IsToggled;
+ }
+ }
+}
diff --git a/src/App/Pages/Accounts/HomePage.xaml.cs b/src/App/Pages/Accounts/HomePage.xaml.cs
index c374afe9f..ae06b5a9d 100644
--- a/src/App/Pages/Accounts/HomePage.xaml.cs
+++ b/src/App/Pages/Accounts/HomePage.xaml.cs
@@ -64,7 +64,7 @@ namespace Bit.App.Pages
}
_broadcasterService.Subscribe(nameof(HomePage), (message) =>
{
- if (message.Command == "updatedTheme")
+ if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY)
{
Device.BeginInvokeOnMainThread(() =>
{
diff --git a/src/App/Pages/BaseModalContentPage.cs b/src/App/Pages/BaseModalContentPage.cs
new file mode 100644
index 000000000..5c69ff4e9
--- /dev/null
+++ b/src/App/Pages/BaseModalContentPage.cs
@@ -0,0 +1,18 @@
+using System;
+namespace Bit.App.Pages
+{
+ public class BaseModalContentPage : BaseContentPage
+ {
+ public BaseModalContentPage()
+ {
+ }
+
+ protected void PopModal_Clicked(object sender, EventArgs e)
+ {
+ if (DoOnce())
+ {
+ Navigation.PopModalAsync();
+ }
+ }
+ }
+}
diff --git a/src/App/Pages/BaseViewModel.cs b/src/App/Pages/BaseViewModel.cs
index 57098f8d0..a6ce6c7a2 100644
--- a/src/App/Pages/BaseViewModel.cs
+++ b/src/App/Pages/BaseViewModel.cs
@@ -1,11 +1,14 @@
using System;
+using System.Threading.Tasks;
+using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
-using Bit.Core.Services;
using Bit.Core.Utilities;
+using Xamarin.CommunityToolkit.ObjectModel;
+using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -47,5 +50,24 @@ namespace Bit.App.Pages
_logger.Value.Exception(ex);
}
+ protected AsyncCommand CreateDefaultAsyncCommnad(Func execute, Func