diff --git a/global.json b/global.json
new file mode 100644
index 000000000..de544e70c
--- /dev/null
+++ b/global.json
@@ -0,0 +1,5 @@
+{
+ "sdk": {
+ "version": "7.0.306"
+ }
+}
diff --git a/src/App/App.csproj b/src/App/App.csproj
index a39ef90af..7742b0589 100644
--- a/src/App/App.csproj
+++ b/src/App/App.csproj
@@ -1,33 +1,28 @@
-
- netstandard2.1Bit.AppBitwardenAppDebug;Release;FDroid
+ net7.0-android;net7.0-ios
+ True
+ Library
+ enable
+ true
-
-
- pdbonly
- true
-
-
-
-
-
-
+
+
+
+
-
-
EnvironmentPage.xaml
@@ -130,7 +125,6 @@
LoginPasswordlessRequestPage.xaml
-
@@ -148,11 +142,9 @@
-
-
Black.xaml
@@ -176,7 +168,6 @@
Android.xaml
-
AppResources.cs.resx
@@ -314,7 +305,6 @@
True
-
AppResources.cs.Designer.cs
@@ -425,7 +415,6 @@
ResXFileCodeGenerator
-
@@ -445,4 +434,4 @@
-
+
\ No newline at end of file
diff --git a/src/App/App.xaml b/src/App/App.xaml
index c21ed3905..ab6339d82 100644
--- a/src/App/App.xaml
+++ b/src/App/App.xaml
@@ -1,5 +1,5 @@
-
-
+
diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs
index aa839370b..7df514569 100644
--- a/src/App/App.xaml.cs
+++ b/src/App/App.xaml.cs
@@ -15,8 +15,9 @@ using Bit.Core.Models.Data;
using Bit.Core.Models.Response;
using Bit.Core.Services;
using Bit.Core.Utilities;
-using Xamarin.Forms;
-using Xamarin.Forms.Xaml;
+using Microsoft.Maui.Controls.Xaml;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Bit.App
@@ -93,6 +94,7 @@ namespace Bit.App
}
else if (message.Command == "resumed")
{
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.iOS)
{
ResumedAsync().FireAndForget();
@@ -100,6 +102,7 @@ namespace Bit.App
}
else if (message.Command == "slept")
{
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.iOS)
{
await SleptAsync();
@@ -121,7 +124,7 @@ namespace Bit.App
Options.OtpData = new OtpData((string)message.Data);
}
- await Device.InvokeOnMainThreadAsync(async () =>
+ Device.InvokeOnMainThreadAsync(async () =>
{
if (Current.MainPage is TabsPage tabsPage)
{
@@ -293,6 +296,7 @@ namespace Bit.App
{
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
}
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.Android)
{
await _vaultTimeoutService.CheckVaultTimeoutAsync();
@@ -308,6 +312,7 @@ namespace Bit.App
{
System.Diagnostics.Debug.WriteLine("XF App: OnSleep");
_isResumed = false;
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.Android)
{
var isLocked = await _vaultTimeoutService.IsLockedAsync();
@@ -331,6 +336,7 @@ namespace Bit.App
{
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
}
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.Android)
{
ResumedAsync().FireAndForget();
@@ -396,6 +402,7 @@ namespace Bit.App
private void ClearAutofillUri()
{
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri))
{
Options.Uri = null;
@@ -404,6 +411,7 @@ namespace Bit.App
private bool SetTabsPageFromAutofill(bool isLocked)
{
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri) &&
!Options.FromAutofillFramework)
{
@@ -452,7 +460,7 @@ namespace Bit.App
private void SyncIfNeeded()
{
- if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
+ if (Microsoft.Maui.Networking.Connectivity.NetworkAccess == Microsoft.Maui.Networking.NetworkAccess.None)
{
return;
}
diff --git a/src/App/Behaviors/EditorPreventAutoBottomScrollingOnFocusedBehavior.cs b/src/App/Behaviors/EditorPreventAutoBottomScrollingOnFocusedBehavior.cs
index b00f63737..618f821ce 100644
--- a/src/App/Behaviors/EditorPreventAutoBottomScrollingOnFocusedBehavior.cs
+++ b/src/App/Behaviors/EditorPreventAutoBottomScrollingOnFocusedBehavior.cs
@@ -1,5 +1,5 @@
-using Xamarin.Essentials;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Behaviors
{
diff --git a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml
index 4dba94dd1..1f6580e3f 100644
--- a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml
+++ b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml
@@ -1,8 +1,8 @@
-
+
Device.RuntimePlatform == Device.Android ? 74 : 70;
+ public int AccountListRowHeight => // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+Device.RuntimePlatform == Device.Android ? 74 : 70;
public bool LongPressAccountEnabled { get; set; } = true;
diff --git a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs
index f9cd86b73..0153aa15e 100644
--- a/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs
+++ b/src/App/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs
@@ -7,7 +7,8 @@ using Bit.Core.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Controls
{
diff --git a/src/App/Controls/AccountViewCell/AccountViewCell.xaml b/src/App/Controls/AccountViewCell/AccountViewCell.xaml
index 9a9f53831..9d270b67a 100644
--- a/src/App/Controls/AccountViewCell/AccountViewCell.xaml
+++ b/src/App/Controls/AccountViewCell/AccountViewCell.xaml
@@ -1,7 +1,7 @@
-
-
+
-
-
+
+
-
+
+
-
+
+
-
+
-
+
-
+
+
+
+
+
RememberEmail = !RememberEmail);
ContinueCommand = new AsyncCommand(ContinueToLoginStepAsync, allowsMultipleExecutions: false);
- CreateAccountCommand = new AsyncCommand(async () => await Device.InvokeOnMainThreadAsync(StartRegisterAction),
+ CreateAccountCommand = new AsyncCommand(async () => Device.InvokeOnMainThreadAsync(StartRegisterAction),
onException: _logger.Exception, allowsMultipleExecutions: false);
- CloseCommand = new AsyncCommand(async () => await Device.InvokeOnMainThreadAsync(CloseAction),
+ CloseCommand = new AsyncCommand(async () => Device.InvokeOnMainThreadAsync(CloseAction),
onException: _logger.Exception, allowsMultipleExecutions: false);
ShowEnvironmentPickerCommand = new AsyncCommand(ShowEnvironmentPickerAsync,
onException: _logger.Exception, allowsMultipleExecutions: false);
diff --git a/src/App/Pages/Accounts/LockPage.xaml b/src/App/Pages/Accounts/LockPage.xaml
index f34a8b284..396db85a1 100644
--- a/src/App/Pages/Accounts/LockPage.xaml
+++ b/src/App/Pages/Accounts/LockPage.xaml
@@ -1,6 +1,6 @@
-
+
Device.BeginInvokeOnMainThread(async () => await UnlockedAsync());
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.iOS)
{
ToolbarItems.Add(_moreItem);
diff --git a/src/App/Pages/Accounts/LockPageViewModel.cs b/src/App/Pages/Accounts/LockPageViewModel.cs
index c56968933..9b5d3437c 100644
--- a/src/App/Pages/Accounts/LockPageViewModel.cs
+++ b/src/App/Pages/Accounts/LockPageViewModel.cs
@@ -11,8 +11,9 @@ using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using Bit.Core.Services;
using Bit.Core.Utilities;
-using Xamarin.CommunityToolkit.Helpers;
-using Xamarin.Forms;
+using CommunityToolkit.Maui.Converters;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Pages
{
@@ -217,6 +218,7 @@ namespace Bit.App.Pages
}
BiometricButtonVisible = true;
BiometricButtonText = AppResources.UseBiometricsToUnlock;
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.iOS)
{
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
diff --git a/src/App/Pages/Accounts/LoginPage.xaml b/src/App/Pages/Accounts/LoginPage.xaml
index 5f93f9b0c..4fab2e774 100644
--- a/src/App/Pages/Accounts/LoginPage.xaml
+++ b/src/App/Pages/Accounts/LoginPage.xaml
@@ -1,6 +1,6 @@
-
+
+
+
+ Microsoft.Maui.ApplicationModel.MainThread.InvokeOnMainThreadAsync(async () =>
{
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(AppResources.GenericErrorMessage);
diff --git a/src/App/Pages/Accounts/LoginPasswordlessViewModel.cs b/src/App/Pages/Accounts/LoginPasswordlessViewModel.cs
index 63eb299c8..2f1901f86 100644
--- a/src/App/Pages/Accounts/LoginPasswordlessViewModel.cs
+++ b/src/App/Pages/Accounts/LoginPasswordlessViewModel.cs
@@ -13,7 +13,8 @@ using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Pages
{
diff --git a/src/App/Pages/Accounts/LoginSsoPage.xaml b/src/App/Pages/Accounts/LoginSsoPage.xaml
index 3c6fd941d..ff3a208b4 100644
--- a/src/App/Pages/Accounts/LoginSsoPage.xaml
+++ b/src/App/Pages/Accounts/LoginSsoPage.xaml
@@ -1,6 +1,6 @@
-
+
+
+
+
+
Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.CloseAction = async () => await Navigation.PopModalAsync();
DuoWebView = _duoWebView;
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.Android)
{
ToolbarItems.Remove(_cancelItem);
}
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.iOS)
{
ToolbarItems.Add(_moreItem);
diff --git a/src/App/Pages/Accounts/TwoFactorPageViewModel.cs b/src/App/Pages/Accounts/TwoFactorPageViewModel.cs
index 486b54f80..b9d99d597 100644
--- a/src/App/Pages/Accounts/TwoFactorPageViewModel.cs
+++ b/src/App/Pages/Accounts/TwoFactorPageViewModel.cs
@@ -14,8 +14,8 @@ using Bit.Core.Models.Request;
using Bit.Core.Utilities;
using Newtonsoft.Json;
using Xamarin.CommunityToolkit.ObjectModel;
-using Xamarin.Essentials;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Pages
{
@@ -84,7 +84,8 @@ namespace Bit.App.Pages
public bool TotpMethod => AuthenticatorMethod || EmailMethod;
- public bool ShowTryAgain => (YubikeyMethod && Device.RuntimePlatform == Device.iOS) || Fido2Method;
+ public bool ShowTryAgain => (YubikeyMethod && // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+Device.RuntimePlatform == Device.iOS) || Fido2Method;
public bool ShowContinue
{
@@ -98,7 +99,8 @@ namespace Bit.App.Pages
set => SetProperty(ref _enableContinue, value);
}
- public string YubikeyInstruction => Device.RuntimePlatform == Device.iOS ? AppResources.YubiKeyInstructionIos :
+ public string YubikeyInstruction => // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+Device.RuntimePlatform == Device.iOS ? AppResources.YubiKeyInstructionIos :
AppResources.YubiKeyInstruction;
public TwoFactorProviderType? SelectedProviderType
@@ -275,7 +277,7 @@ namespace Bit.App.Pages
{
return;
}
- if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
+ if (Microsoft.Maui.Networking.Connectivity.NetworkAccess == Microsoft.Maui.Networking.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
@@ -382,7 +384,7 @@ namespace Bit.App.Pages
{
return false;
}
- if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
+ if (Microsoft.Maui.Networking.Connectivity.NetworkAccess == Microsoft.Maui.Networking.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
diff --git a/src/App/Pages/Accounts/UpdateTempPasswordPage.xaml b/src/App/Pages/Accounts/UpdateTempPasswordPage.xaml
index f559de05a..911033cad 100644
--- a/src/App/Pages/Accounts/UpdateTempPasswordPage.xaml
+++ b/src/App/Pages/Accounts/UpdateTempPasswordPage.xaml
@@ -1,6 +1,6 @@
+
().SetUseSafeArea(true);
@@ -99,6 +101,7 @@ namespace Bit.App.Pages
}
}
}
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.iOS)
{
await DoWorkAsync();
diff --git a/src/App/Pages/BaseViewModel.cs b/src/App/Pages/BaseViewModel.cs
index 57098f8d0..a1ad04b98 100644
--- a/src/App/Pages/BaseViewModel.cs
+++ b/src/App/Pages/BaseViewModel.cs
@@ -6,7 +6,8 @@ using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Services;
using Bit.Core.Utilities;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Pages
{
@@ -39,7 +40,7 @@ namespace Bit.App.Pages
message = apiException.Error.GetSingleMessage();
}
- Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>
+ Microsoft.Maui.ApplicationModel.MainThread.InvokeOnMainThreadAsync(async () =>
{
await _deviceActionService.Value.HideLoadingAsync();
await _platformUtilsService.Value.ShowDialogAsync(message ?? AppResources.GenericErrorMessage);
diff --git a/src/App/Pages/CaptchaProtectedViewModel.cs b/src/App/Pages/CaptchaProtectedViewModel.cs
index 2f9969080..f5bba609d 100644
--- a/src/App/Pages/CaptchaProtectedViewModel.cs
+++ b/src/App/Pages/CaptchaProtectedViewModel.cs
@@ -4,7 +4,6 @@ using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
-using Xamarin.Essentials;
namespace Bit.App.Pages
{
diff --git a/src/App/Pages/Generator/GeneratorHistoryPage.xaml b/src/App/Pages/Generator/GeneratorHistoryPage.xaml
index 8fbe19930..84bc88f31 100644
--- a/src/App/Pages/Generator/GeneratorHistoryPage.xaml
+++ b/src/App/Pages/Generator/GeneratorHistoryPage.xaml
@@ -1,6 +1,6 @@
-
+
+
().SetUpdateMode(UpdateMode.WhenFinished);
+ _typePicker.On().SetUpdateMode(UpdateMode.WhenFinished);
_passwordTypePicker.On().SetUpdateMode(UpdateMode.WhenFinished);
_usernameTypePicker.On().SetUpdateMode(UpdateMode.WhenFinished);
_serviceTypePicker.On().SetUpdateMode(UpdateMode.WhenFinished);
@@ -97,6 +99,7 @@ namespace Bit.App.Pages
protected override bool OnBackButtonPressed()
{
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.Android && _tabsPage != null)
{
_tabsPage.ResetToVaultPage();
@@ -116,7 +119,7 @@ namespace Bit.App.Pages
if (selection == AppResources.PasswordHistory)
{
var page = new GeneratorHistoryPage();
- await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
+ await Navigation.PushModalAsync(new Microsoft.Maui.Controls.NavigationPage(page));
}
}
@@ -128,7 +131,7 @@ namespace Bit.App.Pages
private async void History_Clicked(object sender, EventArgs e)
{
var page = new GeneratorHistoryPage();
- await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
+ await Navigation.PushModalAsync(new Microsoft.Maui.Controls.NavigationPage(page));
}
private async void LengthSlider_DragCompleted(object sender, EventArgs e)
diff --git a/src/App/Pages/Generator/GeneratorPageViewModel.cs b/src/App/Pages/Generator/GeneratorPageViewModel.cs
index 935a9f7e0..4c33260d4 100644
--- a/src/App/Pages/Generator/GeneratorPageViewModel.cs
+++ b/src/App/Pages/Generator/GeneratorPageViewModel.cs
@@ -12,7 +12,8 @@ using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Pages
{
diff --git a/src/App/Pages/Send/SendAddEditPage.xaml b/src/App/Pages/Send/SendAddEditPage.xaml
index a90ed8cd0..08d1120e6 100644
--- a/src/App/Pages/Send/SendAddEditPage.xaml
+++ b/src/App/Pages/Send/SendAddEditPage.xaml
@@ -1,8 +1,8 @@
-
+
+
diff --git a/src/App/Pages/Send/SendAddOnlyOptionsView.xaml.cs b/src/App/Pages/Send/SendAddOnlyOptionsView.xaml.cs
index 84829ea23..93535cf00 100644
--- a/src/App/Pages/Send/SendAddOnlyOptionsView.xaml.cs
+++ b/src/App/Pages/Send/SendAddOnlyOptionsView.xaml.cs
@@ -1,8 +1,14 @@
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Bit.App.Behaviors;
-using Xamarin.CommunityToolkit.UI.Views;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+using CommunityToolkit.Maui.Converters;
+using CommunityToolkit.Maui.ImageSources;
+using CommunityToolkit.Maui;
+using CommunityToolkit.Maui.Core;
+using CommunityToolkit.Maui.Layouts;
+using CommunityToolkit.Maui.Views;
namespace Bit.App.Pages
{
diff --git a/src/App/Pages/Send/SendAddOnlyPage.xaml b/src/App/Pages/Send/SendAddOnlyPage.xaml
index 844313361..bc5c2a600 100644
--- a/src/App/Pages/Send/SendAddOnlyPage.xaml
+++ b/src/App/Pages/Send/SendAddOnlyPage.xaml
@@ -1,6 +1,6 @@
-
+
-
+
+
+
+
{
if (_timerStarted == null || (DateTime.UtcNow - _timerStarted) > _timerMaxLength)
diff --git a/src/App/Pages/Settings/BlockAutofillUrisPage.xaml b/src/App/Pages/Settings/BlockAutofillUrisPage.xaml
index 8514edcad..1a864ea89 100644
--- a/src/App/Pages/Settings/BlockAutofillUrisPage.xaml
+++ b/src/App/Pages/Settings/BlockAutofillUrisPage.xaml
@@ -1,5 +1,5 @@
-
-
+
+
+
+
+
+
+
();
PageTitle = AppResources.Options;
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
var iosIos = Device.RuntimePlatform == Device.iOS;
ClearClipboardOptions = new List>
diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml b/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml
index 4976573af..6e9f0e105 100644
--- a/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml
+++ b/src/App/Pages/Settings/SettingsPage/SettingsPage.xaml
@@ -1,5 +1,5 @@
-
-
+();
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.Android)
{
autofillItems.Add(new SettingsPageListItem
@@ -587,6 +591,7 @@ namespace Bit.App.Pages
if (_supportsBiometric || _biometric)
{
var biometricName = AppResources.Biometrics;
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.iOS)
{
biometricName = _deviceActionService.SupportsFaceBiometric() ? AppResources.FaceID :
@@ -641,6 +646,7 @@ namespace Bit.App.Pages
});
}
}
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.Android)
{
securityItems.Add(new SettingsPageListItem
@@ -651,6 +657,7 @@ namespace Bit.App.Pages
});
}
var accountItems = new List();
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.iOS)
{
accountItems.Add(new SettingsPageListItem
@@ -754,7 +761,8 @@ namespace Bit.App.Pages
};
// TODO: refactor this
- if (Device.RuntimePlatform == Device.Android
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+ if (Device.RuntimePlatform == Device.Android
||
GroupedItems.Any())
{
@@ -818,6 +826,7 @@ namespace Bit.App.Pages
private bool IncludeLinksWithSubscriptionInfo()
{
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.iOS)
{
return false;
diff --git a/src/App/Pages/Settings/SyncPage.xaml b/src/App/Pages/Settings/SyncPage.xaml
index 7ad3a4fd6..877a3a017 100644
--- a/src/App/Pages/Settings/SyncPage.xaml
+++ b/src/App/Pages/Settings/SyncPage.xaml
@@ -1,6 +1,6 @@
-
+
+
SubmitAsync()
{
- if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
+ if (Microsoft.Maui.Networking.Connectivity.NetworkAccess == Microsoft.Maui.Networking.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
@@ -154,7 +155,8 @@ namespace Bit.App.Pages
public async Task ChooseFileAsync()
{
// Prevent Android from locking if vault timeout set to "immediate"
- if (Device.RuntimePlatform == Device.Android)
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+ if (Device.RuntimePlatform == Device.Android)
{
_vaultTimeoutService.DelayLockAndLogoutMs = 60000;
}
@@ -163,7 +165,7 @@ namespace Bit.App.Pages
private async void DeleteAsync(AttachmentView attachment)
{
- if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
+ if (Microsoft.Maui.Networking.Connectivity.NetworkAccess == Microsoft.Maui.Networking.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
diff --git a/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs b/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs
index aade36dd6..5b93ccba6 100644
--- a/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs
+++ b/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs
@@ -9,7 +9,8 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Pages
{
@@ -91,7 +92,7 @@ namespace Bit.App.Pages
{
var options = new List { AppResources.Yes };
if (cipher.Type == CipherType.Login &&
- Xamarin.Essentials.Connectivity.NetworkAccess != Xamarin.Essentials.NetworkAccess.None)
+ Microsoft.Maui.Networking.Connectivity.NetworkAccess != Microsoft.Maui.Networking.NetworkAccess.None)
{
options.Add(AppResources.YesAndSave);
}
diff --git a/src/App/Pages/Vault/CipherAddEditPage.xaml b/src/App/Pages/Vault/CipherAddEditPage.xaml
index 453da9146..a1fb16984 100644
--- a/src/App/Pages/Vault/CipherAddEditPage.xaml
+++ b/src/App/Pages/Vault/CipherAddEditPage.xaml
@@ -1,6 +1,6 @@
-
+
DeleteAsync()
{
- if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
+ if (Microsoft.Maui.Networking.Connectivity.NetworkAccess == Microsoft.Maui.Networking.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
diff --git a/src/App/Pages/Vault/CipherDetailsPage.xaml b/src/App/Pages/Vault/CipherDetailsPage.xaml
index 8aaf74706..a88866ef8 100644
--- a/src/App/Pages/Vault/CipherDetailsPage.xaml
+++ b/src/App/Pages/Vault/CipherDetailsPage.xaml
@@ -1,6 +1,6 @@
-
+
DeleteAsync()
{
- if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
+ if (Microsoft.Maui.Networking.Connectivity.NetworkAccess == Microsoft.Maui.Networking.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
@@ -403,7 +404,7 @@ namespace Bit.App.Pages
{
return false;
}
- if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
+ if (Microsoft.Maui.Networking.Connectivity.NetworkAccess == Microsoft.Maui.Networking.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
@@ -479,7 +480,7 @@ namespace Bit.App.Pages
{
try
{
- if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
+ if (Microsoft.Maui.Networking.Connectivity.NetworkAccess == Microsoft.Maui.Networking.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
@@ -504,6 +505,7 @@ namespace Bit.App.Pages
var canOpenFile = true;
if (!_fileService.CanOpenFile(attachment.FileName))
{
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.iOS)
{
// iOS is currently hardcoded to always return CanOpenFile == true, but should it ever return false
@@ -529,6 +531,7 @@ namespace Bit.App.Pages
return;
}
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.Android)
{
if (canOpenFile)
diff --git a/src/App/Pages/Vault/CipherSelectionPage.xaml b/src/App/Pages/Vault/CipherSelectionPage.xaml
index f71d3a2c2..27fbaec7c 100644
--- a/src/App/Pages/Vault/CipherSelectionPage.xaml
+++ b/src/App/Pages/Vault/CipherSelectionPage.xaml
@@ -1,7 +1,7 @@
-
-
+
+
{ AppResources.Autofill };
if (cipher.Type == CipherType.Login &&
- Xamarin.Essentials.Connectivity.NetworkAccess != Xamarin.Essentials.NetworkAccess.None)
+ Microsoft.Maui.Networking.Connectivity.NetworkAccess != Microsoft.Maui.Networking.NetworkAccess.None)
{
options.Add(AppResources.AutofillAndSave);
}
diff --git a/src/App/Pages/Vault/CollectionsPage.xaml b/src/App/Pages/Vault/CollectionsPage.xaml
index 86980c45d..af08b2798 100644
--- a/src/App/Pages/Vault/CollectionsPage.xaml
+++ b/src/App/Pages/Vault/CollectionsPage.xaml
@@ -1,6 +1,6 @@
-
+
-
+ groupedItems)
{
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
var uppercaseGroupNames = Device.RuntimePlatform == Device.iOS;
_totpTickCts?.Cancel();
if (ShowTotp)
@@ -509,7 +513,7 @@ namespace Bit.App.Pages
public async Task SyncAsync()
{
- if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
+ if (Microsoft.Maui.Networking.Connectivity.NetworkAccess == Microsoft.Maui.Networking.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
diff --git a/src/App/Pages/Vault/OTPCipherSelectionPageViewModel.cs b/src/App/Pages/Vault/OTPCipherSelectionPageViewModel.cs
index 537823194..be87b878d 100644
--- a/src/App/Pages/Vault/OTPCipherSelectionPageViewModel.cs
+++ b/src/App/Pages/Vault/OTPCipherSelectionPageViewModel.cs
@@ -6,7 +6,8 @@ using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Pages
{
diff --git a/src/App/Pages/Vault/PasswordHistoryPage.xaml b/src/App/Pages/Vault/PasswordHistoryPage.xaml
index 215686c7b..69d7afe91 100644
--- a/src/App/Pages/Vault/PasswordHistoryPage.xaml
+++ b/src/App/Pages/Vault/PasswordHistoryPage.xaml
@@ -1,6 +1,6 @@
-
+
+
InitScanner());
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.Android)
{
ToolbarItems.RemoveAt(0);
diff --git a/src/App/Pages/Vault/ScanPageViewModel.cs b/src/App/Pages/Vault/ScanPageViewModel.cs
index 74b2d1a4c..77f83bcfb 100644
--- a/src/App/Pages/Vault/ScanPageViewModel.cs
+++ b/src/App/Pages/Vault/ScanPageViewModel.cs
@@ -8,8 +8,8 @@ using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
-using Xamarin.Essentials;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Pages
{
@@ -118,7 +118,7 @@ namespace Bit.App.Pages
private void HandleException(Exception ex)
{
- Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>
+ Microsoft.Maui.ApplicationModel.MainThread.InvokeOnMainThreadAsync(async () =>
{
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(AppResources.GenericErrorMessage);
diff --git a/src/App/Pages/Vault/SharePage.xaml b/src/App/Pages/Vault/SharePage.xaml
index da484eae3..5fd963db4 100644
--- a/src/App/Pages/Vault/SharePage.xaml
+++ b/src/App/Pages/Vault/SharePage.xaml
@@ -1,6 +1,6 @@
-
+
(string key)
{
var formattedKey = string.Format(KeyFormat, key);
- if (!Xamarin.Essentials.Preferences.ContainsKey(formattedKey, _sharedName))
+ if (!Microsoft.Maui.Storage.Preferences.ContainsKey(formattedKey, _sharedName))
{
return default(T);
}
@@ -47,37 +47,37 @@ namespace Bit.App.Services
var objType = typeof(T);
if (objType == typeof(string))
{
- var val = Xamarin.Essentials.Preferences.Get(formattedKey, default(string), _sharedName);
+ var val = Microsoft.Maui.Storage.Preferences.Get(formattedKey, default(string), _sharedName);
return (T)(object)val;
}
else if (objType == typeof(bool) || objType == typeof(bool?))
{
- var val = Xamarin.Essentials.Preferences.Get(formattedKey, default(bool), _sharedName);
+ var val = Microsoft.Maui.Storage.Preferences.Get(formattedKey, default(bool), _sharedName);
return ChangeType(val);
}
else if (objType == typeof(int) || objType == typeof(int?))
{
- var val = Xamarin.Essentials.Preferences.Get(formattedKey, default(int), _sharedName);
+ var val = Microsoft.Maui.Storage.Preferences.Get(formattedKey, default(int), _sharedName);
return ChangeType(val);
}
else if (objType == typeof(long) || objType == typeof(long?))
{
- var val = Xamarin.Essentials.Preferences.Get(formattedKey, default(long), _sharedName);
+ var val = Microsoft.Maui.Storage.Preferences.Get(formattedKey, default(long), _sharedName);
return ChangeType(val);
}
else if (objType == typeof(double) || objType == typeof(double?))
{
- var val = Xamarin.Essentials.Preferences.Get(formattedKey, default(double), _sharedName);
+ var val = Microsoft.Maui.Storage.Preferences.Get(formattedKey, default(double), _sharedName);
return ChangeType(val);
}
else if (objType == typeof(DateTime) || objType == typeof(DateTime?))
{
- var val = Xamarin.Essentials.Preferences.Get(formattedKey, default(DateTime), _sharedName);
+ var val = Microsoft.Maui.Storage.Preferences.Get(formattedKey, default(DateTime), _sharedName);
return ChangeType(val);
}
else
{
- var val = Xamarin.Essentials.Preferences.Get(formattedKey, default(string), _sharedName);
+ var val = Microsoft.Maui.Storage.Preferences.Get(formattedKey, default(string), _sharedName);
return JsonConvert.DeserializeObject(val, _jsonSettings);
}
}
@@ -94,31 +94,31 @@ namespace Bit.App.Services
var objType = typeof(T);
if (objType == typeof(string))
{
- Xamarin.Essentials.Preferences.Set(formattedKey, obj as string, _sharedName);
+ Microsoft.Maui.Storage.Preferences.Set(formattedKey, obj as string, _sharedName);
}
else if (objType == typeof(bool) || objType == typeof(bool?))
{
- Xamarin.Essentials.Preferences.Set(formattedKey, (obj as bool?).Value, _sharedName);
+ Microsoft.Maui.Storage.Preferences.Set(formattedKey, (obj as bool?).Value, _sharedName);
}
else if (objType == typeof(int) || objType == typeof(int?))
{
- Xamarin.Essentials.Preferences.Set(formattedKey, (obj as int?).Value, _sharedName);
+ Microsoft.Maui.Storage.Preferences.Set(formattedKey, (obj as int?).Value, _sharedName);
}
else if (objType == typeof(long) || objType == typeof(long?))
{
- Xamarin.Essentials.Preferences.Set(formattedKey, (obj as long?).Value, _sharedName);
+ Microsoft.Maui.Storage.Preferences.Set(formattedKey, (obj as long?).Value, _sharedName);
}
else if (objType == typeof(double) || objType == typeof(double?))
{
- Xamarin.Essentials.Preferences.Set(formattedKey, (obj as double?).Value, _sharedName);
+ Microsoft.Maui.Storage.Preferences.Set(formattedKey, (obj as double?).Value, _sharedName);
}
else if (objType == typeof(DateTime) || objType == typeof(DateTime?))
{
- Xamarin.Essentials.Preferences.Set(formattedKey, (obj as DateTime?).Value, _sharedName);
+ Microsoft.Maui.Storage.Preferences.Set(formattedKey, (obj as DateTime?).Value, _sharedName);
}
else
{
- Xamarin.Essentials.Preferences.Set(formattedKey, JsonConvert.SerializeObject(obj, _jsonSettings),
+ Microsoft.Maui.Storage.Preferences.Set(formattedKey, JsonConvert.SerializeObject(obj, _jsonSettings),
_sharedName);
}
}
@@ -126,9 +126,9 @@ namespace Bit.App.Services
public void Remove(string key)
{
var formattedKey = string.Format(KeyFormat, key);
- if (Xamarin.Essentials.Preferences.ContainsKey(formattedKey, _sharedName))
+ if (Microsoft.Maui.Storage.Preferences.ContainsKey(formattedKey, _sharedName))
{
- Xamarin.Essentials.Preferences.Remove(formattedKey, _sharedName);
+ Microsoft.Maui.Storage.Preferences.Remove(formattedKey, _sharedName);
}
}
diff --git a/src/App/Services/PushNotificationListenerService.cs b/src/App/Services/PushNotificationListenerService.cs
index b08a43f1c..d4340c2bc 100644
--- a/src/App/Services/PushNotificationListenerService.cs
+++ b/src/App/Services/PushNotificationListenerService.cs
@@ -17,7 +17,8 @@ using Bit.Core.Services;
using Bit.Core.Utilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Services
{
diff --git a/src/App/Services/SecureStorageService.cs b/src/App/Services/SecureStorageService.cs
index 1c1534b68..b519c21e7 100644
--- a/src/App/Services/SecureStorageService.cs
+++ b/src/App/Services/SecureStorageService.cs
@@ -16,7 +16,7 @@ namespace Bit.App.Services
public async Task GetAsync(string key)
{
var formattedKey = string.Format(_keyFormat, key);
- var val = await Xamarin.Essentials.SecureStorage.GetAsync(formattedKey);
+ var val = await Microsoft.Maui.Storage.SecureStorage.GetAsync(formattedKey);
if (typeof(T) == typeof(string))
{
return (T)(object)val;
@@ -37,11 +37,11 @@ namespace Bit.App.Services
var formattedKey = string.Format(_keyFormat, key);
if (typeof(T) == typeof(string))
{
- await Xamarin.Essentials.SecureStorage.SetAsync(formattedKey, obj as string);
+ await Microsoft.Maui.Storage.SecureStorage.SetAsync(formattedKey, obj as string);
}
else
{
- await Xamarin.Essentials.SecureStorage.SetAsync(formattedKey,
+ await Microsoft.Maui.Storage.SecureStorage.SetAsync(formattedKey,
JsonConvert.SerializeObject(obj, _jsonSettings));
}
}
@@ -49,7 +49,7 @@ namespace Bit.App.Services
public Task RemoveAsync(string key)
{
var formattedKey = string.Format(_keyFormat, key);
- Xamarin.Essentials.SecureStorage.Remove(formattedKey);
+ Microsoft.Maui.Storage.SecureStorage.Remove(formattedKey);
return Task.FromResult(0);
}
}
diff --git a/src/App/Styles/Android.xaml b/src/App/Styles/Android.xaml
index 51c2e37aa..f1bb54e79 100644
--- a/src/App/Styles/Android.xaml
+++ b/src/App/Styles/Android.xaml
@@ -1,5 +1,5 @@
-
-
+
diff --git a/src/App/Styles/Android.xaml.cs b/src/App/Styles/Android.xaml.cs
index a2e9ecfd3..c4c469ccf 100644
--- a/src/App/Styles/Android.xaml.cs
+++ b/src/App/Styles/Android.xaml.cs
@@ -1,4 +1,5 @@
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Styles
{
diff --git a/src/App/Styles/Base.xaml b/src/App/Styles/Base.xaml
index 2dac11bf7..9a10c7a1a 100644
--- a/src/App/Styles/Base.xaml
+++ b/src/App/Styles/Base.xaml
@@ -1,5 +1,5 @@
-
-
+
diff --git a/src/App/Styles/Base.xaml.cs b/src/App/Styles/Base.xaml.cs
index 2e74ee5ea..a031d5d77 100644
--- a/src/App/Styles/Base.xaml.cs
+++ b/src/App/Styles/Base.xaml.cs
@@ -1,4 +1,5 @@
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Styles
{
diff --git a/src/App/Styles/Black.xaml b/src/App/Styles/Black.xaml
index b2027f114..59d990802 100644
--- a/src/App/Styles/Black.xaml
+++ b/src/App/Styles/Black.xaml
@@ -1,5 +1,5 @@
-
-
+#ffffff
diff --git a/src/App/Styles/Black.xaml.cs b/src/App/Styles/Black.xaml.cs
index b126f1bb5..daf47356a 100644
--- a/src/App/Styles/Black.xaml.cs
+++ b/src/App/Styles/Black.xaml.cs
@@ -1,4 +1,5 @@
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Styles
{
diff --git a/src/App/Styles/Dark.xaml b/src/App/Styles/Dark.xaml
index 570e9ca2e..684a2100f 100644
--- a/src/App/Styles/Dark.xaml
+++ b/src/App/Styles/Dark.xaml
@@ -1,5 +1,5 @@
-
-
+#ffffff
diff --git a/src/App/Styles/Dark.xaml.cs b/src/App/Styles/Dark.xaml.cs
index 1a4113ab6..ac21a86b4 100644
--- a/src/App/Styles/Dark.xaml.cs
+++ b/src/App/Styles/Dark.xaml.cs
@@ -1,4 +1,5 @@
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Styles
{
diff --git a/src/App/Styles/Light.xaml b/src/App/Styles/Light.xaml
index 59a424953..f1811d28f 100644
--- a/src/App/Styles/Light.xaml
+++ b/src/App/Styles/Light.xaml
@@ -1,5 +1,5 @@
-
-
+#000000
diff --git a/src/App/Styles/Light.xaml.cs b/src/App/Styles/Light.xaml.cs
index 3ddad1482..c1b454a16 100644
--- a/src/App/Styles/Light.xaml.cs
+++ b/src/App/Styles/Light.xaml.cs
@@ -1,4 +1,5 @@
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Styles
{
diff --git a/src/App/Styles/Nord.xaml b/src/App/Styles/Nord.xaml
index d4690c2de..7aaf3952a 100644
--- a/src/App/Styles/Nord.xaml
+++ b/src/App/Styles/Nord.xaml
@@ -1,5 +1,5 @@
-
-
+#e5e9f0
diff --git a/src/App/Styles/Nord.xaml.cs b/src/App/Styles/Nord.xaml.cs
index 6baf44c3b..3052cf2f7 100644
--- a/src/App/Styles/Nord.xaml.cs
+++ b/src/App/Styles/Nord.xaml.cs
@@ -1,4 +1,5 @@
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Styles
{
diff --git a/src/App/Styles/SolarizedDark.xaml b/src/App/Styles/SolarizedDark.xaml
index e8402a0d2..c453c2af0 100644
--- a/src/App/Styles/SolarizedDark.xaml
+++ b/src/App/Styles/SolarizedDark.xaml
@@ -1,5 +1,5 @@
-
-
+#eee8d5
diff --git a/src/App/Styles/SolarizedDark.xaml.cs b/src/App/Styles/SolarizedDark.xaml.cs
index a8dc19768..153c33dff 100644
--- a/src/App/Styles/SolarizedDark.xaml.cs
+++ b/src/App/Styles/SolarizedDark.xaml.cs
@@ -1,4 +1,5 @@
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Styles
{
diff --git a/src/App/Styles/Variables.xaml b/src/App/Styles/Variables.xaml
index 49b463edf..2ee2734ad 100644
--- a/src/App/Styles/Variables.xaml
+++ b/src/App/Styles/Variables.xaml
@@ -1,5 +1,5 @@
-
-
+
diff --git a/src/App/Styles/Variables.xaml.cs b/src/App/Styles/Variables.xaml.cs
index 31582c54d..264d4022a 100644
--- a/src/App/Styles/Variables.xaml.cs
+++ b/src/App/Styles/Variables.xaml.cs
@@ -1,4 +1,5 @@
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Styles
{
diff --git a/src/App/Styles/iOS.xaml b/src/App/Styles/iOS.xaml
index b6041bcce..768f9a5f4 100644
--- a/src/App/Styles/iOS.xaml
+++ b/src/App/Styles/iOS.xaml
@@ -1,5 +1,5 @@
-
-
+
diff --git a/src/App/Styles/iOS.xaml.cs b/src/App/Styles/iOS.xaml.cs
index a08433b33..bfec6a2c6 100644
--- a/src/App/Styles/iOS.xaml.cs
+++ b/src/App/Styles/iOS.xaml.cs
@@ -1,4 +1,5 @@
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Styles
{
diff --git a/src/App/Utilities/AccountManagement/AccountsManager.cs b/src/App/Utilities/AccountManagement/AccountsManager.cs
index 919070359..c80a62488 100644
--- a/src/App/Utilities/AccountManagement/AccountsManager.cs
+++ b/src/App/Utilities/AccountManagement/AccountsManager.cs
@@ -7,7 +7,8 @@ using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities.AccountManagement
{
@@ -190,6 +191,7 @@ namespace Bit.App.Utilities.AccountManagement
}
var autoPromptBiometric = !userInitiated;
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
{
var vaultTimeout = await _stateService.GetVaultTimeoutAsync();
diff --git a/src/App/Utilities/AppHelpers.cs b/src/App/Utilities/AppHelpers.cs
index 49086a18b..224cd5b0f 100644
--- a/src/App/Utilities/AppHelpers.cs
+++ b/src/App/Utilities/AppHelpers.cs
@@ -17,8 +17,8 @@ using Bit.Core.Models.Data;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Newtonsoft.Json;
-using Xamarin.Essentials;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
diff --git a/src/App/Utilities/BoxRowVsBoxRowInputPaddingConverter.cs b/src/App/Utilities/BoxRowVsBoxRowInputPaddingConverter.cs
index 59e9790ec..4a4d81850 100644
--- a/src/App/Utilities/BoxRowVsBoxRowInputPaddingConverter.cs
+++ b/src/App/Utilities/BoxRowVsBoxRowInputPaddingConverter.cs
@@ -1,5 +1,6 @@
using System;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
diff --git a/src/App/Utilities/ColoredPasswordConverter.cs b/src/App/Utilities/ColoredPasswordConverter.cs
index 60222d2fb..ba5f76769 100644
--- a/src/App/Utilities/ColoredPasswordConverter.cs
+++ b/src/App/Utilities/ColoredPasswordConverter.cs
@@ -1,5 +1,6 @@
using System;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
diff --git a/src/App/Utilities/DateTimeConverter.cs b/src/App/Utilities/DateTimeConverter.cs
index cd8f2cec4..5393bd3d1 100644
--- a/src/App/Utilities/DateTimeConverter.cs
+++ b/src/App/Utilities/DateTimeConverter.cs
@@ -1,7 +1,8 @@
using System;
using Bit.App.Abstractions;
using Bit.Core.Utilities;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
diff --git a/src/App/Utilities/EnumHelper.cs b/src/App/Utilities/EnumHelper.cs
index ef9faf286..edf9b1a88 100644
--- a/src/App/Utilities/EnumHelper.cs
+++ b/src/App/Utilities/EnumHelper.cs
@@ -3,7 +3,7 @@ using System.Linq;
using System.Reflection;
using Bit.App.Resources;
using Bit.Core.Attributes;
-using Xamarin.CommunityToolkit.Helpers;
+using CommunityToolkit.Maui.Converters;
namespace Bit.App.Utilities
{
diff --git a/src/App/Utilities/GeneratedValueFormatter.cs b/src/App/Utilities/GeneratedValueFormatter.cs
index a718862b2..6f0199ea4 100644
--- a/src/App/Utilities/GeneratedValueFormatter.cs
+++ b/src/App/Utilities/GeneratedValueFormatter.cs
@@ -1,6 +1,7 @@
using System;
using System.Web;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
@@ -38,7 +39,8 @@ namespace Bit.App.Utilities
// iOS won't hide the zero-width space char without these div attrs, but Android won't respect
// display:inline-block and adds a newline after the password/username. Hence, only iOS gets the div.
- if (Device.RuntimePlatform == Device.iOS)
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+ if (Device.RuntimePlatform == Device.iOS)
{
result += "
";
}
@@ -112,7 +114,8 @@ namespace Bit.App.Utilities
}
// Close off iOS div
- if (Device.RuntimePlatform == Device.iOS)
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+ if (Device.RuntimePlatform == Device.iOS)
{
result += "
";
}
diff --git a/src/App/Utilities/I18nExtension.cs b/src/App/Utilities/I18nExtension.cs
index b66040c75..e048d1afe 100644
--- a/src/App/Utilities/I18nExtension.cs
+++ b/src/App/Utilities/I18nExtension.cs
@@ -1,8 +1,9 @@
using System;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
-using Xamarin.Forms;
-using Xamarin.Forms.Xaml;
+using Microsoft.Maui.Controls.Xaml;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
diff --git a/src/App/Utilities/IconGlyphConverter.cs b/src/App/Utilities/IconGlyphConverter.cs
index c68c103ac..1c51688fb 100644
--- a/src/App/Utilities/IconGlyphConverter.cs
+++ b/src/App/Utilities/IconGlyphConverter.cs
@@ -1,7 +1,8 @@
using System;
using System.Globalization;
using Bit.Core.Models.View;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
diff --git a/src/App/Utilities/IconImageConverter.cs b/src/App/Utilities/IconImageConverter.cs
index 87ad2391b..8e1752429 100644
--- a/src/App/Utilities/IconImageConverter.cs
+++ b/src/App/Utilities/IconImageConverter.cs
@@ -4,7 +4,8 @@ using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
diff --git a/src/App/Utilities/InverseBoolConverter.cs b/src/App/Utilities/InverseBoolConverter.cs
index b0ee98993..27f6a0e8c 100644
--- a/src/App/Utilities/InverseBoolConverter.cs
+++ b/src/App/Utilities/InverseBoolConverter.cs
@@ -1,5 +1,6 @@
using System;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
diff --git a/src/App/Utilities/IsNotNullConverter.cs b/src/App/Utilities/IsNotNullConverter.cs
index 5888ab362..c0d309fee 100644
--- a/src/App/Utilities/IsNotNullConverter.cs
+++ b/src/App/Utilities/IsNotNullConverter.cs
@@ -1,5 +1,6 @@
using System;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
diff --git a/src/App/Utilities/IsNullConverter.cs b/src/App/Utilities/IsNullConverter.cs
index 0d6f85964..6961eb177 100644
--- a/src/App/Utilities/IsNullConverter.cs
+++ b/src/App/Utilities/IsNullConverter.cs
@@ -1,5 +1,6 @@
using System;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
diff --git a/src/App/Utilities/LocalizableEnumConverter.cs b/src/App/Utilities/LocalizableEnumConverter.cs
index 8c1dc287a..1da0cf1ba 100644
--- a/src/App/Utilities/LocalizableEnumConverter.cs
+++ b/src/App/Utilities/LocalizableEnumConverter.cs
@@ -1,5 +1,6 @@
using System;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
diff --git a/src/App/Utilities/PageExtensions.cs b/src/App/Utilities/PageExtensions.cs
index ec64f20ef..fe8132a65 100644
--- a/src/App/Utilities/PageExtensions.cs
+++ b/src/App/Utilities/PageExtensions.cs
@@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
diff --git a/src/App/Utilities/PermissionManager.cs b/src/App/Utilities/PermissionManager.cs
index 8af2ecff2..99a7de933 100644
--- a/src/App/Utilities/PermissionManager.cs
+++ b/src/App/Utilities/PermissionManager.cs
@@ -1,6 +1,5 @@
using System.Threading.Tasks;
-using Xamarin.Essentials;
-using static Xamarin.Essentials.Permissions;
+using static Microsoft.Maui.ApplicationModel.Permissions;
namespace Bit.App.Utilities
{
diff --git a/src/App/Utilities/ProgressBarExtensions.cs b/src/App/Utilities/ProgressBarExtensions.cs
index 7dcf15f85..94d05b511 100644
--- a/src/App/Utilities/ProgressBarExtensions.cs
+++ b/src/App/Utilities/ProgressBarExtensions.cs
@@ -1,4 +1,5 @@
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
diff --git a/src/App/Utilities/SendIconGlyphConverter.cs b/src/App/Utilities/SendIconGlyphConverter.cs
index 512586768..1cf2fc5f1 100644
--- a/src/App/Utilities/SendIconGlyphConverter.cs
+++ b/src/App/Utilities/SendIconGlyphConverter.cs
@@ -3,7 +3,8 @@ using System.Globalization;
using Bit.Core;
using Bit.Core.Enums;
using Bit.Core.Models.View;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
diff --git a/src/App/Utilities/StringHasValueConverter.cs b/src/App/Utilities/StringHasValueConverter.cs
index 4410c9143..b0f3245db 100644
--- a/src/App/Utilities/StringHasValueConverter.cs
+++ b/src/App/Utilities/StringHasValueConverter.cs
@@ -1,5 +1,6 @@
using System;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
diff --git a/src/App/Utilities/ThemeManager.cs b/src/App/Utilities/ThemeManager.cs
index 65d2d2cfe..9d98db4aa 100644
--- a/src/App/Utilities/ThemeManager.cs
+++ b/src/App/Utilities/ThemeManager.cs
@@ -6,7 +6,9 @@ using Bit.App.Styles;
using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
-using Xamarin.Forms;
+using Microsoft.Maui.ApplicationModel;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
@@ -60,7 +62,8 @@ namespace Bit.App.Utilities
resources.MergedDictionaries.Add(new Base());
// Platform styles
- if (Device.RuntimePlatform == Device.Android)
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+ if (Device.RuntimePlatform == Device.Android)
{
resources.MergedDictionaries.Add(new Android());
}
@@ -147,9 +150,9 @@ namespace Bit.App.Utilities
{
// called from iOS extension
var app = new App(new AppOptions { IosExtension = true });
- return app.RequestedTheme == OSAppTheme.Dark;
+ return app.RequestedTheme == AppTheme.Dark;
}
- return Application.Current.RequestedTheme == OSAppTheme.Dark;
+ return Application.Current.RequestedTheme == AppTheme.Dark;
}
public static void ApplyResourcesTo(VisualElement element)
diff --git a/src/App/Utilities/TimerTask.cs b/src/App/Utilities/TimerTask.cs
index 0288408ac..27e0b81af 100644
--- a/src/App/Utilities/TimerTask.cs
+++ b/src/App/Utilities/TimerTask.cs
@@ -2,7 +2,8 @@
using System.Threading;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
diff --git a/src/App/Utilities/UpperCaseConverter.cs b/src/App/Utilities/UpperCaseConverter.cs
index 592f95326..a02c83de9 100644
--- a/src/App/Utilities/UpperCaseConverter.cs
+++ b/src/App/Utilities/UpperCaseConverter.cs
@@ -1,6 +1,7 @@
using System;
using System.Globalization;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
diff --git a/src/App/Utilities/VerificationActionsFlowHelper.cs b/src/App/Utilities/VerificationActionsFlowHelper.cs
index eaf3376e9..9955233fc 100644
--- a/src/App/Utilities/VerificationActionsFlowHelper.cs
+++ b/src/App/Utilities/VerificationActionsFlowHelper.cs
@@ -7,7 +7,8 @@ using Bit.App.Pages.Accounts;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
-using Xamarin.Forms;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
namespace Bit.App.Utilities
{
diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj
index 76c88095e..2ed069187 100644
--- a/src/Core/Core.csproj
+++ b/src/Core/Core.csproj
@@ -1,17 +1,14 @@
-
- netstandard2.1
+ net7.0Bit.CoreBitwardenCoreDebug;Release;FDroid
-
pdbonlytrue
-
@@ -22,17 +19,13 @@
-
-
-
-
@@ -40,11 +33,11 @@
runtime; build; native; contentfiles; analyzers; buildtransitiveall
+
-
-
+
\ No newline at end of file
diff --git a/src/Maui/Bitwarden/Abstractions/IAccountsManager.cs b/src/Maui/Bitwarden/Abstractions/IAccountsManager.cs
new file mode 100644
index 000000000..ab78c6ca4
--- /dev/null
+++ b/src/Maui/Bitwarden/Abstractions/IAccountsManager.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Threading.Tasks;
+using Bit.App.Models;
+
+namespace Bit.App.Abstractions
+{
+ public interface IAccountsManager
+ {
+ void Init(Func getOptionsFunc, IAccountsManagerHost accountsManagerHost);
+ Task NavigateOnAccountChangeAsync(bool? isAuthed = null);
+ Task StartDefaultNavigationFlowAsync(Action appOptionsAction);
+ Task LogOutAsync(string userId, bool userInitiated, bool expired);
+ Task PromptToSwitchToExistingAccountAsync(string userId);
+ }
+}
diff --git a/src/Maui/Bitwarden/Abstractions/IAccountsManagerHost.cs b/src/Maui/Bitwarden/Abstractions/IAccountsManagerHost.cs
new file mode 100644
index 000000000..17e5ae0e2
--- /dev/null
+++ b/src/Maui/Bitwarden/Abstractions/IAccountsManagerHost.cs
@@ -0,0 +1,14 @@
+using System.Threading.Tasks;
+using Bit.Core.Enums;
+
+namespace Bit.App.Abstractions
+{
+ public interface INavigationParams { }
+
+ public interface IAccountsManagerHost
+ {
+ Task SetPreviousPageInfoAsync();
+ void Navigate(NavigationTarget navTarget, INavigationParams navParams = null);
+ Task UpdateThemeAsync();
+ }
+}
diff --git a/src/Maui/Bitwarden/Abstractions/IDeepLinkContext.cs b/src/Maui/Bitwarden/Abstractions/IDeepLinkContext.cs
new file mode 100644
index 000000000..345596d50
--- /dev/null
+++ b/src/Maui/Bitwarden/Abstractions/IDeepLinkContext.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Bit.App.Abstractions
+{
+ public interface IDeepLinkContext
+ {
+ bool OnNewUri(Uri uri);
+ }
+}
diff --git a/src/Maui/Bitwarden/Abstractions/IDeviceActionService.cs b/src/Maui/Bitwarden/Abstractions/IDeviceActionService.cs
new file mode 100644
index 000000000..c8b7e94f8
--- /dev/null
+++ b/src/Maui/Bitwarden/Abstractions/IDeviceActionService.cs
@@ -0,0 +1,45 @@
+using System.Threading.Tasks;
+using Bit.App.Utilities.Prompts;
+using Bit.Core.Enums;
+using Bit.Core.Models;
+
+namespace Bit.App.Abstractions
+{
+ public interface IDeviceActionService
+ {
+ string DeviceUserAgent { get; }
+ Bit.Core.Enums.DeviceType DeviceType { get; }
+ int SystemMajorVersion();
+ string SystemModel();
+ string GetBuildNumber();
+
+ void Toast(string text, bool longDuration = false);
+ Task ShowLoadingAsync(string text);
+ Task HideLoadingAsync();
+ Task DisplayPromptAync(string title = null, string description = null, string text = null,
+ string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false,
+ bool autofocus = true, bool password = false);
+ Task DisplayValidatablePromptAsync(ValidatablePromptConfig config);
+ Task DisplayAlertAsync(string title, string message, string cancel, params string[] buttons);
+ Task DisplayActionSheetAsync(string title, string cancel, string destruction, params string[] buttons);
+
+ bool SupportsFaceBiometric();
+ Task SupportsFaceBiometricAsync();
+ bool SupportsNfc();
+ bool SupportsCamera();
+ bool SupportsFido2();
+
+ bool LaunchApp(string appName);
+ void RateApp();
+ void OpenAccessibilitySettings();
+ void OpenAccessibilityOverlayPermissionSettings();
+ void OpenAutofillSettings();
+ long GetActiveTime();
+ void CloseMainApp();
+ float GetSystemFontSizeScale();
+ Task OnAccountSwitchCompleteAsync();
+ Task SetScreenCaptureAllowedAsync();
+ void OpenAppSettings();
+ void CloseExtensionPopUp();
+ }
+}
diff --git a/src/Maui/Bitwarden/Abstractions/ILocalizeService.cs b/src/Maui/Bitwarden/Abstractions/ILocalizeService.cs
new file mode 100644
index 000000000..09b0fb924
--- /dev/null
+++ b/src/Maui/Bitwarden/Abstractions/ILocalizeService.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Globalization;
+
+namespace Bit.App.Abstractions
+{
+ public interface ILocalizeService
+ {
+ CultureInfo GetCurrentCultureInfo();
+
+ ///
+ /// Format date using device locale.
+ /// Needed for iOS as it provides locales unsupported in .Net
+ ///
+ string GetLocaleShortDate(DateTime? date);
+
+ ///
+ /// Format time using device locale.
+ /// Needed for iOS as it provides locales unsupported in .Net
+ ///
+ string GetLocaleShortTime(DateTime? time);
+ }
+}
diff --git a/src/Maui/Bitwarden/Abstractions/IPasswordRepromptService.cs b/src/Maui/Bitwarden/Abstractions/IPasswordRepromptService.cs
new file mode 100644
index 000000000..579d9ab44
--- /dev/null
+++ b/src/Maui/Bitwarden/Abstractions/IPasswordRepromptService.cs
@@ -0,0 +1,15 @@
+using System.Threading.Tasks;
+
+namespace Bit.App.Abstractions
+{
+ public interface IPasswordRepromptService
+ {
+ string[] ProtectedFields { get; }
+
+ Task ShowPasswordPromptAsync();
+
+ Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync();
+
+ Task Enabled();
+ }
+}
diff --git a/src/Maui/Bitwarden/Abstractions/IPushNotificationListenerService.cs b/src/Maui/Bitwarden/Abstractions/IPushNotificationListenerService.cs
new file mode 100644
index 000000000..fdbb6ca88
--- /dev/null
+++ b/src/Maui/Bitwarden/Abstractions/IPushNotificationListenerService.cs
@@ -0,0 +1,17 @@
+using System.Threading.Tasks;
+using Bit.App.Models;
+using Newtonsoft.Json.Linq;
+
+namespace Bit.App.Abstractions
+{
+ public interface IPushNotificationListenerService
+ {
+ Task OnMessageAsync(JObject values, string device);
+ Task OnRegisteredAsync(string token, string device);
+ void OnUnregistered(string device);
+ void OnError(string message, string device);
+ Task OnNotificationTapped(BaseNotificationData data);
+ Task OnNotificationDismissed(BaseNotificationData data);
+ bool ShouldShowNotification();
+ }
+}
diff --git a/src/Maui/Bitwarden/Abstractions/IPushNotificationService.cs b/src/Maui/Bitwarden/Abstractions/IPushNotificationService.cs
new file mode 100644
index 000000000..5d69be1aa
--- /dev/null
+++ b/src/Maui/Bitwarden/Abstractions/IPushNotificationService.cs
@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Bit.App.Models;
+
+namespace Bit.App.Abstractions
+{
+ public interface IPushNotificationService
+ {
+ bool IsRegisteredForPush { get; }
+ Task AreNotificationsSettingsEnabledAsync();
+ Task GetTokenAsync();
+ Task RegisterAsync();
+ Task UnregisterAsync();
+ void SendLocalNotification(string title, string message, BaseNotificationData data);
+ void DismissLocalNotification(string notificationId);
+ }
+}
diff --git a/src/Maui/Bitwarden/App.xaml b/src/Maui/Bitwarden/App.xaml
new file mode 100644
index 000000000..58b8d50fc
--- /dev/null
+++ b/src/Maui/Bitwarden/App.xaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Maui/Bitwarden/App.xaml.cs b/src/Maui/Bitwarden/App.xaml.cs
new file mode 100644
index 000000000..7df514569
--- /dev/null
+++ b/src/Maui/Bitwarden/App.xaml.cs
@@ -0,0 +1,546 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Bit.App.Abstractions;
+using Bit.App.Models;
+using Bit.App.Pages;
+using Bit.App.Resources;
+using Bit.App.Services;
+using Bit.App.Utilities;
+using Bit.App.Utilities.AccountManagement;
+using Bit.Core;
+using Bit.Core.Abstractions;
+using Bit.Core.Enums;
+using Bit.Core.Models.Data;
+using Bit.Core.Models.Response;
+using Bit.Core.Services;
+using Bit.Core.Utilities;
+using Microsoft.Maui.Controls.Xaml;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
+namespace Bit.App
+{
+ public partial class App : Application, IAccountsManagerHost
+ {
+ public const string POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE = "popAllAndGoToTabGenerator";
+ public const string POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE = "popAllAndGoToTabMyVault";
+ public const string POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE = "popAllAndGoToTabSend";
+ public const string POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE = "popAllAndGoToAutofillCiphers";
+
+ private readonly IBroadcasterService _broadcasterService;
+ private readonly IMessagingService _messagingService;
+ private readonly IStateService _stateService;
+ private readonly IVaultTimeoutService _vaultTimeoutService;
+ private readonly ISyncService _syncService;
+ private readonly IAuthService _authService;
+ private readonly IDeviceActionService _deviceActionService;
+ private readonly IFileService _fileService;
+ private readonly IAccountsManager _accountsManager;
+ private readonly IPushNotificationService _pushNotificationService;
+ private readonly IConfigService _configService;
+ private static bool _isResumed;
+ // these variables are static because the app is launching new activities on notification click, creating new instances of App.
+ private static bool _pendingCheckPasswordlessLoginRequests;
+ private static object _processingLoginRequestLock = new object();
+
+ public App(AppOptions appOptions)
+ {
+ Options = appOptions ?? new AppOptions();
+ if (Options.IosExtension)
+ {
+ Current = this;
+ return;
+ }
+ _broadcasterService = ServiceContainer.Resolve("broadcasterService");
+ _messagingService = ServiceContainer.Resolve("messagingService");
+ _stateService = ServiceContainer.Resolve("stateService");
+ _vaultTimeoutService = ServiceContainer.Resolve("vaultTimeoutService");
+ _syncService = ServiceContainer.Resolve("syncService");
+ _authService = ServiceContainer.Resolve("authService");
+ _deviceActionService = ServiceContainer.Resolve("deviceActionService");
+ _fileService = ServiceContainer.Resolve();
+ _accountsManager = ServiceContainer.Resolve("accountsManager");
+ _pushNotificationService = ServiceContainer.Resolve();
+ _configService = ServiceContainer.Resolve();
+
+ _accountsManager.Init(() => Options, this);
+
+ Bootstrap();
+ _broadcasterService.Subscribe(nameof(App), async (message) =>
+ {
+ try
+ {
+ if (message.Command == "showDialog")
+ {
+ var details = message.Data as DialogDetails;
+ var confirmed = true;
+ var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
+ AppResources.Ok : details.ConfirmText;
+ Device.BeginInvokeOnMainThread(async () =>
+ {
+ if (!string.IsNullOrWhiteSpace(details.CancelText))
+ {
+ confirmed = await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText,
+ details.CancelText);
+ }
+ else
+ {
+ await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText);
+ }
+ _messagingService.Send("showDialogResolve", new Tuple(details.DialogId, confirmed));
+ });
+ }
+ else if (message.Command == "resumed")
+ {
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+ if (Device.RuntimePlatform == Device.iOS)
+ {
+ ResumedAsync().FireAndForget();
+ }
+ }
+ else if (message.Command == "slept")
+ {
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+ if (Device.RuntimePlatform == Device.iOS)
+ {
+ await SleptAsync();
+ }
+ }
+ else if (message.Command == "migrated")
+ {
+ await Task.Delay(1000);
+ await _accountsManager.NavigateOnAccountChangeAsync();
+ }
+ else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE ||
+ message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE ||
+ message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE ||
+ message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE ||
+ message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
+ {
+ if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
+ {
+ Options.OtpData = new OtpData((string)message.Data);
+ }
+
+ Device.InvokeOnMainThreadAsync(async () =>
+ {
+ if (Current.MainPage is TabsPage tabsPage)
+ {
+ while (tabsPage.Navigation.ModalStack.Count > 0)
+ {
+ await tabsPage.Navigation.PopModalAsync(false);
+ }
+ if (message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE)
+ {
+ Current.MainPage = new NavigationPage(new CipherSelectionPage(Options));
+ }
+ else if (message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE)
+ {
+ Options.MyVaultTile = false;
+ tabsPage.ResetToVaultPage();
+ }
+ else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE)
+ {
+ Options.GeneratorTile = false;
+ tabsPage.ResetToGeneratorPage();
+ }
+ else if (message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE)
+ {
+ tabsPage.ResetToSendPage();
+ }
+ else if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
+ {
+ tabsPage.ResetToVaultPage();
+ await tabsPage.Navigation.PushModalAsync(new NavigationPage(new CipherSelectionPage(Options)));
+ }
+ }
+ });
+ }
+ else if (message.Command == "convertAccountToKeyConnector")
+ {
+ Device.BeginInvokeOnMainThread(async () =>
+ {
+ await Application.Current.MainPage.Navigation.PushModalAsync(
+ new NavigationPage(new RemoveMasterPasswordPage()));
+ });
+ }
+ else if (message.Command == Constants.ForceUpdatePassword)
+ {
+ Device.BeginInvokeOnMainThread(async () =>
+ {
+ await Application.Current.MainPage.Navigation.PushModalAsync(
+ new NavigationPage(new UpdateTempPasswordPage()));
+ });
+ }
+ else if (message.Command == "syncCompleted")
+ {
+ await _configService.GetAsync(true);
+ }
+ else if (message.Command == Constants.PasswordlessLoginRequestKey
+ || message.Command == "unlocked"
+ || message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
+ {
+ lock (_processingLoginRequestLock)
+ {
+ // lock doesn't allow for async execution
+ CheckPasswordlessLoginRequestsAsync().Wait();
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LoggerHelper.LogEvenIfCantBeResolved(ex);
+ }
+ });
+ }
+
+ private async Task CheckPasswordlessLoginRequestsAsync()
+ {
+ if (!_isResumed)
+ {
+ _pendingCheckPasswordlessLoginRequests = true;
+ return;
+ }
+ _pendingCheckPasswordlessLoginRequests = false;
+ if (await _vaultTimeoutService.IsLockedAsync())
+ {
+ return;
+ }
+
+ var notification = await _stateService.GetPasswordlessLoginNotificationAsync();
+ if (notification == null)
+ {
+ return;
+ }
+
+ if (await CheckShouldSwitchActiveUserAsync(notification))
+ {
+ return;
+ }
+
+ // Delay to wait for the vault page to appear
+ await Task.Delay(2000);
+ // if there is a request modal opened ignore all incoming requests
+ if (App.Current.MainPage.Navigation.ModalStack.Any(p => p is NavigationPage navPage && navPage.CurrentPage is LoginPasswordlessPage))
+ {
+ return;
+ }
+ var loginRequestData = await _authService.GetPasswordlessLoginRequestByIdAsync(notification.Id);
+ var page = new LoginPasswordlessPage(new LoginPasswordlessDetails()
+ {
+ PubKey = loginRequestData.PublicKey,
+ Id = loginRequestData.Id,
+ IpAddress = loginRequestData.RequestIpAddress,
+ Email = await _stateService.GetEmailAsync(),
+ FingerprintPhrase = loginRequestData.FingerprintPhrase,
+ RequestDate = loginRequestData.CreationDate,
+ DeviceType = loginRequestData.RequestDeviceType,
+ Origin = loginRequestData.Origin
+ });
+ await _stateService.SetPasswordlessLoginNotificationAsync(null);
+ _pushNotificationService.DismissLocalNotification(Constants.PasswordlessNotificationId);
+ if (!loginRequestData.IsExpired)
+ {
+ await Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page)));
+ }
+ }
+
+ private async Task CheckShouldSwitchActiveUserAsync(PasswordlessRequestNotification notification)
+ {
+ var activeUserId = await _stateService.GetActiveUserIdAsync();
+ if (notification.UserId == activeUserId)
+ {
+ return false;
+ }
+
+ var notificationUserEmail = await _stateService.GetEmailAsync(notification.UserId);
+ Device.BeginInvokeOnMainThread(async () =>
+ {
+ try
+ {
+ var result = await _deviceActionService.DisplayAlertAsync(AppResources.LogInRequested, string.Format(AppResources.LoginAttemptFromXDoYouWantToSwitchToThisAccount, notificationUserEmail), AppResources.Cancel, AppResources.Ok);
+ if (result == AppResources.Ok)
+ {
+ await _stateService.SetActiveUserAsync(notification.UserId);
+ _messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
+ }
+ }
+ catch (Exception ex)
+ {
+ LoggerHelper.LogEvenIfCantBeResolved(ex);
+ }
+ });
+ return true;
+ }
+
+ public AppOptions Options { get; private set; }
+
+ protected async override void OnStart()
+ {
+ System.Diagnostics.Debug.WriteLine("XF App: OnStart");
+ _isResumed = true;
+ await ClearCacheIfNeededAsync();
+ Prime();
+ if (string.IsNullOrWhiteSpace(Options.Uri))
+ {
+ var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService,
+ _stateService);
+ if (!updated)
+ {
+ SyncIfNeeded();
+ }
+ }
+ if (_pendingCheckPasswordlessLoginRequests)
+ {
+ _messagingService.Send(Constants.PasswordlessLoginRequestKey);
+ }
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+ if (Device.RuntimePlatform == Device.Android)
+ {
+ await _vaultTimeoutService.CheckVaultTimeoutAsync();
+ // Reset delay on every start
+ _vaultTimeoutService.DelayLockAndLogoutMs = null;
+ }
+
+ await _configService.GetAsync();
+ _messagingService.Send("startEventTimer");
+ }
+
+ protected async override void OnSleep()
+ {
+ System.Diagnostics.Debug.WriteLine("XF App: OnSleep");
+ _isResumed = false;
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+ if (Device.RuntimePlatform == Device.Android)
+ {
+ var isLocked = await _vaultTimeoutService.IsLockedAsync();
+ if (!isLocked)
+ {
+ await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
+ }
+ if (!SetTabsPageFromAutofill(isLocked))
+ {
+ ClearAutofillUri();
+ }
+ await SleptAsync();
+ }
+ }
+
+ protected override void OnResume()
+ {
+ System.Diagnostics.Debug.WriteLine("XF App: OnResume");
+ _isResumed = true;
+ if (_pendingCheckPasswordlessLoginRequests)
+ {
+ _messagingService.Send(Constants.PasswordlessLoginRequestKey);
+ }
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+ if (Device.RuntimePlatform == Device.Android)
+ {
+ ResumedAsync().FireAndForget();
+ }
+ }
+
+ private async Task SleptAsync()
+ {
+ await _vaultTimeoutService.CheckVaultTimeoutAsync();
+ await ClearSensitiveFieldsAsync();
+ _messagingService.Send("stopEventTimer");
+ }
+
+ private async Task ResumedAsync()
+ {
+ await _stateService.CheckExtensionActiveUserAndSwitchIfNeededAsync();
+ await _vaultTimeoutService.CheckVaultTimeoutAsync();
+ await ClearSensitiveFieldsAsync();
+ _messagingService.Send("startEventTimer");
+ await UpdateThemeAsync();
+ await ClearCacheIfNeededAsync();
+ Prime();
+ SyncIfNeeded();
+ if (Current.MainPage is NavigationPage navPage && navPage.CurrentPage is LockPage lockPage)
+ {
+ await lockPage.PromptBiometricAfterResumeAsync();
+ }
+ }
+
+ public async Task UpdateThemeAsync()
+ {
+ await Device.InvokeOnMainThreadAsync(() =>
+ {
+ ThemeManager.SetTheme(Current.Resources);
+ _messagingService.Send("updatedTheme");
+ });
+ }
+
+ private async Task ClearSensitiveFieldsAsync()
+ {
+ await Device.InvokeOnMainThreadAsync(() =>
+ {
+ _messagingService.Send(Constants.ClearSensitiveFields);
+ });
+ }
+
+ private void SetCulture()
+ {
+ // Calendars are removed by linker. ref https://bugzilla.xamarin.com/show_bug.cgi?id=59077
+ new System.Globalization.ThaiBuddhistCalendar();
+ new System.Globalization.HijriCalendar();
+ new System.Globalization.UmAlQuraCalendar();
+ }
+
+ private async Task ClearCacheIfNeededAsync()
+ {
+ var lastClear = await _stateService.GetLastFileCacheClearAsync();
+ if ((DateTime.UtcNow - lastClear.GetValueOrDefault(DateTime.MinValue)).TotalDays >= 1)
+ {
+ var task = Task.Run(() => _fileService.ClearCacheAsync());
+ }
+ }
+
+ private void ClearAutofillUri()
+ {
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+ if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri))
+ {
+ Options.Uri = null;
+ }
+ }
+
+ private bool SetTabsPageFromAutofill(bool isLocked)
+ {
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+ if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri) &&
+ !Options.FromAutofillFramework)
+ {
+ Task.Run(() =>
+ {
+ Device.BeginInvokeOnMainThread(() =>
+ {
+ Options.Uri = null;
+ if (isLocked)
+ {
+ Current.MainPage = new NavigationPage(new LockPage());
+ }
+ else
+ {
+ Current.MainPage = new TabsPage();
+ }
+ });
+ });
+ return true;
+ }
+ return false;
+ }
+
+ private void Prime()
+ {
+ Task.Run(() =>
+ {
+ var word = EEFLongWordList.Instance.List[1];
+ var parsedDomain = DomainName.TryParse("https://bitwarden.com", out var domainName);
+ });
+ }
+
+ private void Bootstrap()
+ {
+ InitializeComponent();
+ SetCulture();
+ ThemeManager.SetTheme(Current.Resources);
+ Current.RequestedThemeChanged += (s, a) =>
+ {
+ UpdateThemeAsync();
+ };
+ Current.MainPage = new NavigationPage(new HomePage(Options));
+ _accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
+ ServiceContainer.Resolve("platformUtilsService").Init();
+ }
+
+ private void SyncIfNeeded()
+ {
+ if (Microsoft.Maui.Networking.Connectivity.NetworkAccess == Microsoft.Maui.Networking.NetworkAccess.None)
+ {
+ return;
+ }
+ Task.Run(async () =>
+ {
+ var lastSync = await _syncService.GetLastSyncAsync();
+ if (lastSync == null || ((DateTime.UtcNow - lastSync) > TimeSpan.FromMinutes(30)))
+ {
+ await Task.Delay(1000);
+ await _syncService.FullSyncAsync(false);
+ }
+ });
+ }
+
+ public async Task SetPreviousPageInfoAsync()
+ {
+ PreviousPageInfo lastPageBeforeLock = null;
+ if (Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0)
+ {
+ var topPage = tabbedPage.Navigation.ModalStack[tabbedPage.Navigation.ModalStack.Count - 1];
+ if (topPage is NavigationPage navPage)
+ {
+ if (navPage.CurrentPage is CipherDetailsPage cipherDetailsPage)
+ {
+ lastPageBeforeLock = new PreviousPageInfo
+ {
+ Page = "view",
+ CipherId = cipherDetailsPage.ViewModel.CipherId
+ };
+ }
+ else if (navPage.CurrentPage is CipherAddEditPage cipherAddEditPage && cipherAddEditPage.ViewModel.EditMode)
+ {
+ lastPageBeforeLock = new PreviousPageInfo
+ {
+ Page = "edit",
+ CipherId = cipherAddEditPage.ViewModel.CipherId
+ };
+ }
+ }
+ }
+ await _stateService.SetPreviousPageInfoAsync(lastPageBeforeLock);
+ }
+
+ public void Navigate(NavigationTarget navTarget, INavigationParams navParams)
+ {
+ switch (navTarget)
+ {
+ case NavigationTarget.HomeLogin:
+ Current.MainPage = new NavigationPage(new HomePage(Options));
+ break;
+ case NavigationTarget.Login:
+ if (navParams is LoginNavigationParams loginParams)
+ {
+ Current.MainPage = new NavigationPage(new LoginPage(loginParams.Email, Options));
+ }
+ break;
+ case NavigationTarget.Lock:
+ if (navParams is LockNavigationParams lockParams)
+ {
+ Current.MainPage = new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric));
+ }
+ else
+ {
+ Current.MainPage = new NavigationPage(new LockPage(Options));
+ }
+ break;
+ case NavigationTarget.Home:
+ Current.MainPage = new TabsPage(Options);
+ break;
+ case NavigationTarget.AddEditCipher:
+ Current.MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options));
+ break;
+ case NavigationTarget.AutofillCiphers:
+ case NavigationTarget.OtpCipherSelection:
+ Current.MainPage = new NavigationPage(new CipherSelectionPage(Options));
+ break;
+ case NavigationTarget.SendAddEdit:
+ Current.MainPage = new NavigationPage(new SendAddEditPage(Options));
+ break;
+ }
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Behaviors/EditorPreventAutoBottomScrollingOnFocusedBehavior.cs b/src/Maui/Bitwarden/Behaviors/EditorPreventAutoBottomScrollingOnFocusedBehavior.cs
new file mode 100644
index 000000000..618f821ce
--- /dev/null
+++ b/src/Maui/Bitwarden/Behaviors/EditorPreventAutoBottomScrollingOnFocusedBehavior.cs
@@ -0,0 +1,43 @@
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Behaviors
+{
+ ///
+ /// This behavior prevents the Editor to be automatically scrolled to the bottom on focus.
+ /// This is needed due to this Xamarin Forms issue: https://github.com/xamarin/Xamarin.Forms/issues/2233
+ ///
+ public class EditorPreventAutoBottomScrollingOnFocusedBehavior : Behavior
+ {
+ public static readonly BindableProperty ParentScrollViewProperty
+ = BindableProperty.Create(nameof(ParentScrollView), typeof(ScrollView), typeof(EditorPreventAutoBottomScrollingOnFocusedBehavior));
+
+ public ScrollView ParentScrollView
+ {
+ get => (ScrollView)GetValue(ParentScrollViewProperty);
+ set => SetValue(ParentScrollViewProperty, value);
+ }
+
+ protected override void OnAttachedTo(Editor bindable)
+ {
+ base.OnAttachedTo(bindable);
+
+ bindable.Focused += OnFocused;
+ }
+
+ private void OnFocused(object sender, FocusEventArgs e)
+ {
+ if (DeviceInfo.Platform.Equals(DevicePlatform.iOS) && ParentScrollView != null)
+ {
+ ParentScrollView.ScrollToAsync(ParentScrollView.ScrollX, ParentScrollView.ScrollY, true);
+ }
+ }
+
+ protected override void OnDetachingFrom(Editor bindable)
+ {
+ bindable.Focused -= OnFocused;
+
+ base.OnDetachingFrom(bindable);
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Bitwarden.csproj b/src/Maui/Bitwarden/Bitwarden.csproj
new file mode 100644
index 000000000..56bcb15f3
--- /dev/null
+++ b/src/Maui/Bitwarden/Bitwarden.csproj
@@ -0,0 +1,357 @@
+
+
+
+ net7.0-android;net7.0-ios
+ $(TargetFrameworks);net7.0-windows10.0.19041.0
+
+
+ Exe
+ Bit.App
+ true
+ true
+ enable
+
+
+ Bitwarden
+
+
+ com.x8bit.bitwarden
+ ccf4766c-a36c-4647-900c-0ea7d323ccc6
+
+
+ 1.0
+ 1
+
+ 11.0
+ 13.1
+ 21.0
+ 10.0.17763.0
+ 10.0.17763.0
+ 6.5
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Maui/Bitwarden/Bitwarden.sln b/src/Maui/Bitwarden/Bitwarden.sln
new file mode 100644
index 000000000..b87cddfac
--- /dev/null
+++ b/src/Maui/Bitwarden/Bitwarden.sln
@@ -0,0 +1,28 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 25.0.1706.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bitwarden", "Bitwarden.csproj", "{58FBB5C6-CBD6-470D-90A6-00E89C0CD817}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ FDroid|Any CPU = FDroid|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {58FBB5C6-CBD6-470D-90A6-00E89C0CD817}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {58FBB5C6-CBD6-470D-90A6-00E89C0CD817}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {58FBB5C6-CBD6-470D-90A6-00E89C0CD817}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {58FBB5C6-CBD6-470D-90A6-00E89C0CD817}.Release|Any CPU.Build.0 = Release|Any CPU
+ {58FBB5C6-CBD6-470D-90A6-00E89C0CD817}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
+ {58FBB5C6-CBD6-470D-90A6-00E89C0CD817}.FDroid|Any CPU.Build.0 = Debug|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {F599106B-483D-46B0-BB58-A6F3AE484CE3}
+ EndGlobalSection
+EndGlobal
diff --git a/src/Maui/Bitwarden/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml b/src/Maui/Bitwarden/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml
new file mode 100644
index 000000000..1f6580e3f
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Maui/Bitwarden/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs b/src/Maui/Bitwarden/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs
new file mode 100644
index 000000000..3db4084f1
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayView.xaml.cs
@@ -0,0 +1,196 @@
+using System;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using Bit.Core.Abstractions;
+using Bit.Core.Utilities;
+using Bit.App.Utilities;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public partial class AccountSwitchingOverlayView : ContentView
+ {
+ public static readonly BindableProperty MainPageProperty = BindableProperty.Create(
+ nameof(MainPage),
+ typeof(ContentPage),
+ typeof(AccountSwitchingOverlayView),
+ defaultBindingMode: BindingMode.OneWay);
+
+ public static readonly BindableProperty MainFabProperty = BindableProperty.Create(
+ nameof(MainFab),
+ typeof(View),
+ typeof(AccountSwitchingOverlayView),
+ defaultBindingMode: BindingMode.OneWay);
+
+ public ContentPage MainPage
+ {
+ get => (ContentPage)GetValue(MainPageProperty);
+ set => SetValue(MainPageProperty, value);
+ }
+
+ public View MainFab
+ {
+ get => (View)GetValue(MainFabProperty);
+ set => SetValue(MainFabProperty, value);
+ }
+
+ readonly LazyResolve _logger = new LazyResolve("logger");
+
+ public AccountSwitchingOverlayView()
+ {
+ InitializeComponent();
+
+ ToggleVisibililtyCommand = new AsyncCommand(ToggleVisibilityAsync,
+ onException: ex => _logger.Value.Exception(ex),
+ allowsMultipleExecutions: false);
+
+ SelectAccountCommand = new AsyncCommand(SelectAccountAsync,
+ onException: ex => _logger.Value.Exception(ex),
+ allowsMultipleExecutions: false);
+
+ LongPressAccountCommand = new AsyncCommand(LongPressAccountAsync,
+ onException: ex => _logger.Value.Exception(ex),
+ allowsMultipleExecutions: false);
+ }
+
+ public AccountSwitchingOverlayViewModel ViewModel => BindingContext as AccountSwitchingOverlayViewModel;
+
+ public ICommand ToggleVisibililtyCommand { get; }
+
+ public ICommand SelectAccountCommand { get; }
+
+ public ICommand LongPressAccountCommand { get; }
+
+ public int AccountListRowHeight => // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+Device.RuntimePlatform == Device.Android ? 74 : 70;
+
+ public bool LongPressAccountEnabled { get; set; } = true;
+
+ public Action AfterHide { get; set; }
+
+ public async Task ToggleVisibilityAsync()
+ {
+ if (IsVisible)
+ {
+ await HideAsync();
+ }
+ else
+ {
+ await ShowAsync();
+ }
+ }
+
+ public async Task ShowAsync()
+ {
+ if (ViewModel == null)
+ {
+ return;
+ }
+
+ await ViewModel.RefreshAccountViewsAsync();
+
+ await Device.InvokeOnMainThreadAsync(async () =>
+ {
+ // start listView in default (off-screen) position
+ await _accountListContainer.TranslateTo(0, _accountListContainer.Height * -1, 0);
+
+ // re-measure in case accounts have been removed without changing screens
+ if (ViewModel.AccountViews != null)
+ {
+ _accountListView.HeightRequest = AccountListRowHeight * ViewModel.AccountViews.Count;
+ }
+
+ // set overlay opacity to zero before making visible and start fade-in
+ Opacity = 0;
+ IsVisible = true;
+ this.FadeTo(1, 100);
+
+ if (Device.RuntimePlatform == Device.Android && MainFab != null)
+ {
+ // start fab fade-out
+ MainFab.FadeTo(0, 200);
+ }
+
+ // slide account list into view
+ await _accountListContainer.TranslateTo(0, 0, 200, Easing.SinOut);
+ });
+ }
+
+ public async Task HideAsync()
+ {
+ if (!IsVisible)
+ {
+ // already hidden, don't animate again
+ return;
+ }
+ // Not all animations are awaited. This is intentional to allow multiple simultaneous animations.
+ await Device.InvokeOnMainThreadAsync(async () =>
+ {
+ // start overlay fade-out
+ this.FadeTo(0, 200);
+
+ if (Device.RuntimePlatform == Device.Android && MainFab != null)
+ {
+ // start fab fade-in
+ MainFab.FadeTo(1, 200);
+ }
+
+ // slide account list out of view
+ await _accountListContainer.TranslateTo(0, _accountListContainer.Height * -1, 200, Easing.SinIn);
+
+ // remove overlay
+ IsVisible = false;
+
+ AfterHide?.Invoke();
+ });
+ }
+
+ private async void FreeSpaceOverlay_Tapped(object sender, EventArgs e)
+ {
+ try
+ {
+ await HideAsync();
+ }
+ catch (Exception ex)
+ {
+ _logger.Value.Exception(ex);
+ }
+ }
+
+ private async Task SelectAccountAsync(AccountViewCellViewModel item)
+ {
+ try
+ {
+ await Task.Delay(100);
+ await HideAsync();
+
+ ViewModel?.SelectAccountCommand?.Execute(item);
+ }
+ catch (Exception ex)
+ {
+ _logger.Value.Exception(ex);
+ }
+ }
+
+ private async Task LongPressAccountAsync(AccountViewCellViewModel item)
+ {
+ if (!LongPressAccountEnabled || !item.IsAccount)
+ {
+ return;
+ }
+ try
+ {
+ await Task.Delay(100);
+ await HideAsync();
+
+ ViewModel?.LongPressAccountCommand?.Execute(
+ new Tuple(MainPage, item));
+ }
+ catch (Exception ex)
+ {
+ _logger.Value.Exception(ex);
+ }
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs b/src/Maui/Bitwarden/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs
new file mode 100644
index 000000000..272201818
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/AccountSwitchingOverlay/AccountSwitchingOverlayViewModel.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using Bit.App.Utilities;
+using Bit.Core.Abstractions;
+using Bit.Core.Models.View;
+using Bit.Core.Utilities;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class AccountSwitchingOverlayViewModel : ExtendedViewModel
+ {
+ private readonly IStateService _stateService;
+ private readonly IMessagingService _messagingService;
+
+ public AccountSwitchingOverlayViewModel(IStateService stateService,
+ IMessagingService messagingService,
+ ILogger logger)
+ {
+ _stateService = stateService;
+ _messagingService = messagingService;
+
+ SelectAccountCommand = new AsyncCommand(SelectAccountAsync,
+ onException: ex => logger.Exception(ex),
+ allowsMultipleExecutions: false);
+
+ LongPressAccountCommand = new AsyncCommand>(LongPressAccountAsync,
+ onException: ex => logger.Exception(ex),
+ allowsMultipleExecutions: false);
+ }
+
+ // this needs to be a new list every time for the binding to get updated,
+ // XF doesn't currentlyl provide a direct way to update on same instance
+ // https://github.com/xamarin/Xamarin.Forms/issues/1950
+ public List AccountViews => _stateService?.AccountViews is null ? null : new List(_stateService.AccountViews);
+
+ public bool AllowActiveAccountSelection { get; set; }
+
+ public bool AllowAddAccountRow { get; set; }
+
+ public ICommand SelectAccountCommand { get; }
+
+ public ICommand LongPressAccountCommand { get; }
+
+ public bool FromIOSExtension { get; set; }
+
+ private async Task SelectAccountAsync(AccountViewCellViewModel item)
+ {
+ if (!item.AccountView.IsAccount)
+ {
+ _messagingService.Send(AccountsManagerMessageCommands.ADD_ACCOUNT);
+ return;
+ }
+
+ if (!item.AccountView.IsActive)
+ {
+ await _stateService.SetActiveUserAsync(item.AccountView.UserId);
+ _messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
+ if (FromIOSExtension)
+ {
+ await _stateService.SaveExtensionActiveUserIdToStorageAsync(item.AccountView.UserId);
+ }
+ }
+ else if (AllowActiveAccountSelection)
+ {
+ _messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
+ }
+ }
+
+ private async Task LongPressAccountAsync(Tuple item)
+ {
+ var (page, account) = item;
+ if (account.AccountView.IsAccount)
+ {
+ await AppHelpers.AccountListOptions(page, account);
+ }
+ }
+
+ public async Task RefreshAccountViewsAsync()
+ {
+ await _stateService.RefreshAccountViewsAsync(AllowAddAccountRow);
+
+ Device.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(AccountViews)));
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/AccountViewCell/AccountViewCell.xaml b/src/Maui/Bitwarden/Controls/AccountViewCell/AccountViewCell.xaml
new file mode 100644
index 000000000..9d270b67a
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/AccountViewCell/AccountViewCell.xaml
@@ -0,0 +1,162 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Maui/Bitwarden/Controls/AccountViewCell/AccountViewCell.xaml.cs b/src/Maui/Bitwarden/Controls/AccountViewCell/AccountViewCell.xaml.cs
new file mode 100644
index 000000000..65c1b9008
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/AccountViewCell/AccountViewCell.xaml.cs
@@ -0,0 +1,55 @@
+using System.Windows.Input;
+using Bit.Core.Models.View;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public partial class AccountViewCell : ViewCell
+ {
+ public static readonly BindableProperty AccountProperty = BindableProperty.Create(
+ nameof(Account), typeof(AccountView), typeof(AccountViewCell));
+
+ public static readonly BindableProperty SelectAccountCommandProperty = BindableProperty.Create(
+ nameof(SelectAccountCommand), typeof(ICommand), typeof(AccountViewCell));
+
+ public static readonly BindableProperty LongPressAccountCommandProperty = BindableProperty.Create(
+ nameof(LongPressAccountCommand), typeof(ICommand), typeof(AccountViewCell));
+
+ public AccountViewCell()
+ {
+ InitializeComponent();
+ }
+
+ public AccountView Account
+ {
+ get => GetValue(AccountProperty) as AccountView;
+ set => SetValue(AccountProperty, value);
+ }
+
+ public ICommand SelectAccountCommand
+ {
+ get => GetValue(SelectAccountCommandProperty) as ICommand;
+ set => SetValue(SelectAccountCommandProperty, value);
+ }
+
+ public ICommand LongPressAccountCommand
+ {
+ get => GetValue(LongPressAccountCommandProperty) as ICommand;
+ set => SetValue(LongPressAccountCommandProperty, value);
+ }
+
+ protected override void OnPropertyChanged(string propertyName = null)
+ {
+ base.OnPropertyChanged(propertyName);
+ if (propertyName == AccountProperty.PropertyName)
+ {
+ if (Account == null)
+ {
+ return;
+ }
+ BindingContext = new AccountViewCellViewModel(Account);
+ }
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/AccountViewCell/AccountViewCellViewModel.cs b/src/Maui/Bitwarden/Controls/AccountViewCell/AccountViewCellViewModel.cs
new file mode 100644
index 000000000..45e2f2455
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/AccountViewCell/AccountViewCellViewModel.cs
@@ -0,0 +1,94 @@
+using Bit.Core;
+using Bit.Core.Enums;
+using Bit.Core.Models.View;
+using Bit.Core.Utilities;
+
+namespace Bit.App.Controls
+{
+ public class AccountViewCellViewModel : ExtendedViewModel
+ {
+ private AccountView _accountView;
+ private AvatarImageSource _avatar;
+
+ public AccountViewCellViewModel(AccountView accountView)
+ {
+ AccountView = accountView;
+ AvatarImageSource = ServiceContainer.Resolve("avatarImageSourcePool")
+ ?.GetOrCreateAvatar(AccountView.UserId, AccountView.Name, AccountView.Email, AccountView.AvatarColor);
+ }
+
+ public AccountView AccountView
+ {
+ get => _accountView;
+ set => SetProperty(ref _accountView, value);
+ }
+
+ public AvatarImageSource AvatarImageSource
+ {
+ get => _avatar;
+ set => SetProperty(ref _avatar, value);
+ }
+
+ public bool IsAccount
+ {
+ get => AccountView.IsAccount;
+ }
+
+ public bool ShowHostname
+ {
+ get => !string.IsNullOrWhiteSpace(AccountView.Hostname) && AccountView.Hostname != "vault.bitwarden.com";
+ }
+
+ public bool IsActive
+ {
+ get => AccountView.IsActive;
+ }
+
+ public bool IsUnlocked
+ {
+ get => AccountView.AuthStatus == AuthenticationStatus.Unlocked;
+ }
+
+ public bool IsUnlockedAndNotActive
+ {
+ get => IsUnlocked && !IsActive;
+ }
+
+ public bool IsLocked
+ {
+ get => AccountView.AuthStatus == AuthenticationStatus.Locked;
+ }
+
+ public bool IsLockedAndNotActive
+ {
+ get => IsLocked && !IsActive;
+ }
+
+ public bool IsLoggedOut
+ {
+ get => AccountView.AuthStatus == AuthenticationStatus.LoggedOut;
+ }
+
+ public bool IsLoggedOutAndNotActive
+ {
+ get => IsLoggedOut && !IsActive;
+ }
+
+ public string AuthStatusIconActive
+ {
+ get => BitwardenIcons.CheckCircle;
+ }
+
+ public string AuthStatusIconNotActive
+ {
+ get
+ {
+ if (IsUnlocked)
+ {
+ return BitwardenIcons.Unlock;
+ }
+ return BitwardenIcons.Lock;
+ }
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml b/src/Maui/Bitwarden/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml
new file mode 100644
index 000000000..83ac6d937
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Maui/Bitwarden/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs b/src/Maui/Bitwarden/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs
new file mode 100644
index 000000000..308d7f84a
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/AuthenticatorViewCell/AuthenticatorViewCell.xaml.cs
@@ -0,0 +1,68 @@
+using System;
+using Bit.App.Pages;
+using Bit.App.Utilities;
+using Bit.Core.Models.View;
+using Bit.Core.Utilities;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public partial class AuthenticatorViewCell : ExtendedGrid
+ {
+ public static readonly BindableProperty CipherProperty = BindableProperty.Create(
+ nameof(Cipher), typeof(CipherView), typeof(AuthenticatorViewCell), default(CipherView), BindingMode.TwoWay);
+
+ public static readonly BindableProperty WebsiteIconsEnabledProperty = BindableProperty.Create(
+ nameof(WebsiteIconsEnabled), typeof(bool?), typeof(AuthenticatorViewCell));
+
+ public static readonly BindableProperty TotpSecProperty = BindableProperty.Create(
+ nameof(TotpSec), typeof(long), typeof(AuthenticatorViewCell));
+
+ public AuthenticatorViewCell()
+ {
+ InitializeComponent();
+ }
+
+ public Command CopyCommand { get; set; }
+
+ public CipherView Cipher
+ {
+ get => GetValue(CipherProperty) as CipherView;
+ set => SetValue(CipherProperty, value);
+ }
+
+ public bool? WebsiteIconsEnabled
+ {
+ get => (bool)GetValue(WebsiteIconsEnabledProperty);
+ set => SetValue(WebsiteIconsEnabledProperty, value);
+ }
+
+ public long TotpSec
+ {
+ get => (long)GetValue(TotpSecProperty);
+ set => SetValue(TotpSecProperty, value);
+ }
+
+ public bool ShowIconImage
+ {
+ get => WebsiteIconsEnabled ?? false
+ && !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
+ && IconImageSource != null;
+ }
+
+ private string _iconImageSource = string.Empty;
+ public string IconImageSource
+ {
+ get
+ {
+ if (_iconImageSource == string.Empty) // default value since icon source can return null
+ {
+ _iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
+ }
+ return _iconImageSource;
+ }
+
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/AvatarImageSource.cs b/src/Maui/Bitwarden/Controls/AvatarImageSource.cs
new file mode 100644
index 000000000..2c0b54599
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/AvatarImageSource.cs
@@ -0,0 +1,181 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Bit.Core.Utilities;
+using SkiaSharp;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class AvatarImageSource : StreamImageSource
+ {
+ private readonly string _text;
+ private readonly string _id;
+ private readonly string _color;
+
+ public override bool Equals(object obj)
+ {
+ if (obj is null)
+ {
+ return false;
+ }
+
+ if (obj is AvatarImageSource avatar)
+ {
+ return avatar._id == _id && avatar._text == _text && avatar._color == _color;
+ }
+
+ return base.Equals(obj);
+ }
+
+ public override int GetHashCode() => _id?.GetHashCode() ?? _text?.GetHashCode() ?? -1;
+
+ public AvatarImageSource(string userId = null, string name = null, string email = null, string color = null)
+ {
+ _id = userId;
+ _text = name;
+ if (string.IsNullOrWhiteSpace(_text))
+ {
+ _text = email;
+ }
+ _color = color;
+ }
+
+ public override Func> Stream => GetStreamAsync;
+
+ private Task GetStreamAsync(CancellationToken userToken = new CancellationToken())
+ {
+ OnLoadingStarted();
+ userToken.Register(CancellationTokenSource.Cancel);
+ var result = Draw();
+ OnLoadingCompleted(CancellationTokenSource.IsCancellationRequested);
+ return Task.FromResult(result);
+ }
+
+ private Stream Draw()
+ {
+ string chars;
+ string upperCaseText = null;
+
+ if (string.IsNullOrEmpty(_text))
+ {
+ chars = "..";
+ }
+ else if (_text?.Length > 1)
+ {
+ upperCaseText = _text.ToUpper();
+ chars = GetFirstLetters(upperCaseText, 2);
+ }
+ else
+ {
+ chars = upperCaseText = _text.ToUpper();
+ }
+
+ var bgColor = _color ?? CoreHelpers.StringToColor(_id ?? upperCaseText, "#33ffffff");
+ var textColor = CoreHelpers.TextColorFromBgColor(bgColor);
+ var size = 50;
+
+ using (var bitmap = new SKBitmap(size * 2,
+ size * 2,
+ SKImageInfo.PlatformColorType,
+ SKAlphaType.Premul))
+ {
+ using (var canvas = new SKCanvas(bitmap))
+ {
+ canvas.Clear(SKColors.Transparent);
+ using (var paint = new SKPaint
+ {
+ IsAntialias = true,
+ Style = SKPaintStyle.Fill,
+ StrokeJoin = SKStrokeJoin.Miter,
+ Color = SKColor.Parse(bgColor)
+ })
+ {
+ var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2;
+ var midY = canvas.LocalClipBounds.Size.ToSizeI().Height / 2;
+ var radius = midX - midX / 5;
+
+ using (var circlePaint = new SKPaint
+ {
+ IsAntialias = true,
+ Style = SKPaintStyle.Fill,
+ StrokeJoin = SKStrokeJoin.Miter,
+ Color = SKColor.Parse(bgColor)
+ })
+ {
+ canvas.DrawCircle(midX, midY, radius, circlePaint);
+
+ var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal);
+ var textSize = midX / 1.3f;
+ using (var textPaint = new SKPaint
+ {
+ IsAntialias = true,
+ Style = SKPaintStyle.Fill,
+ Color = SKColor.Parse(textColor),
+ TextSize = textSize,
+ TextAlign = SKTextAlign.Center,
+ Typeface = typeface
+ })
+ {
+ var rect = new SKRect();
+ textPaint.MeasureText(chars, ref rect);
+ canvas.DrawText(chars, midX, midY + rect.Height / 2, textPaint);
+
+ using (var img = SKImage.FromBitmap(bitmap))
+ {
+ var data = img.Encode(SKEncodedImageFormat.Png, 100);
+ return data?.AsStream(true);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private string GetFirstLetters(string data, int charCount)
+ {
+ var sanitizedData = data.Trim();
+ var parts = sanitizedData.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
+
+ if (parts.Length > 1 && charCount <= 2)
+ {
+ var text = string.Empty;
+ for (var i = 0; i < charCount; i++)
+ {
+ text += parts[i][0];
+ }
+ return text;
+ }
+ if (sanitizedData.Length > 2)
+ {
+ return sanitizedData.Substring(0, 2);
+ }
+ return sanitizedData;
+ }
+
+ private Color StringToColor(string str)
+ {
+ if (str == null)
+ {
+ return Color.FromArgb("#33ffffff");
+ }
+ var hash = 0;
+ for (var i = 0; i < str.Length; i++)
+ {
+ hash = str[i] + ((hash << 5) - hash);
+ }
+ var color = "#FF";
+ for (var i = 0; i < 3; i++)
+ {
+ var value = (hash >> (i * 8)) & 0xff;
+ var base16 = "00" + Convert.ToString(value, 16);
+ color += base16.Substring(base16.Length - 2);
+ }
+ return Color.FromArgb(color);
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/AvatarImageSourcePool.cs b/src/Maui/Bitwarden/Controls/AvatarImageSourcePool.cs
new file mode 100644
index 000000000..56fcbe886
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/AvatarImageSourcePool.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Concurrent;
+
+namespace Bit.App.Controls
+{
+ public interface IAvatarImageSourcePool
+ {
+ AvatarImageSource GetOrCreateAvatar(string userId, string name, string email, string color);
+ }
+
+ public class AvatarImageSourcePool : IAvatarImageSourcePool
+ {
+ private readonly ConcurrentDictionary _cache = new ConcurrentDictionary();
+
+ public AvatarImageSource GetOrCreateAvatar(string userId, string name, string email, string color)
+ {
+ var key = $"{userId}{name}{email}{color}";
+ if (!_cache.TryGetValue(key, out var avatar))
+ {
+ avatar = new AvatarImageSource(userId, name, email, color);
+ if (!_cache.TryAdd(key, avatar)
+ &&
+ !_cache.TryGetValue(key, out avatar)) // If add fails another thread created the avatar in between the first try get and the try add.
+ {
+ // if add and get after fails, then something wrong is going on with this method.
+ throw new InvalidOperationException("Something is wrong creating the avatar image");
+ }
+ }
+ return avatar;
+ }
+ }
+}
+
diff --git a/src/Maui/Bitwarden/Controls/CipherViewCell/CipherViewCell.xaml b/src/Maui/Bitwarden/Controls/CipherViewCell/CipherViewCell.xaml
new file mode 100644
index 000000000..ff5fc17eb
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/CipherViewCell/CipherViewCell.xaml
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Maui/Bitwarden/Controls/CipherViewCell/CipherViewCell.xaml.cs b/src/Maui/Bitwarden/Controls/CipherViewCell/CipherViewCell.xaml.cs
new file mode 100644
index 000000000..1fc0e6797
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/CipherViewCell/CipherViewCell.xaml.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Windows.Input;
+using Bit.App.Abstractions;
+using Bit.Core.Models.View;
+using Bit.Core.Utilities;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public partial class CipherViewCell : ExtendedGrid
+ {
+ private const int ICON_COLUMN_DEFAULT_WIDTH = 40;
+ private const int ICON_IMAGE_DEFAULT_WIDTH = 22;
+
+ public static readonly BindableProperty CipherProperty = BindableProperty.Create(
+ nameof(Cipher), typeof(CipherView), typeof(CipherViewCell), default(CipherView), BindingMode.OneWay);
+
+ public static readonly BindableProperty WebsiteIconsEnabledProperty = BindableProperty.Create(
+ nameof(WebsiteIconsEnabled), typeof(bool?), typeof(CipherViewCell));
+
+ public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create(
+ nameof(ButtonCommand), typeof(ICommand), typeof(CipherViewCell));
+
+ public CipherViewCell()
+ {
+ InitializeComponent();
+
+ var fontScale = ServiceContainer.Resolve("deviceActionService").GetSystemFontSizeScale();
+ _iconColumn.Width = new GridLength(ICON_COLUMN_DEFAULT_WIDTH * fontScale, GridUnitType.Absolute);
+ _iconImage.WidthRequest = ICON_IMAGE_DEFAULT_WIDTH * fontScale;
+ _iconImage.HeightRequest = ICON_IMAGE_DEFAULT_WIDTH * fontScale;
+ }
+
+ public bool? WebsiteIconsEnabled
+ {
+ get => (bool)GetValue(WebsiteIconsEnabledProperty);
+ set => SetValue(WebsiteIconsEnabledProperty, value);
+ }
+
+ public CipherView Cipher
+ {
+ get => GetValue(CipherProperty) as CipherView;
+ set => SetValue(CipherProperty, value);
+ }
+
+ public ICommand ButtonCommand
+ {
+ get => GetValue(ButtonCommandProperty) as ICommand;
+ set => SetValue(ButtonCommandProperty, value);
+ }
+
+ protected override void OnPropertyChanged(string propertyName = null)
+ {
+ base.OnPropertyChanged(propertyName);
+ if (propertyName == CipherProperty.PropertyName)
+ {
+ if (Cipher == null)
+ {
+ return;
+ }
+ BindingContext = new CipherViewCellViewModel(Cipher, WebsiteIconsEnabled ?? false);
+ }
+ else if (propertyName == WebsiteIconsEnabledProperty.PropertyName)
+ {
+ if (Cipher == null)
+ {
+ return;
+ }
+ ((CipherViewCellViewModel)BindingContext).WebsiteIconsEnabled = WebsiteIconsEnabled ?? false;
+ }
+ }
+
+ private void MoreButton_Clicked(object sender, EventArgs e)
+ {
+ var cipher = ((sender as MiButton)?.BindingContext as CipherViewCellViewModel)?.Cipher;
+ if (cipher != null)
+ {
+ ButtonCommand?.Execute(cipher);
+ }
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/CipherViewCell/CipherViewCellViewModel.cs b/src/Maui/Bitwarden/Controls/CipherViewCell/CipherViewCellViewModel.cs
new file mode 100644
index 000000000..b5150b003
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/CipherViewCell/CipherViewCellViewModel.cs
@@ -0,0 +1,51 @@
+using Bit.App.Utilities;
+using Bit.Core.Models.View;
+using Bit.Core.Utilities;
+
+namespace Bit.App.Controls
+{
+ public class CipherViewCellViewModel : ExtendedViewModel
+ {
+ private CipherView _cipher;
+ private bool _websiteIconsEnabled;
+ private string _iconImageSource = string.Empty;
+
+ public CipherViewCellViewModel(CipherView cipherView, bool websiteIconsEnabled)
+ {
+ Cipher = cipherView;
+ WebsiteIconsEnabled = websiteIconsEnabled;
+ }
+
+ public CipherView Cipher
+ {
+ get => _cipher;
+ set => SetProperty(ref _cipher, value);
+ }
+
+ public bool WebsiteIconsEnabled
+ {
+ get => _websiteIconsEnabled;
+ set => SetProperty(ref _websiteIconsEnabled, value);
+ }
+
+ public bool ShowIconImage
+ {
+ get => WebsiteIconsEnabled
+ && !string.IsNullOrWhiteSpace(Cipher.LaunchUri)
+ && IconImageSource != null;
+ }
+
+ public string IconImageSource
+ {
+ get
+ {
+ if (_iconImageSource == string.Empty) // default value since icon source can return null
+ {
+ _iconImageSource = IconImageHelper.GetIconImage(Cipher);
+ }
+ return _iconImageSource;
+ }
+
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/CircularProgressbarView.cs b/src/Maui/Bitwarden/Controls/CircularProgressbarView.cs
new file mode 100644
index 000000000..f606e51d2
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/CircularProgressbarView.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Runtime.CompilerServices;
+using SkiaSharp;
+using Microsoft.Maui.Graphics;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+using SkiaSharp.Views.Maui.Controls;
+using SkiaSharp.Views.Maui;
+
+namespace Bit.App.Controls
+{
+ public class CircularProgressbarView : SKCanvasView
+ {
+ private Circle _circle;
+
+ public static readonly BindableProperty ProgressProperty = BindableProperty.Create(
+ nameof(Progress), typeof(double), typeof(CircularProgressbarView), propertyChanged: OnProgressChanged);
+
+ public static readonly BindableProperty RadiusProperty = BindableProperty.Create(
+ nameof(Radius), typeof(float), typeof(CircularProgressbarView), 15f);
+
+ public static readonly BindableProperty StrokeWidthProperty = BindableProperty.Create(
+ nameof(StrokeWidth), typeof(float), typeof(CircularProgressbarView), 3f);
+
+ public static readonly BindableProperty ProgressColorProperty = BindableProperty.Create(
+ nameof(ProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.FromArgb("175DDC"));
+
+ public static readonly BindableProperty EndingProgressColorProperty = BindableProperty.Create(
+ nameof(EndingProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.FromArgb("dd4b39"));
+
+ public static readonly BindableProperty BackgroundProgressColorProperty = BindableProperty.Create(
+ nameof(BackgroundProgressColor), typeof(Color), typeof(CircularProgressbarView), Colors.White);
+
+ public double Progress
+ {
+ get { return (double)GetValue(ProgressProperty); }
+ set { SetValue(ProgressProperty, value); }
+ }
+
+ public float Radius
+ {
+ get => (float)GetValue(RadiusProperty);
+ set => SetValue(RadiusProperty, value);
+ }
+ public float StrokeWidth
+ {
+ get => (float)GetValue(StrokeWidthProperty);
+ set => SetValue(StrokeWidthProperty, value);
+ }
+
+ public Color ProgressColor
+ {
+ get => (Color)GetValue(ProgressColorProperty);
+ set => SetValue(ProgressColorProperty, value);
+ }
+
+ public Color EndingProgressColor
+ {
+ get => (Color)GetValue(EndingProgressColorProperty);
+ set => SetValue(EndingProgressColorProperty, value);
+ }
+
+ public Color BackgroundProgressColor
+ {
+ get => (Color)GetValue(BackgroundProgressColorProperty);
+ set => SetValue(BackgroundProgressColorProperty, value);
+ }
+
+ private static void OnProgressChanged(BindableObject bindable, object oldvalue, object newvalue)
+ {
+ var context = bindable as CircularProgressbarView;
+ context.InvalidateSurface();
+ }
+
+ protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ base.OnPropertyChanged(propertyName);
+ if (propertyName == nameof(Progress))
+ {
+ _circle = new Circle(Radius * (float)DeviceDisplay.MainDisplayInfo.Density, (info) => new SKPoint((float)info.Width / 2, (float)info.Height / 2));
+ }
+ }
+
+ protected override void OnPaintSurface(SKPaintSurfaceEventArgs e)
+ {
+ base.OnPaintSurface(e);
+ if (_circle != null)
+ {
+ _circle.CalculateCenter(e.Info);
+ e.Surface.Canvas.Clear();
+ DrawCircle(e.Surface.Canvas, _circle, StrokeWidth * (float)DeviceDisplay.MainDisplayInfo.Density, BackgroundProgressColor.ToSKColor());
+ DrawArc(e.Surface.Canvas, _circle, () => (float)Progress, StrokeWidth * (float)DeviceDisplay.MainDisplayInfo.Density, ProgressColor.ToSKColor(), EndingProgressColor.ToSKColor());
+ }
+ }
+
+ private void DrawCircle(SKCanvas canvas, Circle circle, float strokewidth, SKColor color)
+ {
+ canvas.DrawCircle(circle.Center, circle.Redius,
+ new SKPaint()
+ {
+ StrokeWidth = strokewidth,
+ Color = color,
+ IsStroke = true,
+ IsAntialias = true
+ });
+ }
+
+ private void DrawArc(SKCanvas canvas, Circle circle, Func progress, float strokewidth, SKColor color, SKColor progressEndColor)
+ {
+ var progressValue = progress();
+ var angle = progressValue * 3.6f;
+ canvas.DrawArc(circle.Rect, 270, angle, false,
+ new SKPaint()
+ {
+ StrokeWidth = strokewidth,
+ Color = progressValue < 20f ? progressEndColor : color,
+ IsStroke = true,
+ IsAntialias = true
+ });
+ }
+ }
+
+ public class Circle
+ {
+ private readonly Func _centerFunc;
+
+ public Circle(float redius, Func centerFunc)
+ {
+ _centerFunc = centerFunc;
+ Redius = redius;
+ }
+ public SKPoint Center { get; set; }
+ public float Redius { get; set; }
+ public SKRect Rect => new SKRect(Center.X - Redius, Center.Y - Redius, Center.X + Redius, Center.Y + Redius);
+
+ public void CalculateCenter(SKImageInfo argsInfo)
+ {
+ Center = _centerFunc(argsInfo);
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/CustomLabel.cs b/src/Maui/Bitwarden/Controls/CustomLabel.cs
new file mode 100644
index 000000000..a4e62949b
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/CustomLabel.cs
@@ -0,0 +1,14 @@
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class CustomLabel : Label
+ {
+ public CustomLabel()
+ {
+ }
+
+ public int? FontWeight { get; set; }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/DateTime/DateTimePicker.xaml b/src/Maui/Bitwarden/Controls/DateTime/DateTimePicker.xaml
new file mode 100644
index 000000000..e68d97163
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/DateTime/DateTimePicker.xaml
@@ -0,0 +1,20 @@
+
+
+
+
+
diff --git a/src/Maui/Bitwarden/Controls/DateTime/DateTimePicker.xaml.cs b/src/Maui/Bitwarden/Controls/DateTime/DateTimePicker.xaml.cs
new file mode 100644
index 000000000..4011b6d3b
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/DateTime/DateTimePicker.xaml.cs
@@ -0,0 +1,40 @@
+using System.Runtime.CompilerServices;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+using CommunityToolkit.Maui.Converters;
+using CommunityToolkit.Maui.ImageSources;
+using CommunityToolkit.Maui;
+using CommunityToolkit.Maui.Core;
+using CommunityToolkit.Maui.Layouts;
+using CommunityToolkit.Maui.Views;
+
+namespace Bit.App.Controls
+{
+ public partial class DateTimePicker : Grid
+ {
+ public DateTimePicker()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ base.OnPropertyChanged(propertyName);
+
+ if (propertyName == nameof(BindingContext)
+ &&
+ BindingContext is DateTimeViewModel dateTimeViewModel)
+ {
+ AutomationProperties.SetName(_datePicker, dateTimeViewModel.DateName);
+ AutomationProperties.SetName(_timePicker, dateTimeViewModel.TimeName);
+
+ _datePicker.PlaceHolder = dateTimeViewModel.DatePlaceholder;
+ _timePicker.PlaceHolder = dateTimeViewModel.TimePlaceholder;
+ }
+ }
+ }
+
+ public class LazyDateTimePicker : LazyView
+ {
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/DateTime/DateTimeViewModel.cs b/src/Maui/Bitwarden/Controls/DateTime/DateTimeViewModel.cs
new file mode 100644
index 000000000..ac940a773
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/DateTime/DateTimeViewModel.cs
@@ -0,0 +1,70 @@
+using System;
+using Bit.Core.Utilities;
+
+namespace Bit.App.Controls
+{
+ public class DateTimeViewModel : ExtendedViewModel
+ {
+ DateTime? _date;
+ TimeSpan? _time;
+
+ public DateTimeViewModel(string dateName, string timeName)
+ {
+ DateName = dateName;
+ TimeName = timeName;
+ }
+
+ public Action OnDateChanged { get; set; }
+ public Action OnTimeChanged { get; set; }
+
+ public DateTime? Date
+ {
+ get => _date;
+ set
+ {
+ if (SetProperty(ref _date, value))
+ {
+ OnDateChanged?.Invoke(value);
+ }
+ }
+ }
+ public TimeSpan? Time
+ {
+ get => _time;
+ set
+ {
+ if (SetProperty(ref _time, value))
+ {
+ OnTimeChanged?.Invoke(value);
+ }
+ }
+ }
+
+ public string DateName { get; }
+ public string TimeName { get; }
+
+ public string DatePlaceholder { get; set; }
+ public string TimePlaceholder { get; set; }
+
+ public DateTime? DateTime
+ {
+ get
+ {
+ if (Date.HasValue)
+ {
+ if (Time.HasValue)
+ {
+ return Date.Value.Add(Time.Value);
+ }
+ return Date;
+ }
+ return null;
+ }
+ set
+ {
+ Date = value?.Date;
+ Time = value?.Date.TimeOfDay;
+ }
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/ExtendedCollectionView.cs b/src/Maui/Bitwarden/Controls/ExtendedCollectionView.cs
new file mode 100644
index 000000000..b6e75c4cb
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/ExtendedCollectionView.cs
@@ -0,0 +1,21 @@
+using System.Linq;
+using CommunityToolkit.Maui.Converters;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class ExtendedCollectionView : CollectionView
+ {
+ public string ExtraDataForLogging { get; set; }
+ }
+
+ public class SelectionChangedEventArgsConverter : BaseNullableConverterOneWay
+ {
+ public override object? ConvertFrom(SelectionChangedEventArgs? value)
+ {
+ return value?.CurrentSelection.FirstOrDefault();
+ }
+ }
+
+}
diff --git a/src/Maui/Bitwarden/Controls/ExtendedDatePicker.cs b/src/Maui/Bitwarden/Controls/ExtendedDatePicker.cs
new file mode 100644
index 000000000..ea3e881bb
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/ExtendedDatePicker.cs
@@ -0,0 +1,87 @@
+using System;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class ExtendedDatePicker : DatePicker
+ {
+ private string _format;
+
+ public static readonly BindableProperty PlaceHolderProperty = BindableProperty.Create(
+ nameof(PlaceHolder), typeof(string), typeof(ExtendedDatePicker));
+
+ public string PlaceHolder
+ {
+ get { return (string)GetValue(PlaceHolderProperty); }
+ set { SetValue(PlaceHolderProperty, value); }
+ }
+
+ public static readonly BindableProperty NullableDateProperty = BindableProperty.Create(
+ nameof(NullableDate), typeof(DateTime?), typeof(ExtendedDatePicker));
+
+ public DateTime? NullableDate
+ {
+ get { return (DateTime?)GetValue(NullableDateProperty); }
+ set
+ {
+ SetValue(NullableDateProperty, value);
+ UpdateDate();
+ }
+ }
+
+ private void UpdateDate()
+ {
+ if (NullableDate.HasValue)
+ {
+ if (_format != null)
+ {
+ Format = _format;
+ }
+ }
+ else
+ {
+ Format = PlaceHolder;
+ }
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+ if (BindingContext != null)
+ {
+ _format = Format;
+ UpdateDate();
+ }
+ }
+
+ protected override void OnPropertyChanged(string propertyName = null)
+ {
+ base.OnPropertyChanged(propertyName);
+
+ if (propertyName == DateProperty.PropertyName || (propertyName == IsFocusedProperty.PropertyName &&
+ !IsFocused && (Date.ToString("d") ==
+ DateTime.Now.ToString("d"))))
+ {
+ NullableDate = Date;
+ UpdateDate();
+ }
+
+ if (propertyName == NullableDateProperty.PropertyName)
+ {
+ if (NullableDate.HasValue)
+ {
+ Date = NullableDate.Value;
+ if (Date.ToString(_format) == DateTime.Now.ToString(_format))
+ {
+ UpdateDate();
+ }
+ }
+ else
+ {
+ UpdateDate();
+ }
+ }
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/ExtendedGrid.cs b/src/Maui/Bitwarden/Controls/ExtendedGrid.cs
new file mode 100644
index 000000000..922973456
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/ExtendedGrid.cs
@@ -0,0 +1,9 @@
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class ExtendedGrid : Grid
+ {
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/ExtendedSearchBar.cs b/src/Maui/Bitwarden/Controls/ExtendedSearchBar.cs
new file mode 100644
index 000000000..db7bb3fd5
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/ExtendedSearchBar.cs
@@ -0,0 +1,22 @@
+using Bit.App.Utilities;
+using Microsoft.Maui.Graphics;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class ExtendedSearchBar : SearchBar
+ {
+ public ExtendedSearchBar()
+ {
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+ if (Device.RuntimePlatform == Device.iOS)
+ {
+ if (ThemeManager.UsingLightTheme)
+ {
+ TextColor = Color.Black;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/ExtendedSlider.cs b/src/Maui/Bitwarden/Controls/ExtendedSlider.cs
new file mode 100644
index 000000000..0fe0a833e
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/ExtendedSlider.cs
@@ -0,0 +1,17 @@
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class ExtendedSlider : Slider
+ {
+ public static readonly BindableProperty ThumbBorderColorProperty = BindableProperty.Create(
+ nameof(ThumbBorderColor), typeof(Color), typeof(ExtendedSlider), Color.FromArgb("b5b5b5"));
+
+ public Color ThumbBorderColor
+ {
+ get => (Color)GetValue(ThumbBorderColorProperty);
+ set => SetValue(ThumbBorderColorProperty, value);
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/ExtendedStackLayout.cs b/src/Maui/Bitwarden/Controls/ExtendedStackLayout.cs
new file mode 100644
index 000000000..5186fbb0a
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/ExtendedStackLayout.cs
@@ -0,0 +1,9 @@
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class ExtendedStackLayout : StackLayout
+ {
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/ExtendedStepper.cs b/src/Maui/Bitwarden/Controls/ExtendedStepper.cs
new file mode 100644
index 000000000..b92f12219
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/ExtendedStepper.cs
@@ -0,0 +1,27 @@
+using Microsoft.Maui.Graphics;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class ExtendedStepper : Stepper
+ {
+ public static readonly BindableProperty StepperBackgroundColorProperty = BindableProperty.Create(
+ nameof(StepperBackgroundColor), typeof(Color), typeof(ExtendedStepper), Colors.White);
+
+ public static readonly BindableProperty StepperForegroundColorProperty = BindableProperty.Create(
+ nameof(StepperForegroundColor), typeof(Color), typeof(ExtendedStepper), Colors.Black);
+
+ public Color StepperBackgroundColor
+ {
+ get => (Color)GetValue(StepperBackgroundColorProperty);
+ set => SetValue(StepperBackgroundColorProperty, value);
+ }
+
+ public Color StepperForegroundColor
+ {
+ get => (Color)GetValue(StepperForegroundColorProperty);
+ set => SetValue(StepperForegroundColorProperty, value);
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/ExtendedTimePicker.cs b/src/Maui/Bitwarden/Controls/ExtendedTimePicker.cs
new file mode 100644
index 000000000..b2985bd1b
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/ExtendedTimePicker.cs
@@ -0,0 +1,87 @@
+using System;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class ExtendedTimePicker : TimePicker
+ {
+ private string _format;
+
+ public static readonly BindableProperty PlaceHolderProperty = BindableProperty.Create(
+ nameof(PlaceHolder), typeof(string), typeof(ExtendedTimePicker));
+
+ public string PlaceHolder
+ {
+ get { return (string)GetValue(PlaceHolderProperty); }
+ set { SetValue(PlaceHolderProperty, value); }
+ }
+
+ public static readonly BindableProperty NullableTimeProperty = BindableProperty.Create(
+ nameof(NullableTime), typeof(TimeSpan?), typeof(ExtendedTimePicker));
+
+ public TimeSpan? NullableTime
+ {
+ get { return (TimeSpan?)GetValue(NullableTimeProperty); }
+ set
+ {
+ SetValue(NullableTimeProperty, value);
+ UpdateTime();
+ }
+ }
+
+ private void UpdateTime()
+ {
+ if (NullableTime.HasValue)
+ {
+ if (_format != null)
+ {
+ Format = _format;
+ }
+ }
+ else
+ {
+ Format = PlaceHolder;
+ }
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+ if (BindingContext != null)
+ {
+ _format = Format;
+ UpdateTime();
+ }
+ }
+
+ protected override void OnPropertyChanged(string propertyName = null)
+ {
+ base.OnPropertyChanged(propertyName);
+
+ if (propertyName == TimeProperty.PropertyName || (propertyName == IsFocusedProperty.PropertyName &&
+ !IsFocused && (Time.ToString("t") ==
+ DateTime.Now.TimeOfDay.ToString("t"))))
+ {
+ NullableTime = Time;
+ UpdateTime();
+ }
+
+ if (propertyName == NullableTimeProperty.PropertyName)
+ {
+ if (NullableTime.HasValue)
+ {
+ Time = NullableTime.Value;
+ if (Time.ToString(_format) == DateTime.Now.TimeOfDay.ToString(_format))
+ {
+ UpdateTime();
+ }
+ }
+ else
+ {
+ UpdateTime();
+ }
+ }
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/ExtendedToolbarItem.cs b/src/Maui/Bitwarden/Controls/ExtendedToolbarItem.cs
new file mode 100644
index 000000000..0a004c749
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/ExtendedToolbarItem.cs
@@ -0,0 +1,30 @@
+using System;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class ExtendedToolbarItem : ToolbarItem
+ {
+ public bool UseOriginalImage { get; set; }
+
+ // HACK: For the issue of correctly updating the avatar toolbar item color on iOS
+ // we need to subscribe to the PropertyChanged event of the ToolbarItem on the CustomNavigationRenderer
+ // The problem is that there are a lot of private places where the navigation renderer disposes objects
+ // that we don't have access to, and that we should in order to properly prevent memory leaks
+ // So as a hack solution we have this OnAppearing/OnDisappearing actions and methods to be called on page lifecycle
+ // to subscribe/unsubscribe indirectly on the CustomNavigationRenderer
+ public Action OnAppearingAction { get; set; }
+ public Action OnDisappearingAction { get; set; }
+
+ public void OnAppearing()
+ {
+ OnAppearingAction?.Invoke();
+ }
+
+ public void OnDisappearing()
+ {
+ OnDisappearingAction?.Invoke();
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/HybridWebView.cs b/src/Maui/Bitwarden/Controls/HybridWebView.cs
new file mode 100644
index 000000000..801d9b2f6
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/HybridWebView.cs
@@ -0,0 +1,35 @@
+using System;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class HybridWebView : View
+ {
+ private Action _func;
+
+ public static readonly BindableProperty UriProperty = BindableProperty.Create(propertyName: nameof(Uri),
+ returnType: typeof(string), declaringType: typeof(HybridWebView), defaultValue: default(string));
+
+ public string Uri
+ {
+ get { return (string)GetValue(UriProperty); }
+ set { SetValue(UriProperty, value); }
+ }
+
+ public void RegisterAction(Action callback)
+ {
+ _func = callback;
+ }
+
+ public void Cleanup()
+ {
+ _func = null;
+ }
+
+ public void InvokeAction(string data)
+ {
+ _func?.Invoke(data);
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/IconButton.cs b/src/Maui/Bitwarden/Controls/IconButton.cs
new file mode 100644
index 000000000..d27d50626
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/IconButton.cs
@@ -0,0 +1,26 @@
+using Bit.App.Effects;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class IconButton : Button
+ {
+ public IconButton()
+ {
+ Padding = 0;
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+ switch (Device.RuntimePlatform)
+ {
+ case Device.iOS:
+ FontFamily = "bwi-font";
+ break;
+ case Device.Android:
+ FontFamily = "bwi-font.ttf#bwi-font";
+ break;
+ }
+
+ Effects.Add(new RemoveFontPaddingEffect());
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/IconLabel.cs b/src/Maui/Bitwarden/Controls/IconLabel.cs
new file mode 100644
index 000000000..6b5e72308
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/IconLabel.cs
@@ -0,0 +1,27 @@
+using Bit.App.Effects;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class IconLabel : Label
+ {
+ public bool ShouldUpdateFontSizeDynamicallyForAccesibility { get; set; }
+
+ public IconLabel()
+ {
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+ switch (Device.RuntimePlatform)
+ {
+ case Device.iOS:
+ FontFamily = "bwi-font";
+ break;
+ case Device.Android:
+ FontFamily = "bwi-font.ttf#bwi-font";
+ break;
+ }
+
+ Effects.Add(new RemoveFontPaddingEffect());
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/IconLabelButton/IconLabelButton.xaml b/src/Maui/Bitwarden/Controls/IconLabelButton/IconLabelButton.xaml
new file mode 100644
index 000000000..bb3ce642a
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/IconLabelButton/IconLabelButton.xaml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Maui/Bitwarden/Controls/IconLabelButton/IconLabelButton.xaml.cs b/src/Maui/Bitwarden/Controls/IconLabelButton/IconLabelButton.xaml.cs
new file mode 100644
index 000000000..1fb63785f
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/IconLabelButton/IconLabelButton.xaml.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using Bit.Core.Models.Domain;
+using Microsoft.Maui.Controls.Xaml;
+using Microsoft.Maui.Graphics;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public partial class IconLabelButton : Frame
+ {
+ public static readonly BindableProperty IconProperty = BindableProperty.Create(
+ nameof(Icon), typeof(string), typeof(IconLabelButton));
+
+ public static readonly BindableProperty LabelProperty = BindableProperty.Create(
+ nameof(Label), typeof(string), typeof(IconLabelButton));
+
+ public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create(
+ nameof(ButtonCommand), typeof(ICommand), typeof(IconLabelButton));
+
+ public static readonly BindableProperty IconLabelColorProperty = BindableProperty.Create(
+ nameof(IconLabelColor), typeof(Color), typeof(IconLabelButton), Colors.White);
+
+ public static readonly BindableProperty IconLabelBackgroundColorProperty = BindableProperty.Create(
+ nameof(IconLabelBackgroundColor), typeof(Color), typeof(IconLabelButton), Colors.White);
+
+ public static readonly BindableProperty IconLabelBorderColorProperty = BindableProperty.Create(
+ nameof(IconLabelBorderColor), typeof(Color), typeof(IconLabelButton), Colors.White);
+
+ public IconLabelButton()
+ {
+ InitializeComponent();
+ }
+
+ public string Icon
+ {
+ get => GetValue(IconProperty) as string;
+ set => SetValue(IconProperty, value);
+ }
+
+ public string Label
+ {
+ get => GetValue(LabelProperty) as string;
+ set => SetValue(LabelProperty, value);
+ }
+
+ public ICommand ButtonCommand
+ {
+ get => GetValue(ButtonCommandProperty) as ICommand;
+ set => SetValue(ButtonCommandProperty, value);
+ }
+
+ public Color IconLabelColor
+ {
+ get { return (Color)GetValue(IconLabelColorProperty); }
+ set { SetValue(IconLabelColorProperty, value); }
+ }
+
+ public Color IconLabelBackgroundColor
+ {
+ get { return (Color)GetValue(IconLabelBackgroundColorProperty); }
+ set { SetValue(IconLabelBackgroundColorProperty, value); }
+ }
+
+ public Color IconLabelBorderColor
+ {
+ get { return (Color)GetValue(IconLabelBorderColorProperty); }
+ set { SetValue(IconLabelBorderColorProperty, value); }
+ }
+ }
+}
+
diff --git a/src/Maui/Bitwarden/Controls/MiButton.cs b/src/Maui/Bitwarden/Controls/MiButton.cs
new file mode 100644
index 000000000..99309351b
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/MiButton.cs
@@ -0,0 +1,23 @@
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class MiButton : Button
+ {
+ public MiButton()
+ {
+ Padding = 0;
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+ switch (Device.RuntimePlatform)
+ {
+ case Device.iOS:
+ FontFamily = "Material Icons";
+ break;
+ case Device.Android:
+ FontFamily = "MaterialIcons_Regular.ttf#Material Icons";
+ break;
+ }
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/MiLabel.cs b/src/Maui/Bitwarden/Controls/MiLabel.cs
new file mode 100644
index 000000000..4d510899f
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/MiLabel.cs
@@ -0,0 +1,22 @@
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class MiLabel : Label
+ {
+ public MiLabel()
+ {
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+ switch (Device.RuntimePlatform)
+ {
+ case Device.iOS:
+ FontFamily = "Material Icons";
+ break;
+ case Device.Android:
+ FontFamily = "MaterialIcons_Regular.ttf#Material Icons";
+ break;
+ }
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/MonoEntry.cs b/src/Maui/Bitwarden/Controls/MonoEntry.cs
new file mode 100644
index 000000000..dfa85d849
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/MonoEntry.cs
@@ -0,0 +1,22 @@
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class MonoEntry : Entry
+ {
+ public MonoEntry()
+ {
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+ switch (Device.RuntimePlatform)
+ {
+ case Device.iOS:
+ FontFamily = "Menlo-Regular";
+ break;
+ case Device.Android:
+ FontFamily = "RobotoMono_Regular.ttf#Roboto Mono";
+ break;
+ }
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/MonoLabel.cs b/src/Maui/Bitwarden/Controls/MonoLabel.cs
new file mode 100644
index 000000000..6baaf399e
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/MonoLabel.cs
@@ -0,0 +1,22 @@
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class MonoLabel : Label
+ {
+ public MonoLabel()
+ {
+ // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
+ switch (Device.RuntimePlatform)
+ {
+ case Device.iOS:
+ FontFamily = "Menlo-Regular";
+ break;
+ case Device.Android:
+ FontFamily = "RobotoMono_Regular.ttf#Roboto Mono";
+ break;
+ }
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/PasswordStrengthProgressBar/IPasswordStrengthable.cs b/src/Maui/Bitwarden/Controls/PasswordStrengthProgressBar/IPasswordStrengthable.cs
new file mode 100644
index 000000000..b26c7468c
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/PasswordStrengthProgressBar/IPasswordStrengthable.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace Bit.App.Controls
+{
+ public interface IPasswordStrengthable
+ {
+ string Password { get; }
+ List UserInputs { get; }
+ }
+}
+
diff --git a/src/Maui/Bitwarden/Controls/PasswordStrengthProgressBar/PasswordStrengthCategory.cs b/src/Maui/Bitwarden/Controls/PasswordStrengthProgressBar/PasswordStrengthCategory.cs
new file mode 100644
index 000000000..8752b0077
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/PasswordStrengthProgressBar/PasswordStrengthCategory.cs
@@ -0,0 +1,17 @@
+using Bit.Core.Attributes;
+
+namespace Bit.App.Controls
+{
+ public enum PasswordStrengthLevel
+ {
+ [LocalizableEnum("Weak")]
+ VeryWeak,
+ [LocalizableEnum("Weak")]
+ Weak,
+ [LocalizableEnum("Good")]
+ Good,
+ [LocalizableEnum("Strong")]
+ Strong
+ }
+}
+
diff --git a/src/Maui/Bitwarden/Controls/PasswordStrengthProgressBar/PasswordStrengthProgressBar.xaml b/src/Maui/Bitwarden/Controls/PasswordStrengthProgressBar/PasswordStrengthProgressBar.xaml
new file mode 100644
index 000000000..4a578982a
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/PasswordStrengthProgressBar/PasswordStrengthProgressBar.xaml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Maui/Bitwarden/Controls/PasswordStrengthProgressBar/PasswordStrengthProgressBar.xaml.cs b/src/Maui/Bitwarden/Controls/PasswordStrengthProgressBar/PasswordStrengthProgressBar.xaml.cs
new file mode 100644
index 000000000..d8e5330b8
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/PasswordStrengthProgressBar/PasswordStrengthProgressBar.xaml.cs
@@ -0,0 +1,110 @@
+using System;
+using Microsoft.Maui.Graphics;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public partial class PasswordStrengthProgressBar : StackLayout
+ {
+ public static readonly BindableProperty PasswordStrengthLevelProperty = BindableProperty.Create(
+ nameof(PasswordStrengthLevel),
+ typeof(PasswordStrengthLevel),
+ typeof(PasswordStrengthProgressBar),
+ propertyChanged: OnControlPropertyChanged);
+
+ public static readonly BindableProperty VeryWeakColorProperty = BindableProperty.Create(
+ nameof(VeryWeakColor),
+ typeof(Color),
+ typeof(PasswordStrengthProgressBar),
+ propertyChanged: OnControlPropertyChanged);
+
+ public static readonly BindableProperty WeakColorProperty = BindableProperty.Create(
+ nameof(WeakColor),
+ typeof(Color),
+ typeof(PasswordStrengthProgressBar),
+ propertyChanged: OnControlPropertyChanged);
+
+ public static readonly BindableProperty GoodColorProperty = BindableProperty.Create(
+ nameof(GoodColor),
+ typeof(Color),
+ typeof(PasswordStrengthProgressBar),
+ propertyChanged: OnControlPropertyChanged);
+
+ public static readonly BindableProperty StrongColorProperty = BindableProperty.Create(
+ nameof(StrongColor),
+ typeof(Color),
+ typeof(PasswordStrengthProgressBar),
+ propertyChanged: OnControlPropertyChanged);
+
+ public PasswordStrengthLevel? PasswordStrengthLevel
+ {
+ get { return (PasswordStrengthLevel?)GetValue(PasswordStrengthLevelProperty); }
+ set { SetValue(PasswordStrengthLevelProperty, value); }
+ }
+
+ public Color VeryWeakColor
+ {
+ get { return (Color)GetValue(VeryWeakColorProperty); }
+ set { SetValue(VeryWeakColorProperty, value); }
+ }
+
+ public Color WeakColor
+ {
+ get { return (Color)GetValue(WeakColorProperty); }
+ set { SetValue(WeakColorProperty, value); }
+ }
+
+ public Color GoodColor
+ {
+ get { return (Color)GetValue(GoodColorProperty); }
+ set { SetValue(GoodColorProperty, value); }
+ }
+
+ public Color StrongColor
+ {
+ get { return (Color)GetValue(StrongColorProperty); }
+ set { SetValue(StrongColorProperty, value); }
+ }
+
+ public PasswordStrengthProgressBar()
+ {
+ InitializeComponent();
+ SetBinding(PasswordStrengthProgressBar.PasswordStrengthLevelProperty, new Binding() { Path = nameof(PasswordStrengthViewModel.PasswordStrengthLevel) });
+ UpdateColors();
+ }
+
+ private static void OnControlPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PasswordStrengthProgressBar)?.UpdateColors();
+ }
+
+ public void UpdateColors()
+ {
+ if (_progressBar == null || _progressLabel == null)
+ {
+ return;
+ }
+ _progressBar.ProgressColor = GetColorForStrength();
+ _progressLabel.TextColor = _progressBar.ProgressColor;
+ }
+
+ private Color GetColorForStrength()
+ {
+ switch (PasswordStrengthLevel)
+ {
+ case Controls.PasswordStrengthLevel.VeryWeak:
+ return VeryWeakColor;
+ case Controls.PasswordStrengthLevel.Weak:
+ return WeakColor;
+ case Controls.PasswordStrengthLevel.Good:
+ return GoodColor;
+ case Controls.PasswordStrengthLevel.Strong:
+ return StrongColor;
+ default:
+ return Colors.Transparent;
+ }
+ }
+ }
+}
+
diff --git a/src/Maui/Bitwarden/Controls/PasswordStrengthProgressBar/PasswordStrengthViewModel.cs b/src/Maui/Bitwarden/Controls/PasswordStrengthProgressBar/PasswordStrengthViewModel.cs
new file mode 100644
index 000000000..9b9210d21
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/PasswordStrengthProgressBar/PasswordStrengthViewModel.cs
@@ -0,0 +1,68 @@
+using System.Collections.Generic;
+using Bit.Core.Abstractions;
+using Bit.Core.Utilities;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class PasswordStrengthViewModel : ExtendedViewModel
+ {
+ private readonly IPasswordGenerationService _passwordGenerationService;
+ private readonly IPasswordStrengthable _passwordStrengthable;
+ private double _passwordStrength;
+ private Color _passwordColor;
+ private PasswordStrengthLevel? _passwordStrengthLevel;
+
+ public PasswordStrengthViewModel(IPasswordStrengthable passwordStrengthable)
+ {
+ _passwordGenerationService = ServiceContainer.Resolve();
+ _passwordStrengthable = passwordStrengthable;
+ }
+
+ public double PasswordStrength
+ {
+ get => _passwordStrength;
+ set => SetProperty(ref _passwordStrength, value);
+ }
+
+ public PasswordStrengthLevel? PasswordStrengthLevel
+ {
+ get => _passwordStrengthLevel;
+ set => SetProperty(ref _passwordStrengthLevel, value);
+ }
+
+ public List GetPasswordStrengthUserInput(string email) => _passwordGenerationService.GetPasswordStrengthUserInput(email);
+
+ public void CalculatePasswordStrength()
+ {
+ if (string.IsNullOrEmpty(_passwordStrengthable.Password))
+ {
+ PasswordStrength = 0;
+ PasswordStrengthLevel = null;
+ return;
+ }
+
+ var passwordStrength = _passwordGenerationService.PasswordStrength(_passwordStrengthable.Password, _passwordStrengthable.UserInputs);
+ // The passwordStrength.Score is 0..4, convertion was made to be used as a progress directly by the control 0..1
+ PasswordStrength = (passwordStrength.Score + 1f) / 5f;
+ if (PasswordStrength <= 0.4f)
+ {
+ PasswordStrengthLevel = Controls.PasswordStrengthLevel.VeryWeak;
+ }
+ else if (PasswordStrength <= 0.6f)
+ {
+ PasswordStrengthLevel = Controls.PasswordStrengthLevel.Weak;
+ }
+ else if (PasswordStrength <= 0.8f)
+ {
+ PasswordStrengthLevel = Controls.PasswordStrengthLevel.Good;
+ }
+ else
+ {
+ PasswordStrengthLevel = Controls.PasswordStrengthLevel.Strong;
+ }
+ }
+ }
+}
+
diff --git a/src/Maui/Bitwarden/Controls/RepeaterView.cs b/src/Maui/Bitwarden/Controls/RepeaterView.cs
new file mode 100644
index 000000000..3502bb654
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/RepeaterView.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections;
+using System.Collections.Specialized;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ [Obsolete]
+ public class RepeaterView : StackLayout
+ {
+ public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
+ nameof(ItemTemplate), typeof(DataTemplate), typeof(RepeaterView), default(DataTemplate));
+
+ public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
+ nameof(ItemsSource), typeof(ICollection), typeof(RepeaterView), null, BindingMode.OneWay,
+ propertyChanged: ItemsSourceChanging);
+
+ public RepeaterView()
+ {
+ Spacing = 0;
+ }
+
+ public ICollection ItemsSource
+ {
+ get => GetValue(ItemsSourceProperty) as ICollection;
+ set => SetValue(ItemsSourceProperty, value);
+ }
+
+ public DataTemplate ItemTemplate
+ {
+ get => GetValue(ItemTemplateProperty) as DataTemplate;
+ set => SetValue(ItemTemplateProperty, value);
+ }
+
+ private void OnCollectionChanged(object sender,
+ NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
+ {
+ Populate();
+ }
+
+ protected override void OnPropertyChanged(string propertyName = null)
+ {
+ base.OnPropertyChanged(propertyName);
+ if (propertyName == ItemTemplateProperty.PropertyName || propertyName == ItemsSourceProperty.PropertyName)
+ {
+ Populate();
+ }
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+ Populate();
+ }
+
+ protected virtual View ViewFor(object item)
+ {
+ View view = null;
+ var template = ItemTemplate;
+ if (template != null)
+ {
+ if (template is DataTemplateSelector selector)
+ {
+ template = selector.SelectTemplate(item, this);
+ }
+ var content = template.CreateContent();
+ view = content is View ? content as View : ((ViewCell)content).View;
+ view.BindingContext = item;
+ }
+ return view;
+ }
+
+ private void Populate()
+ {
+ if (ItemsSource != null)
+ {
+ Children.Clear();
+ foreach (var item in ItemsSource)
+ {
+ Children.Add(ViewFor(item));
+ }
+ }
+ }
+
+ private static void ItemsSourceChanging(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (oldValue != null && oldValue is INotifyCollectionChanged ov)
+ {
+ ov.CollectionChanged -= (bindable as RepeaterView).OnCollectionChanged;
+ }
+ if (newValue != null && newValue is INotifyCollectionChanged nv)
+ {
+ nv.CollectionChanged += (bindable as RepeaterView).OnCollectionChanged;
+ }
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/SelectableLabel.cs b/src/Maui/Bitwarden/Controls/SelectableLabel.cs
new file mode 100644
index 000000000..5ffa4ca52
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/SelectableLabel.cs
@@ -0,0 +1,11 @@
+using System;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public class SelectableLabel : Label
+ {
+
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/SendViewCell/SendViewCell.xaml b/src/Maui/Bitwarden/Controls/SendViewCell/SendViewCell.xaml
new file mode 100644
index 000000000..948468594
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/SendViewCell/SendViewCell.xaml
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Maui/Bitwarden/Controls/SendViewCell/SendViewCell.xaml.cs b/src/Maui/Bitwarden/Controls/SendViewCell/SendViewCell.xaml.cs
new file mode 100644
index 000000000..b9ac7cb6a
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/SendViewCell/SendViewCell.xaml.cs
@@ -0,0 +1,69 @@
+using System;
+using Bit.App.Abstractions;
+using Bit.Core.Models.View;
+using Bit.Core.Utilities;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui;
+
+namespace Bit.App.Controls
+{
+ public partial class SendViewCell : ExtendedGrid
+ {
+ public static readonly BindableProperty SendProperty = BindableProperty.Create(
+ nameof(Send), typeof(SendView), typeof(SendViewCell), default(SendView), BindingMode.OneWay);
+
+ public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create(
+ nameof(ButtonCommand), typeof(Command), typeof(SendViewCell));
+
+ public static readonly BindableProperty ShowOptionsProperty = BindableProperty.Create(
+ nameof(ShowOptions), typeof(bool), typeof(SendViewCell), true, BindingMode.OneWay);
+
+ public SendViewCell()
+ {
+ InitializeComponent();
+
+ var deviceActionService = ServiceContainer.Resolve("deviceActionService");
+ _iconColumn.Width = new GridLength(40 * deviceActionService.GetSystemFontSizeScale(), GridUnitType.Absolute);
+ }
+
+ public SendView Send
+ {
+ get => GetValue(SendProperty) as SendView;
+ set => SetValue(SendProperty, value);
+ }
+
+ public Command ButtonCommand
+ {
+ get => GetValue(ButtonCommandProperty) as Command;
+ set => SetValue(ButtonCommandProperty, value);
+ }
+
+ public bool ShowOptions
+ {
+ get => (bool)GetValue(ShowOptionsProperty);
+ set => SetValue(ShowOptionsProperty, value);
+ }
+
+ protected override void OnPropertyChanged(string propertyName = null)
+ {
+ base.OnPropertyChanged(propertyName);
+ if (propertyName == SendProperty.PropertyName)
+ {
+ if (Send == null)
+ {
+ return;
+ }
+ BindingContext = new SendViewCellViewModel(Send, ShowOptions);
+ }
+ }
+
+ private void MoreButton_Clicked(object sender, EventArgs e)
+ {
+ var send = ((sender as MiButton)?.BindingContext as SendViewCellViewModel)?.Send;
+ if (send != null)
+ {
+ ButtonCommand?.Execute(send);
+ }
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Controls/SendViewCell/SendViewCellViewModel.cs b/src/Maui/Bitwarden/Controls/SendViewCell/SendViewCellViewModel.cs
new file mode 100644
index 000000000..63f6f3704
--- /dev/null
+++ b/src/Maui/Bitwarden/Controls/SendViewCell/SendViewCellViewModel.cs
@@ -0,0 +1,29 @@
+using Bit.Core.Models.View;
+using Bit.Core.Utilities;
+
+namespace Bit.App.Controls
+{
+ public class SendViewCellViewModel : ExtendedViewModel
+ {
+ private SendView _send;
+ private bool _showOptions;
+
+ public SendViewCellViewModel(SendView sendView, bool showOptions)
+ {
+ Send = sendView;
+ ShowOptions = showOptions;
+ }
+
+ public SendView Send
+ {
+ get => _send;
+ set => SetProperty(ref _send, value);
+ }
+
+ public bool ShowOptions
+ {
+ get => _showOptions;
+ set => SetProperty(ref _showOptions, value);
+ }
+ }
+}
diff --git a/src/Maui/Bitwarden/Core/Abstractions/IApiService.cs b/src/Maui/Bitwarden/Core/Abstractions/IApiService.cs
new file mode 100644
index 000000000..b073b227c
--- /dev/null
+++ b/src/Maui/Bitwarden/Core/Abstractions/IApiService.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Bit.Core.Models.Domain;
+using Bit.Core.Models.Request;
+using Bit.Core.Models.Response;
+
+namespace Bit.Core.Abstractions
+{
+ public interface IApiService
+ {
+ string ApiBaseUrl { get; set; }
+ string IdentityBaseUrl { get; set; }
+ string EventsBaseUrl { get; set; }
+ bool UrlsSet { get; }
+
+ Task DeleteCipherAsync(string id);
+ Task DeleteCipherAttachmentAsync(string id, string attachmentId);
+ Task DeleteFolderAsync(string id);
+ Task DoRefreshTokenAsync();
+ Task GetAccountRevisionDateAsync();
+ Task GetActiveBearerTokenAsync();
+ Task GetCipherAsync(string id);
+ Task GetFolderAsync(string id);
+ Task GetProfileAsync();
+ Task GetSyncAsync();
+ Task PostAccountKeysAsync(KeysRequest request);
+ Task PostAccountVerifyPasswordAsync(PasswordVerificationRequest request);
+ Task PostAccountRequestOTP();
+ Task PostAccountVerifyOTPAsync(VerifyOTPRequest request);
+ Task PostCipherAsync(CipherRequest request);
+ Task PostCipherCreateAsync(CipherCreateRequest request);
+ Task PostFolderAsync(FolderRequest request);
+ Task PostIdentityTokenAsync(TokenRequest request);
+ Task PostPasswordHintAsync(PasswordHintRequest request);
+ Task SetPasswordAsync(SetPasswordRequest request);
+ Task PostPreloginAsync(PreloginRequest request);
+ Task PostRegisterAsync(RegisterRequest request);
+ Task PutCipherAsync(string id, CipherRequest request);
+ Task PutCipherCollectionsAsync(string id, CipherCollectionsRequest request);
+ Task PutFolderAsync(string id, FolderRequest request);
+ Task PutShareCipherAsync(string id, CipherShareRequest request);
+ Task PutDeleteCipherAsync(string id);
+ Task PutRestoreCipherAsync(string id);
+ Task RefreshIdentityTokenAsync();
+ Task PreValidateSso(string identifier);
+ Task SendAsync(HttpMethod method, string path,
+ TRequest body, bool authed, bool hasResponse, Action alterRequest, bool logoutOnUnauthorized = true);
+ Task SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default);
+ void SetUrls(EnvironmentUrls urls);
+ [Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
+ Task PostCipherAttachmentLegacyAsync(string id, MultipartFormDataContent data);
+ Task PostCipherAttachmentAsync(string id, AttachmentRequest request);
+ Task GetAttachmentData(string cipherId, string attachmentId);
+ Task PostShareCipherAttachmentAsync(string id, string attachmentId, MultipartFormDataContent data,
+ string organizationId);
+ Task RenewAttachmentUploadUrlAsync(string id, string attachmentId);
+ Task PostAttachmentFileAsync(string id, string attachmentId, MultipartFormDataContent data);
+ Task> GetHibpBreachAsync(string username);
+ Task PostTwoFactorEmailAsync(TwoFactorEmailRequest request);
+ Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request);
+ Task PostEventsCollectAsync(IEnumerable request);
+ Task PutUpdateTempPasswordAsync(UpdateTempPasswordRequest request);
+ Task PostPasswordAsync(PasswordRequest request);
+ Task DeleteAccountAsync(DeleteAccountRequest request);
+ Task GetOrganizationKeysAsync(string id);
+ Task GetOrganizationAutoEnrollStatusAsync(string identifier);
+ Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId,
+ OrganizationUserResetPasswordEnrollmentRequest request);
+ Task GetUserKeyFromKeyConnector(string keyConnectorUrl);
+ Task PostUserKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request);
+ Task PostSetKeyConnectorKey(SetKeyConnectorKeyRequest request);
+ Task PostConvertToKeyConnector();
+ Task PostLeaveOrganization(string id);
+
+ Task GetSendAsync(string id);
+ Task PostSendAsync(SendRequest request);
+ Task PostFileTypeSendAsync(SendRequest request);
+ Task PostSendFileAsync(string sendId, string fileId, MultipartFormDataContent data);
+ [Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
+ Task PostSendFileAsync(MultipartFormDataContent data);
+ Task RenewFileUploadUrlAsync(string sendId, string fileId);
+ Task PutSendAsync(string id, SendRequest request);
+ Task PutSendRemovePasswordAsync(string id);
+ Task DeleteSendAsync(string id);
+ Task> GetAuthRequestAsync();
+ Task GetAuthRequestAsync(string id);
+ Task GetAuthResponseAsync(string id, string accessCode);
+ Task PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved);
+ Task PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest);
+ Task GetKnownDeviceAsync(string email, string deviceIdentifier);
+ Task GetOrgDomainSsoDetailsAsync(string email);
+ Task GetConfigsAsync();
+ Task GetFastmailAccountIdAsync(string apiKey);
+ }
+}
diff --git a/src/Maui/Bitwarden/Core/Abstractions/IAppIdService.cs b/src/Maui/Bitwarden/Core/Abstractions/IAppIdService.cs
new file mode 100644
index 000000000..6c3bd6d30
--- /dev/null
+++ b/src/Maui/Bitwarden/Core/Abstractions/IAppIdService.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+
+namespace Bit.Core.Abstractions
+{
+ public interface IAppIdService
+ {
+ Task GetAppIdAsync();
+ Task GetAnonymousAppIdAsync();
+ }
+}
diff --git a/src/Maui/Bitwarden/Core/Abstractions/IAuditService.cs b/src/Maui/Bitwarden/Core/Abstractions/IAuditService.cs
new file mode 100644
index 000000000..047fb7dad
--- /dev/null
+++ b/src/Maui/Bitwarden/Core/Abstractions/IAuditService.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Bit.Core.Models.Response;
+
+namespace Bit.Core.Abstractions
+{
+ public interface IAuditService
+ {
+ Task> BreachedAccountsAsync(string username);
+ Task PasswordLeakedAsync(string password);
+ }
+}
diff --git a/src/Maui/Bitwarden/Core/Abstractions/IAuthService.cs b/src/Maui/Bitwarden/Core/Abstractions/IAuthService.cs
new file mode 100644
index 000000000..e26438743
--- /dev/null
+++ b/src/Maui/Bitwarden/Core/Abstractions/IAuthService.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Bit.Core.Enums;
+using Bit.Core.Models.Domain;
+using Bit.Core.Models.Request;
+using Bit.Core.Models.Response;
+
+namespace Bit.Core.Abstractions
+{
+ public interface IAuthService
+ {
+ string Email { get; set; }
+ string MasterPasswordHash { get; set; }
+ string Code { get; set; }
+ string CodeVerifier { get; set; }
+ string SsoRedirectUrl { get; set; }
+ TwoFactorProviderType? SelectedTwoFactorProviderType { get; set; }
+ Dictionary TwoFactorProviders { get; set; }
+ Dictionary> TwoFactorProvidersData { get; set; }
+
+ TwoFactorProviderType? GetDefaultTwoFactorProvider(bool fido2Supported);
+ bool AuthingWithSso();
+ bool AuthingWithPassword();
+ List GetSupportedTwoFactorProviders();
+ Task LogInAsync(string email, string masterPassword, string captchaToken);
+ Task LogInSsoAsync(string code, string codeVerifier, string redirectUrl, string orgId);
+ Task LogInCompleteAsync(string email, string masterPassword, TwoFactorProviderType twoFactorProvider, string twoFactorToken, bool? remember = null);
+ Task LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken, string captchaToken, bool? remember = null);
+ Task LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered);
+
+ Task> GetPasswordlessLoginRequestsAsync();
+ Task> GetActivePasswordlessLoginRequestsAsync();
+ Task GetPasswordlessLoginRequestByIdAsync(string id);
+ Task GetPasswordlessLoginResponseAsync(string id, string accessCode);
+ Task PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);
+ Task PasswordlessCreateLoginRequestAsync(string email);
+
+ void LogOut(Action callback);
+ void Init();
+ }
+}
diff --git a/src/Maui/Bitwarden/Core/Abstractions/IAutofillHandler.cs b/src/Maui/Bitwarden/Core/Abstractions/IAutofillHandler.cs
new file mode 100644
index 000000000..84c9489b9
--- /dev/null
+++ b/src/Maui/Bitwarden/Core/Abstractions/IAutofillHandler.cs
@@ -0,0 +1,16 @@
+using Bit.Core.Models.View;
+
+namespace Bit.Core.Abstractions
+{
+ public interface IAutofillHandler
+ {
+ bool AutofillServicesEnabled();
+ bool SupportsAutofillService();
+ void Autofill(CipherView cipher);
+ void CloseAutofill();
+ bool AutofillAccessibilityServiceRunning();
+ bool AutofillAccessibilityOverlayPermitted();
+ bool AutofillServiceEnabled();
+ void DisableAutofillService();
+ }
+}
diff --git a/src/Maui/Bitwarden/Core/Abstractions/IAzureFileUpoadService.cs b/src/Maui/Bitwarden/Core/Abstractions/IAzureFileUpoadService.cs
new file mode 100644
index 000000000..18f3e2ec0
--- /dev/null
+++ b/src/Maui/Bitwarden/Core/Abstractions/IAzureFileUpoadService.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Threading.Tasks;
+using Bit.Core.Models.Domain;
+
+namespace Bit.Core.Abstractions
+{
+ public interface IAzureFileUploadService
+ {
+ Task Upload(string uri, EncByteArray data, Func> renewalCallback);
+ }
+}
diff --git a/src/Maui/Bitwarden/Core/Abstractions/IBiometricService.cs b/src/Maui/Bitwarden/Core/Abstractions/IBiometricService.cs
new file mode 100644
index 000000000..232b301ca
--- /dev/null
+++ b/src/Maui/Bitwarden/Core/Abstractions/IBiometricService.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+
+namespace Bit.Core.Abstractions
+{
+ public interface IBiometricService
+ {
+ Task SetupBiometricAsync(string bioIntegritySrcKey = null);
+ Task IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null);
+ }
+}
diff --git a/src/Maui/Bitwarden/Core/Abstractions/IBroadcasterService.cs b/src/Maui/Bitwarden/Core/Abstractions/IBroadcasterService.cs
new file mode 100644
index 000000000..0675aad18
--- /dev/null
+++ b/src/Maui/Bitwarden/Core/Abstractions/IBroadcasterService.cs
@@ -0,0 +1,13 @@
+using System;
+using Bit.Core.Models.Domain;
+
+namespace Bit.Core.Abstractions
+{
+ public interface IBroadcasterService
+ {
+ void Send(Message message);
+ void Send(Message message, string id);
+ void Subscribe(string id, Action messageCallback);
+ void Unsubscribe(string id);
+ }
+}
diff --git a/src/Maui/Bitwarden/Core/Abstractions/ICipherService.cs b/src/Maui/Bitwarden/Core/Abstractions/ICipherService.cs
new file mode 100644
index 000000000..d68b0dc93
--- /dev/null
+++ b/src/Maui/Bitwarden/Core/Abstractions/ICipherService.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Bit.Core.Enums;
+using Bit.Core.Models.Data;
+using Bit.Core.Models.Domain;
+using Bit.Core.Models.View;
+
+namespace Bit.Core.Abstractions
+{
+ public interface ICipherService
+ {
+ public enum ShareWithServerError
+ {
+ None,
+ DuplicatedPasskeyInOrg
+ }
+
+ Task ClearAsync(string userId);
+ Task ClearCacheAsync();
+ Task DeleteAsync(List ids);
+ Task DeleteAsync(string id);
+ Task DeleteAttachmentAsync(string id, string attachmentId);
+ Task DeleteAttachmentWithServerAsync(string id, string attachmentId);
+ Task DeleteWithServerAsync(string id);
+ Task EncryptAsync(CipherView model, SymmetricCryptoKey key = null, Cipher originalCipher = null);
+ Task> GetAllAsync();
+ Task> GetAllDecryptedAsync(Func filter = null);
+ Task, List, List>> GetAllDecryptedByUrlAsync(string url,
+ List includeOtherTypes = null);
+ Task> GetAllDecryptedForGroupingAsync(string groupingId, bool folder = true);
+ Task> GetAllDecryptedForUrlAsync(string url);
+ Task GetAsync(string id);
+ Task GetLastUsedForUrlAsync(string url);
+ Task ReplaceAsync(Dictionary ciphers);
+ Task SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data);
+ Task SaveCollectionsWithServerAsync(Cipher cipher);
+ Task SaveNeverDomainAsync(string domain);
+ Task SaveWithServerAsync(Cipher cipher);
+ Task ShareWithServerAsync(CipherView cipher, string organizationId, HashSet collectionIds);
+ Task UpdateLastUsedDateAsync(string id);
+ Task UpsertAsync(CipherData cipher);
+ Task UpsertAsync(List cipher);
+ Task DownloadAndDecryptAttachmentAsync(string cipherId, AttachmentView attachment, string organizationId);
+ Task SoftDeleteWithServerAsync(string id);
+ Task RestoreWithServerAsync(string id);
+ }
+}
diff --git a/src/Maui/Bitwarden/Core/Abstractions/IClipboardService.cs b/src/Maui/Bitwarden/Core/Abstractions/IClipboardService.cs
new file mode 100644
index 000000000..ebbe2d975
--- /dev/null
+++ b/src/Maui/Bitwarden/Core/Abstractions/IClipboardService.cs
@@ -0,0 +1,19 @@
+using System.Threading.Tasks;
+
+namespace Bit.Core.Abstractions
+{
+ public interface IClipboardService
+ {
+ ///
+ /// Copies the to the Clipboard.
+ /// If is set > 0 then the Clipboard will be cleared after this time in milliseconds.
+ /// if less than 0 then it takes the configuration that the user set in Options.
+ /// If is true the sensitive flag is passed to the clipdata to obfuscate the
+ /// clipboard text in the popup (Android 13+ only)
+ ///
+ /// Text to be copied to the Clipboard
+ /// Expiration time in milliseconds of the copied text
+ /// Flag to mark copied text as sensitive
+ Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true);
+ }
+}
diff --git a/src/Maui/Bitwarden/Core/Abstractions/ICollectionService.cs b/src/Maui/Bitwarden/Core/Abstractions/ICollectionService.cs
new file mode 100644
index 000000000..b9d4d7f7d
--- /dev/null
+++ b/src/Maui/Bitwarden/Core/Abstractions/ICollectionService.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Bit.Core.Models.Data;
+using Bit.Core.Models.Domain;
+using Bit.Core.Models.View;
+using CollectionView = Bit.Core.Models.View.CollectionView;
+
+namespace Bit.Core.Abstractions
+{
+ public interface ICollectionService
+ {
+ Task ClearAsync(string userId);
+ void ClearCache();
+ Task> DecryptManyAsync(List collections);
+ Task DeleteAsync(string id);
+ Task EncryptAsync(CollectionView model);
+ Task> GetAllAsync();
+ Task> GetAllDecryptedAsync();
+ Task>> GetAllNestedAsync(List