diff --git a/src/Samples/EmbeddedIOSNavigationIssue/Core/App.xaml b/src/Samples/EmbeddedIOSNavigationIssue/Core/App.xaml
new file mode 100644
index 000000000..aa6d49ada
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/Core/App.xaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/Core/App.xaml.cs b/src/Samples/EmbeddedIOSNavigationIssue/Core/App.xaml.cs
new file mode 100644
index 000000000..d62f6994a
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/Core/App.xaml.cs
@@ -0,0 +1,12 @@
+namespace Bit.Core;
+
+public partial class App : Application
+{
+ public App(AppOptions appOptions = null)
+ {
+ InitializeComponent();
+
+ MainPage = new MainPage();
+ }
+}
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/Core/AppOptions.cs b/src/Samples/EmbeddedIOSNavigationIssue/Core/AppOptions.cs
new file mode 100644
index 000000000..b41111fcb
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/Core/AppOptions.cs
@@ -0,0 +1,9 @@
+using System;
+namespace Bit.Core
+{
+ public class AppOptions
+ {
+ public bool IosExtension { get; set; }
+ }
+}
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/Core/Core.csproj b/src/Samples/EmbeddedIOSNavigationIssue/Core/Core.csproj
new file mode 100644
index 000000000..b66465bc5
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/Core/Core.csproj
@@ -0,0 +1,39 @@
+
+
+ net8.0-android;net8.0-ios
+ Bit.Core
+
+ true
+ enable
+ true
+
+ 12.0
+ 21.0
+
+
+ false
+
+
+ false
+
+
+ $(DefineConstants);$(CustomConstants)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/Core/HomePage.xaml b/src/Samples/EmbeddedIOSNavigationIssue/Core/HomePage.xaml
new file mode 100644
index 000000000..a39bfdbc9
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/Core/HomePage.xaml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/Core/HomePage.xaml.cs b/src/Samples/EmbeddedIOSNavigationIssue/Core/HomePage.xaml.cs
new file mode 100644
index 000000000..1cd3a9a80
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/Core/HomePage.xaml.cs
@@ -0,0 +1,28 @@
+namespace Bit.Core;
+
+public partial class HomePage : ContentPage
+{
+ public HomePage(AppOptions appOptions = null)
+ {
+ InitializeComponent();
+
+ if (appOptions is null)
+ {
+ StartLoginAction = () => Navigation.PushModalAsync(new LoginPage());
+ CloseAction = () => Navigation.PopModalAsync();
+ }
+ }
+
+ public Action StartLoginAction { get; set; }
+ public Action CloseAction { get; set; }
+
+ void Login_Clicked(System.Object sender, System.EventArgs e)
+ {
+ StartLoginAction();
+ }
+
+ void Cancel_Clicked(System.Object sender, System.EventArgs e)
+ {
+ CloseAction();
+ }
+}
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/Core/LoginPage.xaml b/src/Samples/EmbeddedIOSNavigationIssue/Core/LoginPage.xaml
new file mode 100644
index 000000000..7a068b110
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/Core/LoginPage.xaml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/Core/LoginPage.xaml.cs b/src/Samples/EmbeddedIOSNavigationIssue/Core/LoginPage.xaml.cs
new file mode 100644
index 000000000..b9a6bb10d
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/Core/LoginPage.xaml.cs
@@ -0,0 +1,29 @@
+namespace Bit.Core;
+
+public partial class LoginPage : ContentPage
+{
+ public LoginPage(AppOptions appOptions = null)
+ {
+ InitializeComponent();
+
+ if (appOptions is null)
+ {
+ LogInSuccessAction = () => DisplayAlert("Login", "Success", "Cancel");
+ CloseAction = () => Navigation.PopModalAsync();
+ }
+ }
+
+ public Action LogInSuccessAction { get; set; }
+ public Action CloseAction { get; set; }
+
+ void LoginSuccess_Tapped(System.Object sender, Microsoft.Maui.Controls.TappedEventArgs e)
+ {
+ LogInSuccessAction();
+ }
+
+ void Close_Tapped(System.Object sender, Microsoft.Maui.Controls.TappedEventArgs e)
+ {
+ CloseAction();
+ }
+}
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/Core/MainPage.xaml b/src/Samples/EmbeddedIOSNavigationIssue/Core/MainPage.xaml
new file mode 100644
index 000000000..00b1f0109
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/Core/MainPage.xaml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/Core/MainPage.xaml.cs b/src/Samples/EmbeddedIOSNavigationIssue/Core/MainPage.xaml.cs
new file mode 100644
index 000000000..5a5907640
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/Core/MainPage.xaml.cs
@@ -0,0 +1,16 @@
+namespace Bit.Core;
+
+public partial class MainPage : ContentPage
+{
+ public MainPage()
+ {
+ InitializeComponent();
+ }
+
+ void Button_Clicked(System.Object sender, System.EventArgs e)
+ {
+ Navigation.PushModalAsync(new HomePage());
+ }
+}
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/Core/MauiProgram.cs b/src/Samples/EmbeddedIOSNavigationIssue/Core/MauiProgram.cs
new file mode 100644
index 000000000..cdcf01060
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/Core/MauiProgram.cs
@@ -0,0 +1,33 @@
+using CommunityToolkit.Maui;
+using Microsoft.Extensions.Logging;
+
+namespace Bit.Core;
+
+public static class MauiProgram
+{
+ public static MauiAppBuilder ConfigureMauiAppBuilder(bool useMauiApp, Action customHandlers = null)
+ {
+ var builder = MauiApp.CreateBuilder();
+ if (useMauiApp)
+ {
+ builder.UseMauiApp();
+ }
+ builder.UseMauiCommunityToolkit()
+ .ConfigureFonts(fonts =>
+ {
+ fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
+ fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
+ })
+ .ConfigureMauiHandlers(handlers =>
+ {
+ customHandlers?.Invoke(handlers);
+ });
+
+#if DEBUG
+ builder.Logging.AddDebug();
+#endif
+
+ return builder;
+ }
+}
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/Core/Properties/launchSettings.json b/src/Samples/EmbeddedIOSNavigationIssue/Core/Properties/launchSettings.json
new file mode 100644
index 000000000..90f92d965
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/Core/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "Windows Machine": {
+ "commandName": "MsixPackage",
+ "nativeDebugging": false
+ }
+ }
+}
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/Core/Resources/Styles/Colors.xaml b/src/Samples/EmbeddedIOSNavigationIssue/Core/Resources/Styles/Colors.xaml
new file mode 100644
index 000000000..6ab4277a9
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/Core/Resources/Styles/Colors.xaml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+ #512BD4
+ #ac99ea
+ #242424
+ #DFD8F7
+ #9880e5
+ #2B0B98
+
+ White
+ Black
+ #D600AA
+ #190649
+ #1f1f1f
+
+ #E1E1E1
+ #C8C8C8
+ #ACACAC
+ #919191
+ #6E6E6E
+ #404040
+ #212121
+ #141414
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/Core/Resources/Styles/Styles.xaml b/src/Samples/EmbeddedIOSNavigationIssue/Core/Resources/Styles/Styles.xaml
new file mode 100644
index 000000000..7c29d259b
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/Core/Resources/Styles/Styles.xaml
@@ -0,0 +1,427 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/Core/ServiceContainer.cs b/src/Samples/EmbeddedIOSNavigationIssue/Core/ServiceContainer.cs
new file mode 100644
index 000000000..651e8f81a
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/Core/ServiceContainer.cs
@@ -0,0 +1,16 @@
+using System;
+namespace Bit.Core
+{
+ public class ServiceContainer
+ {
+ public ServiceContainer()
+ {
+ }
+
+ public static void Reset()
+ {
+
+ }
+ }
+}
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/Core/State.cs b/src/Samples/EmbeddedIOSNavigationIssue/Core/State.cs
new file mode 100644
index 000000000..77b6fd5d8
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/Core/State.cs
@@ -0,0 +1,11 @@
+using System;
+namespace Bit.Core
+{
+ public static class State
+ {
+ public static bool IsAuthed { get; set; } = false;
+
+ public static bool IsLocked { get; set; } = false;
+ }
+}
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/Core/TaskExtensions.cs b/src/Samples/EmbeddedIOSNavigationIssue/Core/TaskExtensions.cs
new file mode 100644
index 000000000..c9c20fd0a
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/Core/TaskExtensions.cs
@@ -0,0 +1,24 @@
+namespace Core
+{
+ public static class TaskExtensions
+ {
+ ///
+ /// Fires a task and ignores any exception.
+ /// See http://stackoverflow.com/a/22864616/344182
+ ///
+ /// The task to be forgotten.
+ /// Action to be called on exception.
+ public static async void FireAndForget(this Task task, Action onException = null)
+ {
+ try
+ {
+ await task.ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.ToString());
+ onException?.Invoke(ex);
+ }
+ }
+ }
+}
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue.sln b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue.sln
new file mode 100644
index 000000000..034420248
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue.sln
@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 25.0.1706.7
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbeddedIOSNavigationIssue", "EmbeddedIOSNavigationIssue\EmbeddedIOSNavigationIssue.csproj", "{E8CBD3FE-E964-45B2-A308-B0DAEB3266E6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IOSExtensionSample", "IOSExtensionSample\IOSExtensionSample.csproj", "{66607AD1-2F9F-43C2-98E6-8ECFFE80A61E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{452ACDA9-B7B3-4BDE-AAF3-A1453CAE2B21}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E8CBD3FE-E964-45B2-A308-B0DAEB3266E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E8CBD3FE-E964-45B2-A308-B0DAEB3266E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E8CBD3FE-E964-45B2-A308-B0DAEB3266E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E8CBD3FE-E964-45B2-A308-B0DAEB3266E6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {66607AD1-2F9F-43C2-98E6-8ECFFE80A61E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {66607AD1-2F9F-43C2-98E6-8ECFFE80A61E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {66607AD1-2F9F-43C2-98E6-8ECFFE80A61E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {66607AD1-2F9F-43C2-98E6-8ECFFE80A61E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {452ACDA9-B7B3-4BDE-AAF3-A1453CAE2B21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {452ACDA9-B7B3-4BDE-AAF3-A1453CAE2B21}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {452ACDA9-B7B3-4BDE-AAF3-A1453CAE2B21}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {452ACDA9-B7B3-4BDE-AAF3-A1453CAE2B21}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {54599F8D-8A3F-4A2A-AD7D-D7396CE679D6}
+ EndGlobalSection
+EndGlobal
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue.csproj b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue.csproj
new file mode 100644
index 000000000..d8635a3bb
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue.csproj
@@ -0,0 +1,73 @@
+
+
+
+ net8.0-android;net8.0-ios
+ $(TargetFrameworks);net8.0-windows10.0.19041.0
+
+ Exe
+ EmbeddedIOSNavigationIssue
+ true
+ true
+ enable
+ enable
+ true
+
+
+ EmbeddedIOSNavigationIssue
+
+
+ com.8bit.bitwarden
+
+
+ 2023.12.1
+ 1
+
+ 12.0
+ 21.0
+
+
+
+ false
+ Automatic
+ iPhone Developer
+ Platforms\iOS\Entitlements.plist
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/MauiProgram.cs b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/MauiProgram.cs
new file mode 100644
index 000000000..8cd6c52a1
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/MauiProgram.cs
@@ -0,0 +1,12 @@
+using Microsoft.Maui.Hosting;
+
+namespace EmbeddedIOSNavigationIssue;
+
+public static class MauiProgram
+{
+ public static MauiApp CreateMauiApp()
+ {
+ return Bit.Core.MauiProgram.ConfigureMauiAppBuilder(true).Build();
+ }
+}
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/Android/AndroidManifest.xml b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/Android/AndroidManifest.xml
new file mode 100644
index 000000000..def12822a
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/Android/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/Android/MainActivity.cs b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/Android/MainActivity.cs
new file mode 100644
index 000000000..f39ea38b6
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/Android/MainActivity.cs
@@ -0,0 +1,11 @@
+using Android.App;
+using Android.Content.PM;
+using Android.OS;
+
+namespace EmbeddedIOSNavigationIssue;
+
+[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
+public class MainActivity : MauiAppCompatActivity
+{
+}
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/Android/MainApplication.cs b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/Android/MainApplication.cs
new file mode 100644
index 000000000..2ece6b066
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/Android/MainApplication.cs
@@ -0,0 +1,16 @@
+using Android.App;
+using Android.Runtime;
+
+namespace EmbeddedIOSNavigationIssue;
+
+[Application]
+public class MainApplication : MauiApplication
+{
+ public MainApplication(IntPtr handle, JniHandleOwnership ownership)
+ : base(handle, ownership)
+ {
+ }
+
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/Android/Resources/values/colors.xml b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/Android/Resources/values/colors.xml
new file mode 100644
index 000000000..3b8595ecd
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/Android/Resources/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #512BD4
+ #2B0B98
+ #2B0B98
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/iOS/AppDelegate.cs b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/iOS/AppDelegate.cs
new file mode 100644
index 000000000..fc62f49d4
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/iOS/AppDelegate.cs
@@ -0,0 +1,11 @@
+using Foundation;
+using Microsoft.Maui.Hosting;
+
+namespace EmbeddedIOSNavigationIssue;
+
+[Register("AppDelegate")]
+public class AppDelegate : MauiUIApplicationDelegate
+{
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/iOS/Entitlements.plist b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/iOS/Entitlements.plist
new file mode 100644
index 000000000..d22cbeb9a
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/iOS/Entitlements.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ com.apple.developer.authentication-services.autofill-credential-provider
+
+ com.apple.security.application-groups
+
+ group.com.8bit.bitwarden
+
+ keychain-access-groups
+
+ $(AppIdentifierPrefix)com.8bit.bitwarden
+
+ com.apple.developer.ubiquity-container-identifiers
+
+ iCloud.$(CFBundleIdentifier)
+
+ com.apple.developer.associated-domains
+
+ webcredentials:bitwarden.com
+
+ aps-environment
+ development
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/iOS/Info.plist b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/iOS/Info.plist
new file mode 100644
index 000000000..0004a4fde
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/iOS/Info.plist
@@ -0,0 +1,32 @@
+
+
+
+
+ LSRequiresIPhoneOS
+
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/appicon.appiconset
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/iOS/Program.cs b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/iOS/Program.cs
new file mode 100644
index 000000000..b5ef3366c
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Platforms/iOS/Program.cs
@@ -0,0 +1,16 @@
+using ObjCRuntime;
+using UIKit;
+
+namespace EmbeddedIOSNavigationIssue;
+
+public class Program
+{
+ // This is the main entry point of the application.
+ static void Main(string[] args)
+ {
+ // if you want to use a different Application Delegate class from "AppDelegate"
+ // you can specify it here.
+ UIApplication.Main(args, null, typeof(AppDelegate));
+ }
+}
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Properties/launchSettings.json b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Properties/launchSettings.json
new file mode 100644
index 000000000..90f92d965
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "Windows Machine": {
+ "commandName": "MsixPackage",
+ "nativeDebugging": false
+ }
+ }
+}
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/AppIcon/appicon.svg b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/AppIcon/appicon.svg
new file mode 100644
index 000000000..49f980057
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/AppIcon/appicon.svg
@@ -0,0 +1,5 @@
+
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/AppIcon/appiconfg.svg b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/AppIcon/appiconfg.svg
new file mode 100644
index 000000000..e9b7139de
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/AppIcon/appiconfg.svg
@@ -0,0 +1,8 @@
+
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/Fonts/OpenSans-Regular.ttf b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/Fonts/OpenSans-Regular.ttf
new file mode 100644
index 000000000..9ab655d2b
Binary files /dev/null and b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/Fonts/OpenSans-Regular.ttf differ
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/Fonts/OpenSans-Semibold.ttf b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/Fonts/OpenSans-Semibold.ttf
new file mode 100644
index 000000000..2b7468e99
Binary files /dev/null and b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/Fonts/OpenSans-Semibold.ttf differ
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/Raw/AboutAssets.txt b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/Raw/AboutAssets.txt
new file mode 100644
index 000000000..29b76b2ea
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/Raw/AboutAssets.txt
@@ -0,0 +1,18 @@
+Any raw assets you want to be deployed with your application can be placed in
+this directory (and child directories). Deployment of the asset to your application
+is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
+
+
+
+These files will be deployed with you package and will be accessible using Essentials:
+
+ async Task LoadMauiAsset()
+ {
+ using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
+ using var reader = new StreamReader(stream);
+
+ var contents = reader.ReadToEnd();
+ }
+
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/Splash/splash.svg b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/Splash/splash.svg
new file mode 100644
index 000000000..4b713836f
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/Splash/splash.svg
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/Styles/Colors.xaml b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/Styles/Colors.xaml
new file mode 100644
index 000000000..6ab4277a9
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/Styles/Colors.xaml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+ #512BD4
+ #ac99ea
+ #242424
+ #DFD8F7
+ #9880e5
+ #2B0B98
+
+ White
+ Black
+ #D600AA
+ #190649
+ #1f1f1f
+
+ #E1E1E1
+ #C8C8C8
+ #ACACAC
+ #919191
+ #6E6E6E
+ #404040
+ #212121
+ #141414
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/Styles/Styles.xaml b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/Styles/Styles.xaml
new file mode 100644
index 000000000..7c29d259b
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/EmbeddedIOSNavigationIssue/Resources/Styles/Styles.xaml
@@ -0,0 +1,427 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/AppDelegate.cs b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/AppDelegate.cs
new file mode 100644
index 000000000..e27c57ae6
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/AppDelegate.cs
@@ -0,0 +1,30 @@
+using Foundation;
+using UIKit;
+
+namespace Bit.iOS.Autofill
+{
+ [Register("AppDelegate")]
+ public partial class AppDelegate : UIApplicationDelegate
+ {
+ public override UIWindow Window
+ {
+ get; set;
+ }
+
+ public override void OnResignActivation(UIApplication application)
+ {
+ }
+
+ public override void DidEnterBackground(UIApplication application)
+ {
+ }
+
+ public override void WillEnterForeground(UIApplication application)
+ {
+ }
+
+ public override void WillTerminate(UIApplication application)
+ {
+ }
+ }
+}
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/ClipLogger.cs b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/ClipLogger.cs
new file mode 100644
index 000000000..b28389ac0
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/ClipLogger.cs
@@ -0,0 +1,21 @@
+
+using System;
+using System.Text;
+using UIKit;
+
+namespace IOSExtensionSample
+{
+ public static class ClipLogger
+ {
+ private static readonly StringBuilder _currentBreadcrumbs = new StringBuilder();
+
+ public static void Log(Exception ex) => Log(ex.ToString());
+
+ public static void Log(string breadcrumb)
+ {
+ _currentBreadcrumbs.AppendLine($"{DateTime.Now.ToShortTimeString()}: {breadcrumb}");
+ UIPasteboard.General.String = _currentBreadcrumbs.ToString();
+ }
+ }
+}
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/Context.cs b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/Context.cs
new file mode 100644
index 000000000..dccd66468
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/Context.cs
@@ -0,0 +1,59 @@
+using System;
+using AuthenticationServices;
+using Foundation;
+
+namespace Bit.iOS.Autofill
+{
+ public class Context
+ {
+ public NSExtensionContext ExtContext { get; set; }
+ public ASCredentialServiceIdentifier[] ServiceIdentifiers { get; set; }
+ public ASPasswordCredentialIdentity CredentialIdentity { get; set; }
+ public bool Configuring { get; set; }
+
+ // ---
+
+ private const string iOSProtocol = "iosapp://";
+
+ private string _uriString;
+
+ public Uri Uri
+ {
+ get
+ {
+ if (string.IsNullOrWhiteSpace(UrlString) || !Uri.TryCreate(UrlString, UriKind.Absolute, out Uri uri))
+ {
+ return null;
+ }
+ return uri;
+ }
+ }
+
+ public string UrlString
+ {
+ get
+ {
+ return _uriString;
+ }
+ set
+ {
+ _uriString = value;
+ if (string.IsNullOrWhiteSpace(_uriString))
+ {
+ return;
+ }
+ if (!_uriString.StartsWith(iOSProtocol) && _uriString.Contains("."))
+ {
+ if (!_uriString.Contains("://") && !_uriString.Contains(" "))
+ {
+ _uriString = string.Concat("http://", _uriString);
+ }
+ }
+ if (!_uriString.StartsWith("http") && !_uriString.StartsWith(iOSProtocol))
+ {
+ _uriString = string.Concat(iOSProtocol, _uriString);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/CredentialProviderViewController.cs b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/CredentialProviderViewController.cs
new file mode 100644
index 000000000..aa8c1ef92
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/CredentialProviderViewController.cs
@@ -0,0 +1,365 @@
+using System;
+using System.Threading.Tasks;
+using AuthenticationServices;
+using Bit.Core;
+using Core;
+using CoreFoundation;
+using Foundation;
+using IOSExtensionSample;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Maui;
+using Microsoft.Maui.ApplicationModel;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui.Embedding;
+using Microsoft.Maui.Hosting;
+using Microsoft.Maui.Platform;
+using UIKit;
+
+namespace Bit.iOS.Autofill
+{
+ public partial class CredentialProviderViewController : ASCredentialProviderViewController
+ {
+ private Context _context;
+ private bool _isInit;
+
+ public CredentialProviderViewController(IntPtr handle)
+ : base(handle)
+ {
+ ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
+ }
+
+ private ASCredentialProviderExtensionContext ASExtensionContext => _context?.ExtContext as ASCredentialProviderExtensionContext;
+
+ public override void ViewDidLoad()
+ {
+ try
+ {
+ InitAppIfNeeded();
+
+ base.ViewDidLoad();
+
+ _context = new Context
+ {
+ ExtContext = ExtensionContext
+ };
+ }
+ catch (Exception ex)
+ {
+ ClipLogger.Log(ex);
+ throw;
+ }
+ }
+
+ public override async void PrepareCredentialList(ASCredentialServiceIdentifier[] serviceIdentifiers)
+ {
+ try
+ {
+ InitAppIfNeeded();
+ _context.ServiceIdentifiers = serviceIdentifiers;
+ if (serviceIdentifiers.Length > 0)
+ {
+ var uri = serviceIdentifiers[0].Identifier;
+ if (serviceIdentifiers[0].Type == ASCredentialServiceIdentifierType.Domain)
+ {
+ uri = string.Concat("https://", uri);
+ }
+ _context.UrlString = uri;
+ }
+ if (!await IsAuthed())
+ {
+ LaunchHomePage();
+ }
+ else if (await IsLocked())
+ {
+ PerformSegue("lockPasswordSegue", this);
+ }
+ else
+ {
+ PerformSegue("loginListSegue", this);
+ }
+ }
+ catch (Exception ex)
+ {
+ ClipLogger.Log(ex);
+ throw;
+ }
+ }
+
+ public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity)
+ {
+ try
+ {
+ InitAppIfNeeded();
+ if (!await IsAuthed() || await IsLocked())
+ {
+ var err = new NSError(new NSString("ASExtensionErrorDomain"),
+ Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null);
+ ExtensionContext.CancelRequest(err);
+ return;
+ }
+ _context.CredentialIdentity = credentialIdentity;
+ await ProvideCredentialAsync(false);
+ }
+ catch (Exception ex)
+ {
+ ClipLogger.Log(ex);
+ throw;
+ }
+ }
+
+ public override async void PrepareInterfaceToProvideCredential(ASPasswordCredentialIdentity credentialIdentity)
+ {
+ try
+ {
+ InitAppIfNeeded();
+ if (!await IsAuthed())
+ {
+ LaunchHomePage();
+ return;
+ }
+ _context.CredentialIdentity = credentialIdentity;
+ await CheckLockAsync(async () => await ProvideCredentialAsync());
+ }
+ catch (Exception ex)
+ {
+ ClipLogger.Log(ex);
+ throw;
+ }
+ }
+
+ public override async void PrepareInterfaceForExtensionConfiguration()
+ {
+ try
+ {
+ InitAppIfNeeded();
+ _context.Configuring = true;
+ if (!await IsAuthed())
+ {
+ LaunchHomePage();
+ return;
+ }
+ await CheckLockAsync(() => PerformSegue("setupSegue", this));
+ }
+ catch (Exception ex)
+ {
+ ClipLogger.Log(ex);
+ throw;
+ }
+ }
+
+ public void CompleteRequest(string id = null, string username = null,
+ string password = null, string totp = null)
+ {
+ if ((_context?.Configuring ?? true) && string.IsNullOrWhiteSpace(password))
+ {
+ ServiceContainer.Reset();
+ ASExtensionContext?.CompleteExtensionConfigurationRequest();
+ return;
+ }
+
+ if (_context == null || string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
+ {
+ ServiceContainer.Reset();
+ var err = new NSError(new NSString("ASExtensionErrorDomain"),
+ Convert.ToInt32(ASExtensionErrorCode.UserCanceled), null);
+ NSRunLoop.Main.BeginInvokeOnMainThread(() => ASExtensionContext?.CancelRequest(err));
+ return;
+ }
+
+ var cred = new ASPasswordCredential(username, password);
+ NSRunLoop.Main.BeginInvokeOnMainThread(() =>
+ {
+ ServiceContainer.Reset();
+ ASExtensionContext?.CompleteRequest(cred, null);
+ });
+ }
+
+ public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
+ {
+ try
+ {
+ if (segue.DestinationViewController is UINavigationController navController)
+ {
+ if (navController.TopViewController is LoginListViewController listLoginController)
+ {
+ listLoginController.CPViewController = this;
+ segue.DestinationViewController.PresentationController.Delegate =
+ new CustomPresentationControllerDelegate(listLoginController.DismissModalAction);
+ }
+ else if (navController.TopViewController is LockPasswordViewController passwordViewController)
+ {
+ passwordViewController.CPViewController = this;
+ passwordViewController.LaunchHomePage = () => DismissViewController(false, () => LaunchHomePage());
+ segue.DestinationViewController.PresentationController.Delegate =
+ new CustomPresentationControllerDelegate(passwordViewController.DismissModalAction);
+ }
+ else if (navController.TopViewController is SetupViewController setupViewController)
+ {
+ setupViewController.CPViewController = this;
+ segue.DestinationViewController.PresentationController.Delegate =
+ new CustomPresentationControllerDelegate(setupViewController.DismissModalAction);
+ }
+ }
+
+ }
+ catch (Exception ex)
+ {
+ ClipLogger.Log(ex);
+ throw;
+ }
+ }
+
+ public async Task OnLockDismissedAsync()
+ {
+ try
+ {
+ if (_context.CredentialIdentity != null)
+ {
+ await MainThread.InvokeOnMainThreadAsync(() => ProvideCredentialAsync());
+ return;
+ }
+ if (_context.Configuring)
+ {
+ await MainThread.InvokeOnMainThreadAsync(() => PerformSegue("setupSegue", this));
+ return;
+ }
+
+ await MainThread.InvokeOnMainThreadAsync(() => PerformSegue("loginListSegue", this));
+ }
+ catch (Exception ex)
+ {
+ ClipLogger.Log(ex);
+ throw;
+ }
+ }
+
+ private async Task ProvideCredentialAsync(bool userInteraction = true)
+ {
+ try
+ {
+ await Task.Delay(200);
+
+ CompleteRequest("asdfa", "myUser", "myPass", "someTotp");
+ }
+ catch (Exception ex)
+ {
+ ClipLogger.Log(ex);
+ throw;
+ }
+ }
+
+ private async Task CheckLockAsync(Action notLockedAction)
+ {
+ if (await IsLocked())
+ {
+ DispatchQueue.MainQueue.DispatchAsync(() => PerformSegue("lockPasswordSegue", this));
+ }
+ else
+ {
+ notLockedAction();
+ }
+ }
+
+ private Task IsLocked()
+ {
+ return Task.FromResult(State.IsLocked);
+ }
+
+ private Task IsAuthed()
+ {
+ return Task.FromResult(State.IsAuthed);
+ }
+
+ private void InitAppIfNeeded()
+ {
+ if (_isInit)
+ return;
+
+ var builder = Bit.Core.MauiProgram.ConfigureMauiAppBuilder(false, handlers =>
+ {
+ // WORKAROUND: This is needed to make TapGestureRecognizer work on extensions.
+ handlers.AddHandler(typeof(Window), typeof(CustomWindowHandler));
+ })
+ .UseMauiEmbedding();
+ // Register the Window
+ builder.Services.Add(new ServiceDescriptor(typeof(UIWindow), _ => UIApplication.SharedApplication.KeyWindow, ServiceLifetime.Singleton));
+ var mauiApp = builder.Build();
+
+ MauiContextSingleton.Instance.Init(new MauiContext(mauiApp.Services));
+
+ _isInit = true;
+ }
+
+ private void LaunchHomePage()
+ {
+ var appOptions = new AppOptions { IosExtension = true };
+ var homePage = new HomePage(appOptions);
+ var app = new App(appOptions);
+
+ homePage.StartLoginAction = () => DismissViewController(false, () => LaunchLoginFlow());
+ homePage.CloseAction = () => CompleteRequest();
+
+ NavigateToPage(homePage);
+ }
+
+ private void LaunchLoginFlow()
+ {
+ var appOptions = new AppOptions { IosExtension = true };
+ var app = new App(appOptions);
+ var loginPage = new LoginPage(appOptions);
+
+ loginPage.LogInSuccessAction = () => DismissLockAndContinue();
+ loginPage.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
+
+ NavigateToPage(loginPage);
+ }
+
+#if !ENABLED_TAP_GESTURE_RECOGNIZER_MAUI_EMBEDDED_WORKAROUND
+ public async void DismissLockAndContinue()
+ {
+ DismissViewController(false, async () => await OnLockDismissedAsync());
+ }
+
+ private void NavigateToPage(ContentPage page)
+ {
+ var navigationPage = new NavigationPage(page);
+ var uiController = navigationPage.ToUIViewController(MauiContextSingleton.Instance.MauiContext);
+ uiController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
+
+ PresentViewController(uiController, true, null);
+ }
+#else
+ const string STORYBOARD_NAME = "MainInterface";
+ Lazy _storyboard = new Lazy(() => UIStoryboard.FromName(STORYBOARD_NAME, null));
+
+ public void InitWithContext(Context context)
+ {
+ _context = context;
+ }
+
+ public void DismissLockAndContinue()
+ {
+ if (UIApplication.SharedApplication.KeyWindow is null)
+ {
+ return;
+ }
+
+ UIApplication.SharedApplication.KeyWindow.RootViewController = _storyboard.Value.InstantiateInitialViewController();
+
+ if (UIApplication.SharedApplication.KeyWindow?.RootViewController is CredentialProviderViewController cpvc)
+ {
+ cpvc.InitWithContext(_context);
+ cpvc.OnLockDismissedAsync().FireAndForget();
+ }
+ }
+
+ private void NavigateToPage(ContentPage page)
+ {
+ var navigationPage = new NavigationPage(page);
+
+ var window = new Window(navigationPage);
+ window.ToHandler(MauiContextSingleton.Instance.MauiContext);
+ }
+#endif
+ }
+}
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/CredentialProviderViewController.designer.cs b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/CredentialProviderViewController.designer.cs
new file mode 100644
index 000000000..39ed7490a
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/CredentialProviderViewController.designer.cs
@@ -0,0 +1,20 @@
+// WARNING
+//
+// This file has been generated automatically by Visual Studio to store outlets and
+// actions made in the UI designer. If it is removed, they will be lost.
+// Manual changes to this file may not be handled correctly.
+//
+using Foundation;
+using System.CodeDom.Compiler;
+
+namespace Bit.iOS.Autofill
+{
+ [Register ("CredentialProviderViewController")]
+ partial class CredentialProviderViewController
+ {
+
+ void ReleaseDesignerOutlets ()
+ {
+ }
+ }
+}
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/CustomPresentationControllerDelegate.cs b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/CustomPresentationControllerDelegate.cs
new file mode 100644
index 000000000..5e71a968c
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/CustomPresentationControllerDelegate.cs
@@ -0,0 +1,22 @@
+using System;
+using Foundation;
+using UIKit;
+
+namespace Bit.iOS.Autofill
+{
+ public class CustomPresentationControllerDelegate : UIAdaptivePresentationControllerDelegate
+ {
+ private readonly Action DismissModalAction;
+
+ public CustomPresentationControllerDelegate(Action dismissModalAction)
+ {
+ DismissModalAction = dismissModalAction;
+ }
+
+ [Export("presentationControllerDidDismiss:")]
+ public override void DidDismiss(UIPresentationController presentationController)
+ {
+ DismissModalAction?.Invoke();
+ }
+ }
+}
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/CustomWindowHandler.cs b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/CustomWindowHandler.cs
new file mode 100644
index 000000000..d682a945f
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/CustomWindowHandler.cs
@@ -0,0 +1,26 @@
+using Microsoft.Maui;
+using Microsoft.Maui.ApplicationModel;
+using Microsoft.Maui.Handlers;
+using UIKit;
+
+namespace IOSExtensionSample
+{
+ public class CustomWindowHandler : ElementHandler, IWindowHandler
+ {
+ public static IPropertyMapper Mapper = new PropertyMapper(ElementHandler.ElementMapper)
+ {
+ };
+
+ public CustomWindowHandler() : base(Mapper)
+ {
+ }
+
+ protected override UIWindow CreatePlatformElement()
+ {
+ // Haven't tested
+ //return UIApplication.SharedApplication.Delegate.GetWindow();
+ return Platform.GetCurrentUIViewController().View.Window;
+ }
+ }
+}
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/Entitlements.plist b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/Entitlements.plist
new file mode 100644
index 000000000..ef9236c02
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/Entitlements.plist
@@ -0,0 +1,16 @@
+
+
+
+
+ com.apple.developer.authentication-services.autofill-credential-provider
+
+ com.apple.security.application-groups
+
+ group.com.8bit.bitwarden
+
+ keychain-access-groups
+
+ $(AppIdentifierPrefix)com.8bit.bitwarden
+
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/IOSExtensionSample.csproj b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/IOSExtensionSample.csproj
new file mode 100644
index 000000000..d79f9a2a5
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/IOSExtensionSample.csproj
@@ -0,0 +1,71 @@
+
+
+
+ net8.0-ios
+ True
+ Library
+ com.8bit.bitwarden.autofill
+ 1.0
+ 1
+
+ False
+ $(DefineConstants);ENABLED_TAP_GESTURE_RECOGNIZER_MAUI_EMBEDDED_WORKAROUND
+
+ 12.0
+
+
+
+ true
+ false
+
+
+
+ false
+ Automatic
+ iPhone Developer
+ Entitlements.plist
+ true
+
+
+ false
+ true
+ iPhone Distribution
+ Automatic:AppStore
+ Entitlements.plist
+
+
+
+
+ CredentialProviderViewController.cs
+
+
+
+ LockPasswordViewController.cs
+
+
+
+ LoginListViewController.cs
+
+
+
+ SetupViewController.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/Info.plist b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/Info.plist
new file mode 100644
index 000000000..1f84bb003
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/Info.plist
@@ -0,0 +1,58 @@
+
+
+
+
+ MinimumOSVersion
+ 12.0
+ CFBundleDisplayName
+ Bitwarden
+ CFBundleName
+ Bitwarden Autofill
+ CFBundleIdentifier
+ com.8bit.bitwarden.autofill
+ CFBundleShortVersionString
+ 2023.12.1
+ CFBundleVersion
+ 1
+ CFBundleLocalizations
+
+ en
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundlePackageType
+ XPC!
+ CFBundleSignature
+ ????
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UISupportedInterfaceOrientations
+
+ CADisableMinimumFrameDurationOnPhone
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+
+ NSFaceIDUsageDescription
+ Use Face ID to unlock your vault.
+ NSExtension
+
+ NSExtensionMainStoryboard
+ MainInterface
+ NSExtensionPointIdentifier
+ com.apple.authentication-services-credential-provider-ui
+ NSExtensionAttributes
+
+ ASCredentialProviderExtensionShowsConfigurationUI
+
+
+
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/LockPasswordViewController.cs b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/LockPasswordViewController.cs
new file mode 100644
index 000000000..b17e2671e
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/LockPasswordViewController.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Threading.Tasks;
+using Core;
+using UIKit;
+
+namespace Bit.iOS.Autofill
+{
+ public partial class LockPasswordViewController : UIViewController
+ {
+ public Action DismissModalAction { get; set; }
+
+ public LockPasswordViewController(IntPtr handle)
+ : base(handle)
+ {
+ DismissModalAction = Cancel;
+ }
+
+ public UIBarButtonItem BaseCancelButton => CancelButton;
+ public UIBarButtonItem BaseSubmitButton => SubmitButton;
+ public Action Success => () => CPViewController.DismissLockAndContinue();
+ public Action Cancel => () => CPViewController.CompleteRequest();
+ public Action LaunchHomePage { get; set; }
+
+ public CredentialProviderViewController CPViewController { get; set; }
+
+ public override void ViewDidLoad()
+ {
+ base.ViewDidLoad();
+ }
+
+ partial void SubmitButton_Activated(UIBarButtonItem sender)
+ {
+ CheckPasswordAsync().FireAndForget();
+ }
+
+ private async Task CheckPasswordAsync()
+ {
+ // some logic
+ await Task.Delay(100);
+ Success();
+ }
+
+ partial void CancelButton_Activated(UIBarButtonItem sender)
+ {
+ Cancel();
+ }
+ }
+}
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/LockPasswordViewController.designer.cs b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/LockPasswordViewController.designer.cs
new file mode 100644
index 000000000..18975a7f8
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/LockPasswordViewController.designer.cs
@@ -0,0 +1,54 @@
+// WARNING
+//
+// This file has been generated automatically by Visual Studio to store outlets and
+// actions made in the UI designer. If it is removed, they will be lost.
+// Manual changes to this file may not be handled correctly.
+//
+using Foundation;
+using System.CodeDom.Compiler;
+
+namespace Bit.iOS.Autofill
+{
+ [Register ("LockPasswordViewController")]
+ partial class LockPasswordViewController
+ {
+ [Outlet]
+ [GeneratedCode ("iOS Designer", "1.0")]
+ UIKit.UIBarButtonItem CancelButton { get; set; }
+
+ [Outlet]
+ [GeneratedCode ("iOS Designer", "1.0")]
+ UIKit.UINavigationItem NavItem { get; set; }
+
+ [Outlet]
+ [GeneratedCode ("iOS Designer", "1.0")]
+ UIKit.UIBarButtonItem SubmitButton { get; set; }
+
+ [Action ("AccountSwitchingBarButton_Activated:")]
+ partial void AccountSwitchingBarButton_Activated (UIKit.UIBarButtonItem sender);
+
+ [Action ("CancelButton_Activated:")]
+ partial void CancelButton_Activated (UIKit.UIBarButtonItem sender);
+
+ [Action ("SubmitButton_Activated:")]
+ partial void SubmitButton_Activated (UIKit.UIBarButtonItem sender);
+
+ void ReleaseDesignerOutlets ()
+ {
+ if (CancelButton != null) {
+ CancelButton.Dispose ();
+ CancelButton = null;
+ }
+
+ if (NavItem != null) {
+ NavItem.Dispose ();
+ NavItem = null;
+ }
+
+ if (SubmitButton != null) {
+ SubmitButton.Dispose ();
+ SubmitButton = null;
+ }
+ }
+ }
+}
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/LoginListViewController.cs b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/LoginListViewController.cs
new file mode 100644
index 000000000..7cd37ac60
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/LoginListViewController.cs
@@ -0,0 +1,77 @@
+using System;
+using Foundation;
+using UIKit;
+
+namespace Bit.iOS.Autofill
+{
+ public partial class LoginListViewController : UIViewController
+ {
+ public Action DismissModalAction { get; set; }
+
+ public LoginListViewController(IntPtr handle)
+ : base(handle)
+ {
+ DismissModalAction = Cancel;
+ }
+
+ public CredentialProviderViewController CPViewController { get; set; }
+
+ public override void ViewDidLoad()
+ {
+ base.ViewDidLoad();
+
+ NavItem.Title = "Items";
+ CancelBarButton.Title = "Cancel";
+
+ TableView.RowHeight = UITableView.AutomaticDimension;
+ TableView.EstimatedRowHeight = 44;
+ TableView.BackgroundColor = UIColor.LightGray;
+ TableView.Source = new TableSource(this);
+ }
+
+ partial void CancelBarButton_Activated(UIBarButtonItem sender)
+ {
+ Cancel();
+ }
+
+ private void Cancel()
+ {
+ CPViewController.CompleteRequest();
+ }
+
+ public class TableSource : UITableViewSource
+ {
+ private LoginListViewController _controller;
+
+ public TableSource(LoginListViewController controller)
+ {
+ _controller = controller;
+ }
+
+ public override nint RowsInSection(UITableView tableview, nint section)
+ {
+ return 3;
+ }
+
+ public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
+ {
+ var cell = tableView.DequeueReusableCell("TableCell");
+
+ if (cell == null)
+ {
+ cell = new UITableViewCell(UITableViewCellStyle.Default, "TableCell");
+ cell.TextLabel.Text = "Some Item";
+ }
+ return cell;
+ }
+
+ public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
+ {
+ tableView.DeselectRow(indexPath, true);
+ tableView.EndEditing(true);
+
+ _controller.CPViewController.CompleteRequest("qer", "myUser", "myPassword", "myTOTP");
+ }
+ }
+ }
+}
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/LoginListViewController.designer.cs b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/LoginListViewController.designer.cs
new file mode 100644
index 000000000..8bdd8059c
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/LoginListViewController.designer.cs
@@ -0,0 +1,89 @@
+// WARNING
+//
+// This file has been generated automatically by Visual Studio to store outlets and
+// actions made in the UI designer. If it is removed, they will be lost.
+// Manual changes to this file may not be handled correctly.
+//
+using Foundation;
+using System.CodeDom.Compiler;
+
+namespace Bit.iOS.Autofill
+{
+ [Register ("LoginListViewController")]
+ partial class LoginListViewController
+ {
+ [Outlet]
+ UIKit.UIBarButtonItem AccountSwitchingBarButton { get; set; }
+
+ [Outlet]
+ [GeneratedCode ("iOS Designer", "1.0")]
+ UIKit.UIBarButtonItem AddBarButton { get; set; }
+
+ [Outlet]
+ [GeneratedCode ("iOS Designer", "1.0")]
+ UIKit.UIBarButtonItem CancelBarButton { get; set; }
+
+ [Outlet]
+ UIKit.UIView MainView { get; set; }
+
+ [Outlet]
+ [GeneratedCode ("iOS Designer", "1.0")]
+ UIKit.UINavigationItem NavItem { get; set; }
+
+ [Outlet]
+ UIKit.UIView OverlayView { get; set; }
+
+ [Outlet]
+ UIKit.UITableView TableView { get; set; }
+
+ [Action ("AccountSwitchingBarButton_Activated:")]
+ partial void AccountSwitchingBarButton_Activated (UIKit.UIBarButtonItem sender);
+
+ [Action ("AddBarButton_Activated:")]
+ partial void AddBarButton_Activated (UIKit.UIBarButtonItem sender);
+
+ [Action ("CancelBarButton_Activated:")]
+ partial void CancelBarButton_Activated (UIKit.UIBarButtonItem sender);
+
+ [Action ("SearchBarButton_Activated:")]
+ partial void SearchBarButton_Activated (UIKit.UIBarButtonItem sender);
+
+ void ReleaseDesignerOutlets ()
+ {
+ if (AddBarButton != null) {
+ AddBarButton.Dispose ();
+ AddBarButton = null;
+ }
+
+ if (CancelBarButton != null) {
+ CancelBarButton.Dispose ();
+ CancelBarButton = null;
+ }
+
+ if (MainView != null) {
+ MainView.Dispose ();
+ MainView = null;
+ }
+
+ if (NavItem != null) {
+ NavItem.Dispose ();
+ NavItem = null;
+ }
+
+ if (OverlayView != null) {
+ OverlayView.Dispose ();
+ OverlayView = null;
+ }
+
+ if (TableView != null) {
+ TableView.Dispose ();
+ TableView = null;
+ }
+
+ if (AccountSwitchingBarButton != null) {
+ AccountSwitchingBarButton.Dispose ();
+ AccountSwitchingBarButton = null;
+ }
+ }
+ }
+}
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/Main.cs b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/Main.cs
new file mode 100644
index 000000000..8289b7cd3
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/Main.cs
@@ -0,0 +1,12 @@
+using UIKit;
+
+namespace Bit.iOS.Autofill
+{
+ public class Application
+ {
+ static void Main(string[] args)
+ {
+ UIApplication.Main(args, null, "AppDelegate");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/MainInterface.storyboard b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/MainInterface.storyboard
new file mode 100644
index 000000000..e72b51bca
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/MainInterface.storyboard
@@ -0,0 +1,296 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/MauiContextSingleton.cs b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/MauiContextSingleton.cs
new file mode 100644
index 000000000..9b6d8c954
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/MauiContextSingleton.cs
@@ -0,0 +1,20 @@
+using System;
+using Microsoft.Maui;
+
+namespace IOSExtensionSample
+{
+
+ public class MauiContextSingleton
+ {
+ private static readonly Lazy _instance = new Lazy(() => new MauiContextSingleton());
+
+ public static MauiContextSingleton Instance => _instance.Value;
+
+ private MauiContextSingleton() { }
+
+ public MauiContext? MauiContext { get; private set; }
+
+ public void Init(MauiContext mauiContext) => MauiContext = mauiContext;
+ }
+}
+
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/SetupViewController.cs b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/SetupViewController.cs
new file mode 100644
index 000000000..0d9c36cba
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/SetupViewController.cs
@@ -0,0 +1,28 @@
+using System;
+using UIKit;
+
+namespace Bit.iOS.Autofill
+{
+ public partial class SetupViewController : UIViewController
+ {
+ public Action DismissModalAction { get; set; }
+
+ public SetupViewController(IntPtr handle)
+ : base(handle)
+ {
+ DismissModalAction = Cancel;
+ }
+
+ public CredentialProviderViewController CPViewController { get; set; }
+
+ partial void BackButton_Activated(UIBarButtonItem sender)
+ {
+ Cancel();
+ }
+
+ private void Cancel()
+ {
+ CPViewController.CompleteRequest();
+ }
+ }
+}
diff --git a/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/SetupViewController.designer.cs b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/SetupViewController.designer.cs
new file mode 100644
index 000000000..052173cec
--- /dev/null
+++ b/src/Samples/EmbeddedIOSNavigationIssue/IOSExtensionSample/SetupViewController.designer.cs
@@ -0,0 +1,69 @@
+// WARNING
+//
+// This file has been generated automatically by Visual Studio from the outlets and
+// actions declared in your storyboard file.
+// Manual changes to this file will not be maintained.
+//
+using Foundation;
+using System;
+using System.CodeDom.Compiler;
+using UIKit;
+
+namespace Bit.iOS.Autofill
+{
+ [Register ("SetupViewController")]
+ partial class SetupViewController
+ {
+ [Outlet]
+ [GeneratedCode ("iOS Designer", "1.0")]
+ UIKit.UILabel ActivatedLabel { get; set; }
+
+ [Outlet]
+ [GeneratedCode ("iOS Designer", "1.0")]
+ UIKit.UIBarButtonItem BackButton { get; set; }
+
+ [Outlet]
+ [GeneratedCode ("iOS Designer", "1.0")]
+ UIKit.UILabel DescriptionLabel { get; set; }
+
+ [Outlet]
+ [GeneratedCode ("iOS Designer", "1.0")]
+ UIKit.UIImageView IconImage { get; set; }
+
+ [Outlet]
+ [GeneratedCode ("iOS Designer", "1.0")]
+ UIKit.UINavigationItem NavItem { get; set; }
+
+ [Action ("BackButton_Activated:")]
+ [GeneratedCode ("iOS Designer", "1.0")]
+ partial void BackButton_Activated (UIKit.UIBarButtonItem sender);
+
+ void ReleaseDesignerOutlets ()
+ {
+ if (ActivatedLabel != null) {
+ ActivatedLabel.Dispose ();
+ ActivatedLabel = null;
+ }
+
+ if (BackButton != null) {
+ BackButton.Dispose ();
+ BackButton = null;
+ }
+
+ if (DescriptionLabel != null) {
+ DescriptionLabel.Dispose ();
+ DescriptionLabel = null;
+ }
+
+ if (IconImage != null) {
+ IconImage.Dispose ();
+ IconImage = null;
+ }
+
+ if (NavItem != null) {
+ NavItem.Dispose ();
+ NavItem = null;
+ }
+ }
+ }
+}
\ No newline at end of file