diff --git a/.vscode/launch.json b/.vscode/launch.json
index 36dc17a2..dc8d3c68 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -5,19 +5,19 @@
"version": "0.2.0",
"configurations": [
{
- "name": "Android",
+ "name": "Android Debug",
"type": "mono",
"preLaunchTask": "run-debug-android",
"request": "attach",
"address": "localhost",
- "port": 10000
+ "port": 55555
},
{
- "name": "Attach - Android",
+ "name": "Android Attach - Debug",
"type": "mono",
"request": "attach",
"address": "localhost",
- "port": 10000
+ "port": 55555
},
{
"name": "API",
@@ -26,19 +26,19 @@
"projectPath": "${workspaceFolder}/src/Api/Api.csproj"
},
{
- "name": "Yavsc.Org",
+ "name": "Yavsc Org",
"type": "dotnet",
"request": "launch",
"projectPath": "${workspaceFolder}/src/Yavsc.Org/Yavsc.Org.csproj",
},
{
- "name": "Yavsc.Blogs",
+ "name": "Yavsc Blogs",
"type": "dotnet",
"request": "launch",
"projectPath": "${workspaceFolder}/src/Yavsc.Blogs/Yavsc.Blogs.csproj"
},
{
- "name": "PostIt",
+ "name": "PostIt Desktop",
"type": "dotnet",
"request": "launch",
"projectPath": "${workspaceFolder}/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj",
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 16bbe483..915683fc 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -5,6 +5,7 @@
"appsettings",
"asciidoctor",
"ASPNETCORE",
+ "Avalonia",
"Configurabilité",
"Cratie",
"DESTDIR",
@@ -15,6 +16,7 @@
"Hsts",
"Newtonsoft",
"Npgsql",
+ "Oidc",
"PKCE",
"postit",
"pschneider",
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 9fe9caaf..3c0c0bf2 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -18,8 +18,8 @@
"-p:TargetFramework=net10.0-android",
"-p:Configuration=Debug",
"-p:AndroidAttachDebugger=true",
- "-p:AndroidSdbHostPort=10000",
- "-p:AndroidSdbTargetPort=10000"
+ "-p:AndroidSdbHostPort=55555",
+ "-p:AndroidSdbTargetPort=55555"
],
"problemMatcher": [
{
diff --git a/src/PostIt/PostIt.Android/MainActivity.cs b/src/PostIt/PostIt.Android/MainActivity.cs
index 4f788550..ad8455ef 100644
--- a/src/PostIt/PostIt.Android/MainActivity.cs
+++ b/src/PostIt/PostIt.Android/MainActivity.cs
@@ -1,13 +1,11 @@
-using Android;
+
using Android.App;
using Android.Content;
using Android.Content.PM;
using AndroidX.Core.Provider;
using AndroidX.Emoji2.Text;
-using Avalonia;
using Avalonia.Android;
-using AndroidX.Core.Provider;
-using AndroidX.Emoji2.Text;
+using PostIt.Droid.Services;
namespace PostIt.Android;
@@ -34,7 +32,7 @@ public class MainActivity : AvaloniaMainActivity
Yavsc.Resource.Array.com_google_android_gms_fonts_certs); //com_google_android_gms_fonts_certs
EmojiCompat.Config config = new FontRequestEmojiCompatConfig(this, fontRequest);
EmojiCompat.Init(config);
- PlatformBootstrap.EnsureInitialized();
+ PlatformBootstrap.InitPlatform();
base.OnCreate(savedInstanceState);
Current = this;
}
@@ -50,7 +48,13 @@ public class MainActivity : AvaloniaMainActivity
protected override void OnNewIntent(Intent? intent)
{
base.OnNewIntent(intent);
- if (intent is not null) AndroidOidcCallbackSink.Handle(intent);
+
+ var url = intent?.DataString;
+ if (!string.IsNullOrEmpty(url) && url.StartsWith("postit://callback"))
+ {
+ OidcCallbackManager.SetResult(url);
+ }
+
}
internal static class AndroidOidcCallbackSink
diff --git a/src/PostIt/PostIt.Android/PlatformBootstrap.cs b/src/PostIt/PostIt.Android/PlatformBootstrap.cs
index d59b154f..f208f9ce 100644
--- a/src/PostIt/PostIt.Android/PlatformBootstrap.cs
+++ b/src/PostIt/PostIt.Android/PlatformBootstrap.cs
@@ -12,14 +12,9 @@ namespace PostIt.Android;
///
internal static class PlatformBootstrap
{
- private static int _initialized;
-
- internal static void EnsureInitialized()
+ internal static void InitPlatform()
{
- if (System.Threading.Interlocked.Exchange(ref _initialized, 1) != 0)
- return;
- Platform.DefaultRedirectUri = ViewModels.Settings.AndroidRedirectUri;
Platform.CreateBrowser = () =>
{
var activity = MainActivity.Current;
diff --git a/src/PostIt/PostIt.Android/Properties/AndroidManifest.xml b/src/PostIt/PostIt.Android/Properties/AndroidManifest.xml
index 91b61d05..8793aae8 100644
--- a/src/PostIt/PostIt.Android/Properties/AndroidManifest.xml
+++ b/src/PostIt/PostIt.Android/Properties/AndroidManifest.xml
@@ -2,31 +2,5 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/src/PostIt/PostIt.Android/Services/AndroidSystemBrowser.cs b/src/PostIt/PostIt.Android/Services/AndroidSystemBrowser.cs
index b6716e22..bb10b364 100644
--- a/src/PostIt/PostIt.Android/Services/AndroidSystemBrowser.cs
+++ b/src/PostIt/PostIt.Android/Services/AndroidSystemBrowser.cs
@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Android.App;
using AndroidX.Browser.CustomTabs;
using IdentityModel.OidcClient.Browser;
+using PostIt.Droid.Services;
namespace PostIt.Android.Services;
@@ -35,9 +36,14 @@ public sealed class AndroidSystemBrowser : IBrowser
};
}
- var uri = global::Android.Net.Uri.Parse(options.StartUrl)!;
+ // 1. Enregistrez la tâche avant de lancer le Custom Tab
+ var callbackTask = OidcCallbackManager.RegisterCallback(cancellationToken);
- var callbackTask = MainActivity.AndroidOidcCallbackSink.AwaitNextCallbackAsync();
+ // 2. LANCEZ VOTRE CUSTOM TAB ICI (via AndroidX.Browser.CustomTabs)
+ // ... code pour ouvrir l'URL d'authentification ...
+
+
+ var uri = global::Android.Net.Uri.Parse(options.StartUrl)!;
var tabsIntent = new CustomTabsIntent.Builder()
.SetShowTitle(true)!
diff --git a/src/PostIt/PostIt.Android/Services/OidcCallbackManager.cs b/src/PostIt/PostIt.Android/Services/OidcCallbackManager.cs
new file mode 100644
index 00000000..30f8fa18
--- /dev/null
+++ b/src/PostIt/PostIt.Android/Services/OidcCallbackManager.cs
@@ -0,0 +1,21 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace PostIt.Droid.Services;
+
+public static class OidcCallbackManager
+{
+ private static TaskCompletionSource? _tcs;
+
+ public static Task RegisterCallback(CancellationToken cancellationToken)
+ {
+ _tcs = new TaskCompletionSource();
+ cancellationToken.Register(() => _tcs.TrySetCanceled());
+ return _tcs.Task;
+ }
+
+ public static void SetResult(string url)
+ {
+ _tcs?.TrySetResult(url);
+ }
+}
diff --git a/src/PostIt/PostIt.Android/WebAuthenticationCallbackActivity.cs b/src/PostIt/PostIt.Android/WebAuthenticationCallbackActivity.cs
new file mode 100644
index 00000000..9ed2eb18
--- /dev/null
+++ b/src/PostIt/PostIt.Android/WebAuthenticationCallbackActivity.cs
@@ -0,0 +1,35 @@
+using Android.App;
+using Android.Content;
+using Android.Content.PM;
+using Android.OS;
+using PostIt.Droid.Services;
+
+namespace PostIt.Android;
+
+[Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop, Exported = true)]
+[IntentFilter(new[] { Intent.ActionView },
+ Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
+ DataScheme = "postit", // Remplacez par votre schéma personnalisé (ex: yavsc ou postit)
+ DataHost = "callback")] // Correspond à postit://callback
+public class WebAuthenticationCallbackActivity : Activity
+{
+ protected override void OnCreate(Bundle? savedInstanceState)
+ {
+ base.OnCreate(savedInstanceState);
+
+ // Capturer l'URL de redirection OIDC
+ var url = Intent?.DataString;
+
+ if (!string.IsNullOrEmpty(url))
+ {
+ // Transmettre l'URL au gestionnaire partagé pour compléter la Task
+ OidcCallbackManager.SetResult(url);
+ }
+
+ // Fermer cette activité transparente et ramener l'application au premier plan
+ var intent = new Intent(this, typeof(MainActivity));
+ intent.AddFlags(ActivityFlags.ClearTop | ActivityFlags.SingleTop);
+ StartActivity(intent);
+ Finish();
+ }
+}
diff --git a/src/PostIt/PostIt.Desktop/PlatformBootstrap.cs b/src/PostIt/PostIt.Desktop/PlatformBootstrap.cs
deleted file mode 100644
index ff9ca7f6..00000000
--- a/src/PostIt/PostIt.Desktop/PlatformBootstrap.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using PostIt.Services;
-
-namespace PostIt.Desktop;
-
-///
-/// One-shot platform bootstrap. Called from Program.Main so that
-/// the shared OIDC login path sees a working IBrowser — the
-/// custom-scheme browser that hands the OIDC callback off to the
-/// running instance through the named pipe. Desktop builds do NOT use
-/// a loopback HTTP listener: the postit:// scheme is registered
-/// with the OS at install time and the browser is whatever the user
-/// has configured to open it.
-///
-internal static class PlatformBootstrap
-{
- private static int _initialized;
-
- internal static void EnsureInitialized()
- {
- if (System.Threading.Interlocked.Exchange(ref _initialized, 1) != 0)
- return;
-
- // Use the custom-scheme redirect on Desktop. Loopback is only
- // a fallback for platforms that cannot register postit://
- // (see Settings.DefaultLoopbackRedirectUri for that path).
- Platform.DefaultRedirectUri = AuthenticationSettings.DefaultDesktopRedirectUri;
- Platform.CustomScheme = "postit";
- }
-}
diff --git a/src/PostIt/PostIt.Desktop/Program.cs b/src/PostIt/PostIt.Desktop/Program.cs
index f22f79b0..0de3bd69 100644
--- a/src/PostIt/PostIt.Desktop/Program.cs
+++ b/src/PostIt/PostIt.Desktop/Program.cs
@@ -12,8 +12,6 @@ sealed class Program
[STAThread]
public static void Main(string[] args)
{
- PlatformBootstrap.EnsureInitialized();
-
// Short-circuit 2nd-instance launches (OS handing us the
// postit://callback URL) BEFORE Avalonia spins up a window.
// If we let Avalonia initialise, the new MainWindow flashes
diff --git a/src/PostIt/PostIt.Tests/SettingsLoadTests.cs b/src/PostIt/PostIt.Tests/SettingsLoadTests.cs
index dc58e9fc..43021caa 100644
--- a/src/PostIt/PostIt.Tests/SettingsLoadTests.cs
+++ b/src/PostIt/PostIt.Tests/SettingsLoadTests.cs
@@ -85,7 +85,7 @@ public class SettingsLoadTests
bool flip = ((workerId + i) & 1) == 0;
settings.DarkMode = flip;
settings.Authentication.RedirectUri =
- global::AuthenticationSettings.DefaultDesktopRedirectUri;
+ global::AuthenticationSettings.DesktopRedirectUri;
settings.BusinessApiUrl = flip
? "https://a.example.test/api/v1/"
diff --git a/src/PostIt/PostIt/App.axaml.cs b/src/PostIt/PostIt/App.axaml.cs
index f8430347..1b019f6e 100644
--- a/src/PostIt/PostIt/App.axaml.cs
+++ b/src/PostIt/PostIt/App.axaml.cs
@@ -1,5 +1,4 @@
using System;
-using System.Linq;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
@@ -73,10 +72,8 @@ public partial class App : Application
ConfigureRootView(View);
ApplyDarkMode(settings);
}
- var mainVm = ServiceProvider!.GetRequiredService();
base.OnFrameworkInitializationCompleted();
- this.PushPageAsync(mainVm);
}
private void ConfigureRootView(MainView rootView)
@@ -93,9 +90,9 @@ private void ConfigureRootView(MainView rootView)
rootView.NavRoot.PopToRootAsync();
};
- sessionStatus.LoginSucceeded += () =>
+ sessionStatus.LoginSucceeded += async () =>
{
- PushMainPageAsync();
+ await PushMainPageAsync();
};
rootView.SessionBanner.DataContext = sessionStatus;
@@ -133,6 +130,9 @@ private void ConfigureRootView(MainView rootView)
var refreshed = await api.TrySilentLoginAsync().ConfigureAwait(true);
var sessionStatus = provider.GetRequiredService();
sessionStatus.Refresh();
+ var homePage = provider.GetRequiredService();
+ var app = (App)Current!;
+ await app.PushPageAsync(homePage);
if (!refreshed) return;
await PushMainPageAsync().ConfigureAwait(true);
diff --git a/src/PostIt/PostIt/Services/Platform.cs b/src/PostIt/PostIt/Services/Platform.cs
index c867c63c..8e5f7e25 100644
--- a/src/PostIt/PostIt/Services/Platform.cs
+++ b/src/PostIt/PostIt/Services/Platform.cs
@@ -21,14 +21,14 @@ public static class Platform
/// override this property at startup (e.g. PostIt.Android sets
/// it to android://postit-signin).
///
- public static string DefaultRedirectUri { get; set; } = "postit://callback";
+ public const string RedirectUri = "postit://callback";
///
/// Scheme prefix the matches
/// against BrowserOptions.EndUrl. Overridable for apps
/// that want to register their own scheme.
///
- public static string CustomScheme { get; set; } = "postit";
+ public const string CustomScheme = "postit";
///
/// Constructs a fresh for the running platform.
@@ -37,4 +37,4 @@ public static class Platform
///
public static System.Func? CreateBrowser { get; set; } =
() => new CustomSchemeBrowser(CustomScheme);
-}
\ No newline at end of file
+}
diff --git a/src/PostIt/PostIt/Settings/AuthenticationSettings.cs b/src/PostIt/PostIt/Settings/AuthenticationSettings.cs
index 99358cfb..9ebeaebd 100644
--- a/src/PostIt/PostIt/Settings/AuthenticationSettings.cs
+++ b/src/PostIt/PostIt/Settings/AuthenticationSettings.cs
@@ -10,7 +10,7 @@ public partial class AuthenticationSettings : ObservableObject
/// hand-off in
/// (RFC 8252 §7.1). Production Desktop builds use this.
///
- public const string DefaultDesktopRedirectUri = "postit://callback";
+ public const string DesktopRedirectUri = "postit://callback";
///
/// Redirect URI used by the Android app. The corresponding IntentFilter
@@ -34,15 +34,19 @@ public partial class AuthenticationSettings : ObservableObject
[ObservableProperty]
public partial string[] Scopes { get; set; }
-
///
- /// OAuth redirect URI. Defaults to
+ /// OAuth redirect URI. Defaults to
/// (custom URI scheme) which is the right answer for desktop
/// production builds. Mobile platforms must set this to
/// before calling LoginAsync.
///
[ObservableProperty]
- public partial string RedirectUri { get; set; } = DefaultDesktopRedirectUri;
+ public partial string RedirectUri { get; set; }
+#if ANDROID
+ = AndroidRedirectUri;
+#else
+ = DesktopRedirectUri;
+#endif
///
/// Space-separated view of . Exists for the
diff --git a/src/PostIt/PostIt/ViewModels/Settings.cs b/src/PostIt/PostIt/ViewModels/Settings.cs
index 2115a0cd..fd87d772 100644
--- a/src/PostIt/PostIt/ViewModels/Settings.cs
+++ b/src/PostIt/PostIt/ViewModels/Settings.cs
@@ -2,13 +2,11 @@ using System.Runtime.CompilerServices;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using IdentityModel.OidcClient;
-using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text.Json;
-using System.Threading;
[assembly: InternalsVisibleTo("PostIt.Tests")]
@@ -18,37 +16,6 @@ public partial class Settings : ViewModelBase
{
const string SettingsFileName = "postit-settings.json";
- ///
- /// Redirect URI used by the Android app. The corresponding IntentFilter
- /// in PostIt.Android/Properties/AndroidManifest.xml must match.
- ///
- public const string AndroidRedirectUri = "android://postit-signin";
-
-
-
- ///
- /// Process-wide canonical instance, wired up
- /// at application boot by
- /// through . The hybrid pattern:
- ///
- /// - The static Current reference gives
- /// ViewModels a non-DI way to reach the same instance (and lets
- /// the framework bindings push notifications through one stable
- /// ).
- /// - Tests that want to exercise a clean
- /// instance still call new Settings(); Current
- /// stays null in those contexts because
- /// is never invoked.
- /// - Reads () are
- /// thread-safe and never allocate; mutations always go through
- /// the DI-resolved singleton so two threads cannot each register
- /// a different "current" Settings.
- ///
- ///
- private static Settings? s_current;
-
-
-
[ObservableProperty]
public partial AuthenticationSettings Authentication { get; set; } = new();
@@ -66,7 +33,7 @@ public partial class Settings : ViewModelBase
/// setters above all funnel through here, and we flip
/// in lock-step. Sub-property mutations
/// (e.g. Authentication.Authority) are caught by the
- /// subscription wired up in
+ /// subscription wired up in
/// below. disables the flag during bulk
/// hydration so the disk load itself does not count as a user
/// edit.
@@ -336,7 +303,7 @@ public partial class Settings : ViewModelBase
this.Authentication.ClientId = string.IsNullOrWhiteSpace(settings.Authentication.ClientId) ?
AuthenticationSettings.DefaultClientId : settings.Authentication.ClientId;
this.Authentication.RedirectUri = string.IsNullOrWhiteSpace(settings.Authentication.RedirectUri) ?
- AuthenticationSettings.DefaultDesktopRedirectUri : settings.Authentication.RedirectUri;
+ AuthenticationSettings.DesktopRedirectUri : settings.Authentication.RedirectUri;
if (settings.Authentication.Scopes is null || settings.Authentication.Scopes.Length == 0)
{
settings.Authentication.Scopes = AuthenticationSettings.DefaultScopes;
@@ -377,7 +344,7 @@ public partial class Settings : ViewModelBase
{
Authority = AuthenticationSettings.DefaultAuthority,
ClientId = AuthenticationSettings.DefaultClientId,
- RedirectUri = AuthenticationSettings.DefaultDesktopRedirectUri,
+ RedirectUri = AuthenticationSettings.DesktopRedirectUri,
Scopes = AuthenticationSettings.DefaultScopes
};
this.DarkMode = false;