diff --git a/Firebase.Auth.sln b/Firebase.Auth.sln
index 742b844..5828da2 100644
--- a/Firebase.Auth.sln
+++ b/Firebase.Auth.sln
@@ -47,6 +47,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Auth.UI.WinUI3", "src\Auth.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Auth.WinUI3.Sample", "samples\WinUI3\Auth.WinUI3.Sample.csproj", "{4D7E8D5F-9DB9-46B3-99F3-2D768BBAA92D}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Auth.UI.MAUI", "src\Auth.UI.MAUI\Auth.UI.MAUI.csproj", "{08B76970-ACB8-4823-AEEA-4E2F32CADD47}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Auth.MAUI.Sample", "samples\MAUI\Auth.MAUI.Sample.csproj", "{249C7854-09D3-4137-BFF1-D32FE972488A}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -299,6 +303,56 @@ Global
{4D7E8D5F-9DB9-46B3-99F3-2D768BBAA92D}.Release|x86.ActiveCfg = Release|x86
{4D7E8D5F-9DB9-46B3-99F3-2D768BBAA92D}.Release|x86.Build.0 = Release|x86
{4D7E8D5F-9DB9-46B3-99F3-2D768BBAA92D}.Release|x86.Deploy.0 = Release|x86
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Debug|ARM.Build.0 = Debug|Any CPU
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Debug|x64.Build.0 = Debug|Any CPU
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Debug|x86.Build.0 = Debug|Any CPU
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Release|Any CPU.Build.0 = Release|Any CPU
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Release|ARM.ActiveCfg = Release|Any CPU
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Release|ARM.Build.0 = Release|Any CPU
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Release|ARM64.Build.0 = Release|Any CPU
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Release|x64.ActiveCfg = Release|Any CPU
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Release|x64.Build.0 = Release|Any CPU
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Release|x86.ActiveCfg = Release|Any CPU
+ {08B76970-ACB8-4823-AEEA-4E2F32CADD47}.Release|x86.Build.0 = Release|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Debug|ARM.Build.0 = Debug|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Debug|ARM.Deploy.0 = Debug|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Debug|ARM64.Deploy.0 = Debug|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Debug|x64.Build.0 = Debug|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Debug|x64.Deploy.0 = Debug|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Debug|x86.Build.0 = Debug|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Debug|x86.Deploy.0 = Debug|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Release|Any CPU.Deploy.0 = Release|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Release|ARM.ActiveCfg = Release|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Release|ARM.Build.0 = Release|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Release|ARM.Deploy.0 = Release|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Release|ARM64.Build.0 = Release|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Release|ARM64.Deploy.0 = Release|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Release|x64.ActiveCfg = Release|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Release|x64.Build.0 = Release|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Release|x64.Deploy.0 = Release|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Release|x86.ActiveCfg = Release|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Release|x86.Build.0 = Release|Any CPU
+ {249C7854-09D3-4137-BFF1-D32FE972488A}.Release|x86.Deploy.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -310,6 +364,7 @@ Global
{31FAEF42-D294-4269-94B8-D5AE76CDF964} = {5528D6EA-402F-4FD9-9A01-0B799AFFC14B}
{69BFF1A9-8384-4DD8-BD23-31E384D57499} = {5528D6EA-402F-4FD9-9A01-0B799AFFC14B}
{4D7E8D5F-9DB9-46B3-99F3-2D768BBAA92D} = {CF8B7ECC-0E53-4B26-ABB3-0D74BDBB642F}
+ {249C7854-09D3-4137-BFF1-D32FE972488A} = {CF8B7ECC-0E53-4B26-ABB3-0D74BDBB642F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EBE8EC40-A988-4401-8C7E-61E1165C7D46}
diff --git a/build/build-libs.ps1 b/build/build-libs.ps1
index 3720dd2..176f299 100644
--- a/build/build-libs.ps1
+++ b/build/build-libs.ps1
@@ -2,4 +2,5 @@
dotnet build --configuration release .\src\Auth.UI\Auth.UI.csproj
dotnet build --configuration release .\src\Auth.UI.WPF\Auth.UI.WPF.csproj
dotnet build --configuration release .\src\Auth.UI.WinUI3\Auth.UI.WinUI3.csproj
-msbuild /restore /p:Configuration=Release .\src\Auth.UI.UWP\Auth.UI.UWP.csproj
\ No newline at end of file
+msbuild /restore /p:Configuration=Release .\src\Auth.UI.UWP\Auth.UI.UWP.csproj
+dotnet build --configuration release .\src\Auth.UI.MAUI\Auth.UI.MAUI.csproj
\ No newline at end of file
diff --git a/build/build-samples.ps1 b/build/build-samples.ps1
index adb0509..774b282 100644
--- a/build/build-samples.ps1
+++ b/build/build-samples.ps1
@@ -1,4 +1,5 @@
dotnet build --configuration release .\samples\Console\Auth.Console.Sample.csproj
dotnet build --configuration release .\samples\WPF\Auth.WPF.Sample.csproj
dotnet build --configuration release .\samples\WinUI3\Auth.WinUI3.Sample.csproj
-msbuild /restore /p:Configuration=Debug .\samples\UWP\Auth.UWP.Sample.csproj
\ No newline at end of file
+msbuild /restore /p:Configuration=Debug .\samples\UWP\Auth.UWP.Sample.csproj
+dotnet build --configuration release .\samples\MAUI\Auth.MAUI.Sample.csproj
\ No newline at end of file
diff --git a/build/run-pack.ps1 b/build/run-pack.ps1
index f5d89ef..62a4b50 100644
--- a/build/run-pack.ps1
+++ b/build/run-pack.ps1
@@ -9,6 +9,7 @@ if ($preview) {
dotnet pack --configuration release --output $output --version-suffix=$suffix .\src\Auth.UI.WPF\Auth.UI.WPF.csproj
dotnet pack --configuration release --output $output --version-suffix=$suffix .\src\Auth.UI.WinUI3\Auth.UI.WinUI3.csproj
msbuild /t:restore,pack /p:Configuration=Release /p:PackageOutputPath=$output /p:VersionSuffix=$suffix .\src\Auth.UI.UWP\Auth.UI.UWP.csproj
+ dotnet pack --configuration release --output $output --version-suffix=$suffix .\src\Auth.UI.MAUI\Auth.UI.MAUI.csproj
} else {
$version = $(git describe --tags --abbrev=0).substring(1)
write "Creating packages with tag version $version"
@@ -16,4 +17,5 @@ if ($preview) {
dotnet pack --configuration release --output $output -p:version=$version .\src\Auth.UI.WPF\Auth.UI.WPF.csproj
dotnet pack --configuration release --output $output -p:version=$version .\src\Auth.UI.WinUI3\Auth.UI.WinUI3.csproj
msbuild /t:restore,pack /p:Configuration=Release /p:PackageOutputPath=$output /p:Version=$version .\src\Auth.UI.UWP\Auth.UI.UWP.csproj
+ dotnet pack --configuration release --output $output -p:version=$version .\src\Auth.UI.MAUI\Auth.UI.MAUI.csproj
}
\ No newline at end of file
diff --git a/samples/MAUI/App.xaml b/samples/MAUI/App.xaml
new file mode 100644
index 0000000..4a49538
--- /dev/null
+++ b/samples/MAUI/App.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/MAUI/App.xaml.cs b/samples/MAUI/App.xaml.cs
new file mode 100644
index 0000000..fef1016
--- /dev/null
+++ b/samples/MAUI/App.xaml.cs
@@ -0,0 +1,47 @@
+using Firebase.Auth;
+using Firebase.Auth.UI;
+using Firebase.Auth.UI.MAUI;
+
+namespace Auth.MAUI.Sample
+{
+ public partial class App : Application
+ {
+ public App()
+ {
+ InitializeComponent();
+ // Use Direct Navigation
+ UseDirectNavigation();
+ // Use APP Shell
+ //MainPage = new AppShell();
+ // Use Navigation Page
+ //MainPage = new AppNavigation();
+ }
+
+ private void UseDirectNavigation()
+ {
+ Router.RegisterMainType(Router.NavigationModeEnum.Direct);
+ MainPage = new LoginPage();
+ FirebaseUI.Instance.Client.AuthStateChanged += this.AuthStateChanged;
+ }
+ private void AuthStateChanged(object sender, UserEventArgs e)
+ {
+ Application.Current.Dispatcher.DispatchAsync(async () =>
+ {
+ if (e.User == null)
+ {
+ if (this.MainPage?.GetType() != Router.MainType)
+ await Router.NavigateToMain();
+ }
+ else if (e.User.IsAnonymous)
+ {
+ if (this.MainPage?.GetType() != typeof(MainPage))
+ MainPage = new MainPage();
+ }
+ else if (this.MainPage?.GetType() != typeof(MainPage))
+ {
+ MainPage = new MainPage();
+ }
+ });
+ }
+ }
+}
diff --git a/samples/MAUI/AppNavigation.xaml b/samples/MAUI/AppNavigation.xaml
new file mode 100644
index 0000000..3874c6e
--- /dev/null
+++ b/samples/MAUI/AppNavigation.xaml
@@ -0,0 +1,6 @@
+
+
+
\ No newline at end of file
diff --git a/samples/MAUI/AppNavigation.xaml.cs b/samples/MAUI/AppNavigation.xaml.cs
new file mode 100644
index 0000000..684d5da
--- /dev/null
+++ b/samples/MAUI/AppNavigation.xaml.cs
@@ -0,0 +1,39 @@
+using Firebase.Auth;
+using Firebase.Auth.UI;
+using Firebase.Auth.UI.MAUI;
+
+namespace Auth.MAUI.Sample;
+
+public partial class AppNavigation : NavigationPage
+{
+ public AppNavigation()
+ {
+ Router.RegisterMainType(Router.NavigationModeEnum.Stack);
+ InitializeComponent();
+
+ FirebaseUI.Instance.Client.AuthStateChanged += this.AuthStateChanged;
+ }
+
+ private void AuthStateChanged(object sender, UserEventArgs e)
+ {
+ Application.Current.Dispatcher.DispatchAsync(async () =>
+ {
+ Shell.SetBackButtonBehavior(this, new BackButtonBehavior() { IsVisible = false });
+
+ if (e.User == null)
+ {
+ if (this.Navigation.NavigationStack.LastOrDefault()?.GetType() != Router.MainType)
+ await Router.NavigateToMain();
+ }
+ else if (e.User.IsAnonymous)
+ {
+ if (this.Navigation.NavigationStack.LastOrDefault()?.GetType() != typeof(MainPage))
+ await Router.NavigateToMain();
+ }
+ else if (this.Navigation.NavigationStack.LastOrDefault()?.GetType() != typeof(MainPage))
+ {
+ await this.Navigation.PushAsync(new MainPage());
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/samples/MAUI/AppShell.xaml b/samples/MAUI/AppShell.xaml
new file mode 100644
index 0000000..fd8efbe
--- /dev/null
+++ b/samples/MAUI/AppShell.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/samples/MAUI/AppShell.xaml.cs b/samples/MAUI/AppShell.xaml.cs
new file mode 100644
index 0000000..a53e44b
--- /dev/null
+++ b/samples/MAUI/AppShell.xaml.cs
@@ -0,0 +1,39 @@
+using Firebase.Auth.UI;
+using Firebase.Auth;
+using Firebase.Auth.UI.MAUI;
+
+namespace Auth.MAUI.Sample
+{
+ public partial class AppShell : Shell
+ {
+ public AppShell()
+ {
+ Router.RegisterShellRoutes();
+ Router.RegisterMainType(Router.NavigationModeEnum.StackModal);
+ InitializeComponent();
+
+ FirebaseUI.Instance.Client.AuthStateChanged += this.AuthStateChanged;
+ }
+
+ private void AuthStateChanged(object sender, UserEventArgs e)
+ {
+ Application.Current.Dispatcher.DispatchAsync(async () =>
+ {
+ if (e.User == null)
+ {
+ if (this.CurrentPage.GetType() != Router.MainType)
+ await Router.NavigateToMain();
+ }
+ else if (e.User.IsAnonymous)
+ {
+ if (this.CurrentPage.GetType() != typeof(MainPage))
+ await GoToAsync(nameof(MainPage));
+ }
+ else if (this.CurrentPage.GetType() != typeof(MainPage))
+ {
+ await GoToAsync(nameof(MainPage));
+ }
+ });
+ }
+ }
+}
diff --git a/samples/MAUI/Auth.MAUI.Sample.csproj b/samples/MAUI/Auth.MAUI.Sample.csproj
new file mode 100644
index 0000000..4353afd
--- /dev/null
+++ b/samples/MAUI/Auth.MAUI.Sample.csproj
@@ -0,0 +1,77 @@
+
+
+
+ net8.0-android;net8.0-ios;net8.0-maccatalyst
+ $(TargetFrameworks);net8.0-windows10.0.19041.0
+
+
+
+
+
+
+ Exe
+ Auth.MAUI.Sample
+ true
+ true
+ enable
+
+
+ Auth.MAUI.Sample
+
+
+ com.companyname.auth.maui.sample
+
+
+ 1.0
+ 1
+
+ 11.0
+ 13.1
+ 21.0
+ 10.0.17763.0
+ 10.0.17763.0
+ 6.5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+
+
diff --git a/samples/MAUI/LoginPage.xaml b/samples/MAUI/LoginPage.xaml
new file mode 100644
index 0000000..13b02a9
--- /dev/null
+++ b/samples/MAUI/LoginPage.xaml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/MAUI/LoginPage.xaml.cs b/samples/MAUI/LoginPage.xaml.cs
new file mode 100644
index 0000000..2af5580
--- /dev/null
+++ b/samples/MAUI/LoginPage.xaml.cs
@@ -0,0 +1,9 @@
+namespace Auth.MAUI.Sample;
+
+public partial class LoginPage : ContentPage
+{
+ public LoginPage()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/samples/MAUI/MainPage.xaml b/samples/MAUI/MainPage.xaml
new file mode 100644
index 0000000..ae81c58
--- /dev/null
+++ b/samples/MAUI/MainPage.xaml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/MAUI/MainPage.xaml.cs b/samples/MAUI/MainPage.xaml.cs
new file mode 100644
index 0000000..0e4a808
--- /dev/null
+++ b/samples/MAUI/MainPage.xaml.cs
@@ -0,0 +1,40 @@
+using Firebase.Auth;
+using Firebase.Auth.UI;
+
+namespace Auth.MAUI.Sample
+{
+ public partial class MainPage : ContentPage
+ {
+ public MainPage()
+ {
+ InitializeComponent();
+
+ FirebaseUI.Instance.Client.AuthStateChanged += this.AuthStateChanged;
+ }
+
+ private void AuthStateChanged(object sender, UserEventArgs e)
+ {
+ var user = e.User;
+
+ Application.Current.Dispatcher.DispatchAsync(() =>
+ {
+ this.UidTextBlock.Text = user?.Uid;
+ this.NameTextBlock.Text = user?.Info?.DisplayName;
+ this.EmailTextBlock.Text = user?.Info?.Email;
+ this.ProviderTextBlock.Text = user?.Credential?.ProviderType.ToString();
+
+ if (!string.IsNullOrWhiteSpace(user?.Info?.PhotoUrl))
+ {
+ this.ProfileImage.Source = new Uri(user.Info.PhotoUrl);
+ }
+ });
+ }
+
+ private void SignOutClick(object sender, EventArgs e)
+ {
+ FirebaseUI.Instance.Client.AuthStateChanged -= this.AuthStateChanged;
+ FirebaseUI.Instance.Client.SignOut();
+ }
+ }
+
+}
diff --git a/samples/MAUI/MauiProgram.cs b/samples/MAUI/MauiProgram.cs
new file mode 100644
index 0000000..e7a7d27
--- /dev/null
+++ b/samples/MAUI/MauiProgram.cs
@@ -0,0 +1,59 @@
+using Firebase.Auth.Providers;
+using Firebase.Auth.Repository;
+using Firebase.Auth.UI;
+using Microsoft.Extensions.Logging;
+
+namespace Auth.MAUI.Sample
+{
+ public static class MauiProgram
+ {
+ public static MauiApp CreateMauiApp()
+ {
+ // Force override culture & language
+ //CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("cs");
+ //CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("cs");
+
+ // Firebase UI initialization
+ FirebaseUI.Initialize(new FirebaseUIConfig
+ {
+ ApiKey = "",
+ AuthDomain = ".firebaseapp.com",
+ Providers = new FirebaseAuthProvider[]
+ {
+ new GoogleProvider(),
+ new FacebookProvider(),
+ new AppleProvider(),
+ new TwitterProvider(),
+ new GithubProvider(),
+ new MicrosoftProvider(),
+ new EmailProvider()
+ },
+ PrivacyPolicyUrl = "https://github.com/step-up-labs/firebase-authentication-dotnet",
+ TermsOfServiceUrl = "https://github.com/step-up-labs/firebase-database-dotnet",
+ IsAnonymousAllowed = true,
+ AutoUpgradeAnonymousUsers = true,
+ UserRepository = new FileUserRepository("FirebaseSample"),
+ // Func called when upgrade of anonymous user fails because the user already exists
+ // You should grab any data created under your anonymous user, sign in with the pending credential
+ // and copy the existing data to the new user
+ // see details here: https://github.com/firebase/firebaseui-web#upgrading-anonymous-users
+ AnonymousUpgradeConflict = conflict => conflict.SignInWithPendingCredentialAsync(true)
+ });
+
+ var builder = MauiApp.CreateBuilder();
+ builder
+ .UseMauiApp()
+ .ConfigureFonts(fonts =>
+ {
+ fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
+ fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
+ });
+
+#if DEBUG
+ builder.Logging.AddDebug();
+#endif
+
+ return builder.Build();
+ }
+ }
+}
diff --git a/samples/MAUI/Platforms/Android/AndroidManifest.xml b/samples/MAUI/Platforms/Android/AndroidManifest.xml
new file mode 100644
index 0000000..e9937ad
--- /dev/null
+++ b/samples/MAUI/Platforms/Android/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/MAUI/Platforms/Android/MainActivity.cs b/samples/MAUI/Platforms/Android/MainActivity.cs
new file mode 100644
index 0000000..ff45809
--- /dev/null
+++ b/samples/MAUI/Platforms/Android/MainActivity.cs
@@ -0,0 +1,11 @@
+using Android.App;
+using Android.Content.PM;
+using Android.OS;
+
+namespace Auth.MAUI.Sample
+{
+ [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/samples/MAUI/Platforms/Android/MainApplication.cs b/samples/MAUI/Platforms/Android/MainApplication.cs
new file mode 100644
index 0000000..2f5bf9c
--- /dev/null
+++ b/samples/MAUI/Platforms/Android/MainApplication.cs
@@ -0,0 +1,16 @@
+using Android.App;
+using Android.Runtime;
+
+namespace Auth.MAUI.Sample
+{
+ [Application]
+ public class MainApplication : MauiApplication
+ {
+ public MainApplication(IntPtr handle, JniHandleOwnership ownership)
+ : base(handle, ownership)
+ {
+ }
+
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+ }
+}
diff --git a/samples/MAUI/Platforms/Android/Resources/values/colors.xml b/samples/MAUI/Platforms/Android/Resources/values/colors.xml
new file mode 100644
index 0000000..c04d749
--- /dev/null
+++ b/samples/MAUI/Platforms/Android/Resources/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #512BD4
+ #2B0B98
+ #2B0B98
+
\ No newline at end of file
diff --git a/samples/MAUI/Platforms/MacCatalyst/AppDelegate.cs b/samples/MAUI/Platforms/MacCatalyst/AppDelegate.cs
new file mode 100644
index 0000000..df36df1
--- /dev/null
+++ b/samples/MAUI/Platforms/MacCatalyst/AppDelegate.cs
@@ -0,0 +1,10 @@
+using Foundation;
+
+namespace Auth.MAUI.Sample
+{
+ [Register("AppDelegate")]
+ public class AppDelegate : MauiUIApplicationDelegate
+ {
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+ }
+}
diff --git a/samples/MAUI/Platforms/MacCatalyst/Entitlements.plist b/samples/MAUI/Platforms/MacCatalyst/Entitlements.plist
new file mode 100644
index 0000000..de4adc9
--- /dev/null
+++ b/samples/MAUI/Platforms/MacCatalyst/Entitlements.plist
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ com.apple.security.app-sandbox
+
+
+ com.apple.security.network.client
+
+
+
+
diff --git a/samples/MAUI/Platforms/MacCatalyst/Info.plist b/samples/MAUI/Platforms/MacCatalyst/Info.plist
new file mode 100644
index 0000000..7268977
--- /dev/null
+++ b/samples/MAUI/Platforms/MacCatalyst/Info.plist
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UIDeviceFamily
+
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/appicon.appiconset
+
+
diff --git a/samples/MAUI/Platforms/MacCatalyst/Program.cs b/samples/MAUI/Platforms/MacCatalyst/Program.cs
new file mode 100644
index 0000000..3f0aff2
--- /dev/null
+++ b/samples/MAUI/Platforms/MacCatalyst/Program.cs
@@ -0,0 +1,16 @@
+using ObjCRuntime;
+using UIKit;
+
+namespace Auth.MAUI.Sample
+{
+ 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/samples/MAUI/Platforms/Tizen/Main.cs b/samples/MAUI/Platforms/Tizen/Main.cs
new file mode 100644
index 0000000..8a27e9b
--- /dev/null
+++ b/samples/MAUI/Platforms/Tizen/Main.cs
@@ -0,0 +1,17 @@
+using Microsoft.Maui;
+using Microsoft.Maui.Hosting;
+using System;
+
+namespace Auth.MAUI.Sample
+{
+ internal class Program : MauiApplication
+ {
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+
+ static void Main(string[] args)
+ {
+ var app = new Program();
+ app.Run(args);
+ }
+ }
+}
diff --git a/samples/MAUI/Platforms/Tizen/tizen-manifest.xml b/samples/MAUI/Platforms/Tizen/tizen-manifest.xml
new file mode 100644
index 0000000..ce3b41c
--- /dev/null
+++ b/samples/MAUI/Platforms/Tizen/tizen-manifest.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ maui-appicon-placeholder
+
+
+
+
+ http://tizen.org/privilege/internet
+
+
+
+
\ No newline at end of file
diff --git a/samples/MAUI/Platforms/Windows/App.xaml b/samples/MAUI/Platforms/Windows/App.xaml
new file mode 100644
index 0000000..9fa42ee
--- /dev/null
+++ b/samples/MAUI/Platforms/Windows/App.xaml
@@ -0,0 +1,8 @@
+
+
+
diff --git a/samples/MAUI/Platforms/Windows/App.xaml.cs b/samples/MAUI/Platforms/Windows/App.xaml.cs
new file mode 100644
index 0000000..60865c4
--- /dev/null
+++ b/samples/MAUI/Platforms/Windows/App.xaml.cs
@@ -0,0 +1,25 @@
+using Microsoft.UI.Xaml;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace Auth.MAUI.Sample.WinUI
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ public partial class App : MauiWinUIApplication
+ {
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+ }
+
+}
diff --git a/samples/MAUI/Platforms/Windows/Package.appxmanifest b/samples/MAUI/Platforms/Windows/Package.appxmanifest
new file mode 100644
index 0000000..d20f02d
--- /dev/null
+++ b/samples/MAUI/Platforms/Windows/Package.appxmanifest
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+ $placeholder$
+ User Name
+ $placeholder$.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/MAUI/Platforms/Windows/app.manifest b/samples/MAUI/Platforms/Windows/app.manifest
new file mode 100644
index 0000000..b47dad0
--- /dev/null
+++ b/samples/MAUI/Platforms/Windows/app.manifest
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+ true/PM
+ PerMonitorV2, PerMonitor
+
+
+
diff --git a/samples/MAUI/Platforms/iOS/AppDelegate.cs b/samples/MAUI/Platforms/iOS/AppDelegate.cs
new file mode 100644
index 0000000..df36df1
--- /dev/null
+++ b/samples/MAUI/Platforms/iOS/AppDelegate.cs
@@ -0,0 +1,10 @@
+using Foundation;
+
+namespace Auth.MAUI.Sample
+{
+ [Register("AppDelegate")]
+ public class AppDelegate : MauiUIApplicationDelegate
+ {
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+ }
+}
diff --git a/samples/MAUI/Platforms/iOS/Info.plist b/samples/MAUI/Platforms/iOS/Info.plist
new file mode 100644
index 0000000..0004a4f
--- /dev/null
+++ b/samples/MAUI/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/samples/MAUI/Platforms/iOS/Program.cs b/samples/MAUI/Platforms/iOS/Program.cs
new file mode 100644
index 0000000..3f0aff2
--- /dev/null
+++ b/samples/MAUI/Platforms/iOS/Program.cs
@@ -0,0 +1,16 @@
+using ObjCRuntime;
+using UIKit;
+
+namespace Auth.MAUI.Sample
+{
+ 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/samples/MAUI/Properties/launchSettings.json b/samples/MAUI/Properties/launchSettings.json
new file mode 100644
index 0000000..edf8aad
--- /dev/null
+++ b/samples/MAUI/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "Windows Machine": {
+ "commandName": "MsixPackage",
+ "nativeDebugging": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/MAUI/Resources/AppIcon/appicon.svg b/samples/MAUI/Resources/AppIcon/appicon.svg
new file mode 100644
index 0000000..9d63b65
--- /dev/null
+++ b/samples/MAUI/Resources/AppIcon/appicon.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/samples/MAUI/Resources/AppIcon/appiconfg.svg b/samples/MAUI/Resources/AppIcon/appiconfg.svg
new file mode 100644
index 0000000..21dfb25
--- /dev/null
+++ b/samples/MAUI/Resources/AppIcon/appiconfg.svg
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/samples/MAUI/Resources/Fonts/OpenSans-Regular.ttf b/samples/MAUI/Resources/Fonts/OpenSans-Regular.ttf
new file mode 100644
index 0000000..3b69f73
Binary files /dev/null and b/samples/MAUI/Resources/Fonts/OpenSans-Regular.ttf differ
diff --git a/samples/MAUI/Resources/Fonts/OpenSans-Semibold.ttf b/samples/MAUI/Resources/Fonts/OpenSans-Semibold.ttf
new file mode 100644
index 0000000..0fc5a9a
Binary files /dev/null and b/samples/MAUI/Resources/Fonts/OpenSans-Semibold.ttf differ
diff --git a/samples/MAUI/Resources/Images/dotnet_bot.png b/samples/MAUI/Resources/Images/dotnet_bot.png
new file mode 100644
index 0000000..f93ce02
Binary files /dev/null and b/samples/MAUI/Resources/Images/dotnet_bot.png differ
diff --git a/samples/MAUI/Resources/Raw/AboutAssets.txt b/samples/MAUI/Resources/Raw/AboutAssets.txt
new file mode 100644
index 0000000..15d6244
--- /dev/null
+++ b/samples/MAUI/Resources/Raw/AboutAssets.txt
@@ -0,0 +1,15 @@
+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/samples/MAUI/Resources/Splash/splash.svg b/samples/MAUI/Resources/Splash/splash.svg
new file mode 100644
index 0000000..21dfb25
--- /dev/null
+++ b/samples/MAUI/Resources/Splash/splash.svg
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/samples/MAUI/Resources/Styles/Colors.xaml b/samples/MAUI/Resources/Styles/Colors.xaml
new file mode 100644
index 0000000..30307a5
--- /dev/null
+++ b/samples/MAUI/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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/MAUI/Resources/Styles/Styles.xaml b/samples/MAUI/Resources/Styles/Styles.xaml
new file mode 100644
index 0000000..e0d36bb
--- /dev/null
+++ b/samples/MAUI/Resources/Styles/Styles.xaml
@@ -0,0 +1,426 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Auth.UI.MAUI/Assets/anonymous.png b/src/Auth.UI.MAUI/Assets/anonymous.png
new file mode 100644
index 0000000..8243108
Binary files /dev/null and b/src/Auth.UI.MAUI/Assets/anonymous.png differ
diff --git a/src/Auth.UI.MAUI/Assets/apple.png b/src/Auth.UI.MAUI/Assets/apple.png
new file mode 100644
index 0000000..35d331e
Binary files /dev/null and b/src/Auth.UI.MAUI/Assets/apple.png differ
diff --git a/src/Auth.UI.MAUI/Assets/facebook.png b/src/Auth.UI.MAUI/Assets/facebook.png
new file mode 100644
index 0000000..4e6dd71
Binary files /dev/null and b/src/Auth.UI.MAUI/Assets/facebook.png differ
diff --git a/src/Auth.UI.MAUI/Assets/firebase.png b/src/Auth.UI.MAUI/Assets/firebase.png
new file mode 100644
index 0000000..9857d91
Binary files /dev/null and b/src/Auth.UI.MAUI/Assets/firebase.png differ
diff --git a/src/Auth.UI.MAUI/Assets/github.png b/src/Auth.UI.MAUI/Assets/github.png
new file mode 100644
index 0000000..36edccd
Binary files /dev/null and b/src/Auth.UI.MAUI/Assets/github.png differ
diff --git a/src/Auth.UI.MAUI/Assets/google.png b/src/Auth.UI.MAUI/Assets/google.png
new file mode 100644
index 0000000..2ba42f3
Binary files /dev/null and b/src/Auth.UI.MAUI/Assets/google.png differ
diff --git a/src/Auth.UI.MAUI/Assets/mail.png b/src/Auth.UI.MAUI/Assets/mail.png
new file mode 100644
index 0000000..39f53eb
Binary files /dev/null and b/src/Auth.UI.MAUI/Assets/mail.png differ
diff --git a/src/Auth.UI.MAUI/Assets/microsoft.png b/src/Auth.UI.MAUI/Assets/microsoft.png
new file mode 100644
index 0000000..385e3d6
Binary files /dev/null and b/src/Auth.UI.MAUI/Assets/microsoft.png differ
diff --git a/src/Auth.UI.MAUI/Assets/twitter.png b/src/Auth.UI.MAUI/Assets/twitter.png
new file mode 100644
index 0000000..38edd23
Binary files /dev/null and b/src/Auth.UI.MAUI/Assets/twitter.png differ
diff --git a/src/Auth.UI.MAUI/Auth.UI.MAUI.csproj b/src/Auth.UI.MAUI/Auth.UI.MAUI.csproj
new file mode 100644
index 0000000..c57d389
--- /dev/null
+++ b/src/Auth.UI.MAUI/Auth.UI.MAUI.csproj
@@ -0,0 +1,82 @@
+
+
+
+ net8.0-android;net8.0-ios;net8.0-maccatalyst
+ $(TargetFrameworks);net8.0-windows10.0.19041.0
+
+
+ true
+ true
+ enable
+
+ 11.0
+ 13.1
+ 21.0
+ 10.0.17763.0
+ 10.0.17763.0
+ 6.5
+ Firebase.$(MSBuildProjectName.Replace(" ", "_"))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+
+
diff --git a/src/Auth.UI.MAUI/Components/ContentPage.cs b/src/Auth.UI.MAUI/Components/ContentPage.cs
new file mode 100644
index 0000000..bfbd334
--- /dev/null
+++ b/src/Auth.UI.MAUI/Components/ContentPage.cs
@@ -0,0 +1,10 @@
+namespace Firebase.Auth.UI.MAUI.Components;
+
+public class ContentPage : ContentPage where TView : View
+{
+ public TView View { get => Content as TView; }
+ public ContentPage(TView content)
+ {
+ Content = content;
+ }
+}
\ No newline at end of file
diff --git a/src/Auth.UI.MAUI/Components/HyperlinkSpan.cs b/src/Auth.UI.MAUI/Components/HyperlinkSpan.cs
new file mode 100644
index 0000000..52597cf
--- /dev/null
+++ b/src/Auth.UI.MAUI/Components/HyperlinkSpan.cs
@@ -0,0 +1,77 @@
+namespace Firebase.Auth.UI.MAUI.Components;
+
+public class HyperlinkSpan : Span
+{
+ public static readonly BindableProperty UrlProperty = BindableProperty.Create(nameof(Url), typeof(string), typeof(HyperlinkSpan), null);
+ public static readonly BindableProperty ClickProperty = BindableProperty.Create(nameof(Click), typeof(Func), typeof(HyperlinkSpan), null);
+
+ public string Url
+ {
+ get { return (string)GetValue(UrlProperty); }
+ set { SetValue(UrlProperty, value); }
+ }
+
+ public Func Click
+ {
+ get { return (Func)GetValue(ClickProperty); }
+ set { SetValue(ClickProperty, value); }
+ }
+
+ public HyperlinkSpan()
+ {
+ TextColor = Application.Current.RequestedTheme == AppTheme.Dark ? Colors.SteelBlue : Colors.Blue;
+ TextDecorations = TextDecorations.Underline;
+
+ GestureRecognizers.Add(new TapGestureRecognizer {
+ Command = new Command(async () => await (Click == null ? Launcher.LaunchUriAsync(Url) : Click()))
+ });
+ //GestureRecognizers.Add(new PointerGestureRecognizer
+ //{
+ // PointerReleasedCommand = new Command(async () => await Launcher.LaunchUriAsync(Url)),
+ // PointerEnteredCommand = new Command(() => TextColor = Colors.Red),
+ // PointerExitedCommand = new Command(() => TextColor = Application.Current.RequestedTheme == AppTheme.Dark ? Colors.SteelBlue : Colors.Blue),
+ //});
+ }
+}
+
+public class Hyperlink : Label
+{
+ public static readonly BindableProperty UrlProperty = BindableProperty.Create(nameof(Url), typeof(string), typeof(Hyperlink), null);
+ public static readonly BindableProperty ClickProperty = BindableProperty.Create(nameof(Click), typeof(Func), typeof(Hyperlink), null);
+ public static readonly BindableProperty ActiveTextColorProperty = BindableProperty.Create(nameof(ActiveTextColor), typeof(Color), typeof(Hyperlink), Colors.Red);
+
+ public string Url
+ {
+ get { return (string)GetValue(UrlProperty); }
+ set { SetValue(UrlProperty, value); }
+ }
+
+ public Func Click
+ {
+ get { return (Func)GetValue(ClickProperty); }
+ set { SetValue(ClickProperty, value); }
+ }
+ public Color ActiveTextColor
+ {
+ get { return (Color)GetValue(ActiveTextColorProperty); }
+ set { SetValue(ActiveTextColorProperty, value); }
+ }
+
+ public Hyperlink()
+ {
+ TextColor = Application.Current.RequestedTheme == AppTheme.Dark ? Colors.SteelBlue : Colors.Blue;
+ TextDecorations = TextDecorations.Underline;
+
+ GestureRecognizers.Add(new TapGestureRecognizer
+ {
+ Command = new Command(async () => await (Click == null ? Launcher.LaunchUriAsync(Url) : Click()))
+ });
+
+ GestureRecognizers.Add(new PointerGestureRecognizer
+ {
+ //PointerPressedCommand
+ PointerEnteredCommand = new Command(() => TextColor = ActiveTextColor),
+ PointerExitedCommand = new Command(() => TextColor = Application.Current.RequestedTheme == AppTheme.Dark ? Colors.SteelBlue : Colors.Blue),
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/Auth.UI.MAUI/Components/LinksFooter.xaml b/src/Auth.UI.MAUI/Components/LinksFooter.xaml
new file mode 100644
index 0000000..58db1b2
--- /dev/null
+++ b/src/Auth.UI.MAUI/Components/LinksFooter.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/src/Auth.UI.MAUI/Components/LinksFooter.xaml.cs b/src/Auth.UI.MAUI/Components/LinksFooter.xaml.cs
new file mode 100644
index 0000000..ee3f7dc
--- /dev/null
+++ b/src/Auth.UI.MAUI/Components/LinksFooter.xaml.cs
@@ -0,0 +1,15 @@
+namespace Firebase.Auth.UI.MAUI.Components;
+
+public partial class LinksFooter : ContentView
+{
+ public string TermsOfServiceUrl { get; set; }
+ public string PrivacyPolicyUrl { get; set; }
+
+ public LinksFooter()
+ {
+ TermsOfServiceUrl = FirebaseUI.Instance.Config.TermsOfServiceUrl;
+ PrivacyPolicyUrl = FirebaseUI.Instance.Config.PrivacyPolicyUrl;
+
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/src/Auth.UI.MAUI/Converters/AssetConverter.cs b/src/Auth.UI.MAUI/Converters/AssetConverter.cs
new file mode 100644
index 0000000..abb8ee8
--- /dev/null
+++ b/src/Auth.UI.MAUI/Converters/AssetConverter.cs
@@ -0,0 +1,25 @@
+using System.Globalization;
+
+namespace Firebase.Auth.UI.MAUI.Converters
+{
+ public class AssetConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value == null) return null;
+
+ switch ((FirebaseProviderType)value)
+ {
+ case FirebaseProviderType.EmailAndPassword:
+ return "mail.png";
+ default:
+ return $"{value.ToString().ToLower()}.png";
+ }
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/Auth.UI.MAUI/Converters/BackgroundConverter.cs b/src/Auth.UI.MAUI/Converters/BackgroundConverter.cs
new file mode 100644
index 0000000..42f2dfe
--- /dev/null
+++ b/src/Auth.UI.MAUI/Converters/BackgroundConverter.cs
@@ -0,0 +1,34 @@
+using Firebase.Auth.UI.Converters;
+using System.Globalization;
+
+namespace Firebase.Auth.UI.MAUI.Converters
+{
+ public class BackgroundConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value == null) return "#000";
+ float aux = GetParameter(parameter);
+ //aux
+ var color = ProviderToBackgroundConverter.Convert((FirebaseProviderType)value);
+ return Color.FromArgb(color).WithAlpha(aux).ToArgbHex();
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+
+ float GetParameter(object parameter)
+ {
+ if (parameter is float)
+ return (float)parameter;
+ else if (parameter is int)
+ return (float)parameter;
+ else if (parameter is string)
+ return float.Parse((string)parameter, NumberStyles.Float, CultureInfo.InvariantCulture);
+
+ return 1;
+ }
+ }
+}
diff --git a/src/Auth.UI.MAUI/Converters/ForegroundConverter.cs b/src/Auth.UI.MAUI/Converters/ForegroundConverter.cs
new file mode 100644
index 0000000..3d87789
--- /dev/null
+++ b/src/Auth.UI.MAUI/Converters/ForegroundConverter.cs
@@ -0,0 +1,19 @@
+using Firebase.Auth.UI.Converters;
+using System.Globalization;
+
+namespace Firebase.Auth.UI.MAUI.Converters
+{
+ public class ForegroundConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value == null) return "#fff";
+ return ProviderToForegroundConverter.Convert((FirebaseProviderType)value);
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/Auth.UI.MAUI/Converters/TitleConverter.cs b/src/Auth.UI.MAUI/Converters/TitleConverter.cs
new file mode 100644
index 0000000..9272bc5
--- /dev/null
+++ b/src/Auth.UI.MAUI/Converters/TitleConverter.cs
@@ -0,0 +1,19 @@
+using Firebase.Auth.UI.Converters;
+using System.Globalization;
+
+namespace Firebase.Auth.UI.MAUI.Converters
+{
+ public class TitleConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value == null) return null;
+ return ProviderToTitleConverter.Convert((FirebaseProviderType)value);
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/Auth.UI.MAUI/FirebaseMainPage.xaml b/src/Auth.UI.MAUI/FirebaseMainPage.xaml
new file mode 100644
index 0000000..10aa83d
--- /dev/null
+++ b/src/Auth.UI.MAUI/FirebaseMainPage.xaml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Auth.UI.MAUI/FirebaseMainPage.xaml.cs b/src/Auth.UI.MAUI/FirebaseMainPage.xaml.cs
new file mode 100644
index 0000000..68f0b38
--- /dev/null
+++ b/src/Auth.UI.MAUI/FirebaseMainPage.xaml.cs
@@ -0,0 +1,9 @@
+namespace Firebase.Auth.UI.MAUI;
+
+public partial class FirebaseMainPage : ContentPage
+{
+ public FirebaseMainPage()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/src/Auth.UI.MAUI/FirebaseUIControl.xaml b/src/Auth.UI.MAUI/FirebaseUIControl.xaml
new file mode 100644
index 0000000..9c8919d
--- /dev/null
+++ b/src/Auth.UI.MAUI/FirebaseUIControl.xaml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Auth.UI.MAUI/FirebaseUIControl.xaml.cs b/src/Auth.UI.MAUI/FirebaseUIControl.xaml.cs
new file mode 100644
index 0000000..bf7e136
--- /dev/null
+++ b/src/Auth.UI.MAUI/FirebaseUIControl.xaml.cs
@@ -0,0 +1,58 @@
+using Firebase.Auth.UI.MAUI.Pages;
+
+namespace Firebase.Auth.UI.MAUI;
+
+public partial class FirebaseUIControl : ContentView
+{
+ public static readonly BindableProperty HeaderProperty = BindableProperty.Create(nameof(FirebaseUIControl), typeof(IView), typeof(FirebaseUIControl),propertyChanged: HeaderValueChanged);
+ public event EventHandler AuthStateChanged;
+ private ProvidersPage ProvidersPage { get; set; }
+ private Page CurrentPage { get; set; }
+ private static void HeaderValueChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is FirebaseUIControl control)
+ {
+ control.ProvidersPage.Header = newValue as IView;
+ }
+ }
+
+ public IView Header
+ {
+ get => GetValue(HeaderProperty) as IView;
+ set => SetValue(HeaderProperty, value);
+ }
+
+ public FirebaseUIControl()
+ {
+ InitializeComponent();
+ if (!FirebaseUI.IsInitialized)
+ {
+ this.Content = new Label {
+ Text = "FirebaseUI has not been initialized yet. Make sure to initialize it during the startup of your application, e.g. in MauiProgram.cs",
+ HorizontalTextAlignment = TextAlignment.Center,
+ VerticalTextAlignment = TextAlignment.Center,
+ };
+ return;
+ }
+
+ ProvidersPage = new() { Header = Header };
+ this.Loaded += ControlLoaded;
+ this.Unloaded += ControlUnloaded;
+
+ this.ContentPage.Children.Add(ProvidersPage);
+ }
+ private void ControlUnloaded(object sender, EventArgs e)
+ {
+ FirebaseUI.Instance.Client.AuthStateChanged -= ClientAuthStateChanged;
+ }
+
+ private void ControlLoaded(object sender, EventArgs args)
+ {
+ FirebaseUI.Instance.Client.AuthStateChanged += ClientAuthStateChanged;
+ }
+ private void ClientAuthStateChanged(object sender, UserEventArgs e)
+ {
+ Dispatcher.DispatchAsync(new Action(() => this.AuthStateChanged?.Invoke(sender, e)));
+ }
+
+}
\ No newline at end of file
diff --git a/src/Auth.UI.MAUI/Launcher.cs b/src/Auth.UI.MAUI/Launcher.cs
new file mode 100644
index 0000000..cd46715
--- /dev/null
+++ b/src/Auth.UI.MAUI/Launcher.cs
@@ -0,0 +1,22 @@
+using System.Diagnostics;
+
+namespace Firebase.Auth.UI.MAUI
+{
+ internal static class Launcher
+ {
+ public static Task LaunchUriAsync(Uri uri)
+ {
+ return LaunchUriAsync(uri.ToString());
+ }
+
+ public static Task LaunchUriAsync(string uri)
+ {
+ var ps = new ProcessStartInfo(uri)
+ {
+ UseShellExecute = true,
+ Verb = "open"
+ };
+ return Process.Start(ps).WaitForExitAsync();
+ }
+ }
+}
diff --git a/src/Auth.UI.MAUI/LoginRouter.cs b/src/Auth.UI.MAUI/LoginRouter.cs
new file mode 100644
index 0000000..d62594a
--- /dev/null
+++ b/src/Auth.UI.MAUI/LoginRouter.cs
@@ -0,0 +1,59 @@
+using Firebase.Auth.UI.MAUI.Pages;
+
+namespace Firebase.Auth.UI.MAUI
+{
+ public static class Router
+ {
+ public enum NavigationModeEnum { Shell, Stack, Direct, StackModal }
+ public static NavigationModeEnum NavigationMode { get; private set; } = NavigationModeEnum.Stack;
+ public static string MainPath { get; private set; }
+ private static bool IsCustomMainRegister { get; set; }
+ public static Type MainType { get; private set; } = typeof(FirebaseMainPage);
+
+ public static void RegisterMainType(NavigationModeEnum navigationMode) where TMainPage : Page
+ {
+ NavigationMode = navigationMode;
+ MainType = typeof(TMainPage);
+ }
+
+ public static void RegisterShellRoutes(string mainPath = "Login") where TMainPage : Page
+ {
+ MainType = typeof(TMainPage);
+ IsCustomMainRegister = true;
+ RegisterShellRoutes(mainPath);
+ Routing.RegisterRoute(mainPath, typeof(TMainPage));
+ }
+
+ public static void RegisterShellRoutes(string mainPath = "Login")
+ {
+ MainPath = mainPath;
+ NavigationMode = NavigationModeEnum.Shell;
+ Routing.RegisterRoute(GetPath(), typeof(FirebaseMainPage));
+ Routing.RegisterRoute(GetPath(), typeof(ProvidersPage));
+ Routing.RegisterRoute(GetPath(), typeof(EmailPage));
+ Routing.RegisterRoute(GetPath(), typeof(SignUpPage));
+ Routing.RegisterRoute(GetPath(), typeof(SignInPage));
+ Routing.RegisterRoute(GetPath(), typeof(RecoverPasswordPage));
+ }
+
+ public static Task NavigateToMain()
+ {
+ return UIFLow.Instance.NavigateToMain();
+ }
+
+ public static string GetMainPath()
+ {
+ return IsCustomMainRegister ? MainPath : GetPath();
+ }
+
+ public static string GetPath() where T : class
+ {
+ return GetPath(typeof(T));
+ }
+
+ public static string GetPath(Type type)
+ {
+ return $"{MainPath}/{type.Name}";
+ }
+ }
+}
diff --git a/src/Auth.UI.MAUI/Pages/EmailPage.xaml b/src/Auth.UI.MAUI/Pages/EmailPage.xaml
new file mode 100644
index 0000000..50488b4
--- /dev/null
+++ b/src/Auth.UI.MAUI/Pages/EmailPage.xaml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Auth.UI.MAUI/Pages/EmailPage.xaml.cs b/src/Auth.UI.MAUI/Pages/EmailPage.xaml.cs
new file mode 100644
index 0000000..8065115
--- /dev/null
+++ b/src/Auth.UI.MAUI/Pages/EmailPage.xaml.cs
@@ -0,0 +1,85 @@
+
+using System.ComponentModel;
+
+namespace Firebase.Auth.UI.MAUI.Pages;
+
+public partial class EmailPage : ContentPage, IQueryAttributable
+{
+ private TaskCompletionSource Result { get; set; }
+ private string Error { get; set; }
+ private bool isLoading = false;
+ public bool IsLoading
+ {
+ get { return isLoading; }
+ set
+ {
+ isLoading = value;
+ OnPropertyChanged();
+ }
+ }
+ public EmailPage()
+ {
+ InitializeComponent();
+ }
+
+ public EmailPage Initialize()
+ {
+ this.IsLoading = false;
+ this.ButtonsPanel.IsEnabled = true;
+ this.EmailTextBox.IsEnabled = true;
+ this.EmailTextBox.Focus();
+
+ this.EmailTextBox.Text = Error ?? string.Empty;
+ this.ErrorTextBlock.IsVisible = !string.IsNullOrEmpty(Error);
+
+ Unloaded += EmailPage_Unloaded;
+ return this;
+ }
+ private void SignInClick(object sender, EventArgs e)
+ {
+ this.SignIn();
+ }
+
+ private void SignIn()
+ {
+ if (!this.CheckEmailAddress(this.EmailTextBox.Text))
+ return;
+
+ this.IsLoading = true;
+ this.EmailTextBox.IsEnabled = false;
+ this.ButtonsPanel.IsEnabled = false;
+ this.Result.SetResult(this.EmailTextBox.Text);
+ }
+
+ private bool CheckEmailAddress(string email)
+ {
+ if (!EmailValidator.ValidateEmail(email))
+ {
+ this.ErrorTextBlock.IsVisible = true;
+ return false;
+ }
+
+ return true;
+ }
+
+ private void CancelClick(object sender, EventArgs e)
+ {
+ Result.SetResult(null);
+ }
+
+ private void EmailPage_Unloaded(object sender, EventArgs e)
+ {
+ if (!Result.Task.IsCompleted)
+ Result.SetResult(null);
+ }
+
+ public void ApplyQueryAttributes(IDictionary query)
+ {
+ if (query == null || query.Count == 0) return;
+
+ this.Result = (TaskCompletionSource)query[nameof(Result)];
+ this.Error = (string)query[nameof(Error)];
+
+ Initialize();
+ }
+}
\ No newline at end of file
diff --git a/src/Auth.UI.MAUI/Pages/ProvidersPage.xaml b/src/Auth.UI.MAUI/Pages/ProvidersPage.xaml
new file mode 100644
index 0000000..4472c41
--- /dev/null
+++ b/src/Auth.UI.MAUI/Pages/ProvidersPage.xaml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Auth.UI.MAUI/Pages/ProvidersPage.xaml.cs b/src/Auth.UI.MAUI/Pages/ProvidersPage.xaml.cs
new file mode 100644
index 0000000..929203a
--- /dev/null
+++ b/src/Auth.UI.MAUI/Pages/ProvidersPage.xaml.cs
@@ -0,0 +1,99 @@
+using Firebase.Auth.UI.MAUI.Components;
+using Firebase.Auth.UI.Resources;
+using System.Collections.ObjectModel;
+
+namespace Firebase.Auth.UI.MAUI.Pages;
+public partial class ProvidersPage : ContentView
+{
+ public ObservableCollection Providers { get; set; }
+ private bool isLoading = false;
+ public bool IsLoading
+ {
+ get { return isLoading; }
+ set
+ {
+ isLoading = value;
+ OnPropertyChanged();
+ }
+ }
+ public static readonly BindableProperty HeaderProperty = BindableProperty.Create(nameof(Header), typeof(IView), typeof(ProvidersPage));
+ public IView Header
+ {
+ get
+ {
+ return GetValue(HeaderProperty) as IView;
+ }
+
+ set
+ {
+ SetValue(HeaderProperty, value);
+ HeaderContent.Content = Header as View;
+ }
+ }
+
+ public ProvidersPage()
+ {
+ this.Providers = new ObservableCollection(FirebaseUI.Instance.Providers);
+
+ if (FirebaseUI.Instance.Config.IsAnonymousAllowed)
+ this.Providers.Add(FirebaseProviderType.Anonymous);
+
+ InitializeComponent();
+
+ var str = AppResources.Instance.FuiTosAndPp;
+ var arr = str.Split(new[] { "{0}", "{1}" }, StringSplitOptions.None);
+
+ FooterTextBlock.FormattedText = new();
+
+ FooterTextBlock.FormattedText.Spans.Add(new Span() {
+ Text = arr[0]
+ });
+ FooterTextBlock.FormattedText.Spans.Add(CreateHiperlink(AppResources.Instance.FuiTermsOfService, FirebaseUI.Instance.Config.TermsOfServiceUrl));
+ FooterTextBlock.FormattedText.Spans.Add(new Span() {
+ Text = arr[1]
+ });
+ FooterTextBlock.FormattedText.Spans.Add(CreateHiperlink(AppResources.Instance.FuiPrivacyPolicy, FirebaseUI.Instance.Config.PrivacyPolicyUrl));
+ FooterTextBlock.FormattedText.Spans.Add(new Span() {
+ Text = arr[2]
+ });
+ }
+
+ private HyperlinkSpan CreateHiperlink(string text, string url)
+ {
+ var span = new HyperlinkSpan()
+ {
+ Text = text,
+ Url = url
+ };
+
+ return span;
+ }
+
+ private void ProviderSignInClick(object sender, TappedEventArgs e)
+ {
+ this.IsLoading = true;
+ this.IsEnabled = false;
+
+ var provider = (FirebaseProviderType)e.Parameter;
+
+ try
+ {
+ _ = FirebaseUI.Instance.SignInAsync(UIFLow.Instance, provider)
+ .ContinueWith((data) => {
+ MainThread.InvokeOnMainThreadAsync(() => {
+ this.IsLoading = false;
+ this.IsEnabled = true;
+ });
+ });
+ }
+ catch (FirebaseAuthException ex)
+ {
+ _ = Application.Current.MainPage.DisplayAlert("Test", FirebaseErrorLookup.LookupError(ex), "Cancelar");
+ }
+ }
+
+ private void PointerPressed(object sender, PointerEventArgs e)
+ {
+ VisualStateManager.GoToState(sender as VisualElement, "Pressed");
+ }
+}
\ No newline at end of file
diff --git a/src/Auth.UI.MAUI/Pages/RecoverPasswordPage.xaml b/src/Auth.UI.MAUI/Pages/RecoverPasswordPage.xaml
new file mode 100644
index 0000000..453ebf4
--- /dev/null
+++ b/src/Auth.UI.MAUI/Pages/RecoverPasswordPage.xaml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Auth.UI.MAUI/Pages/RecoverPasswordPage.xaml.cs b/src/Auth.UI.MAUI/Pages/RecoverPasswordPage.xaml.cs
new file mode 100644
index 0000000..3deab5f
--- /dev/null
+++ b/src/Auth.UI.MAUI/Pages/RecoverPasswordPage.xaml.cs
@@ -0,0 +1,71 @@
+
+namespace Firebase.Auth.UI.MAUI.Pages;
+
+public partial class RecoverPasswordPage : ContentPage, IQueryAttributable
+{
+ private TaskCompletionSource