WIP: addressing the layer-3 WindowingPlatformStub.NotSupportedException
crash on PostIt.Android by downgrading to the Avalonia 11 / Avalonia.Maui
11 stack, where the Android runtime platform layer (IWindowingPlatform)
gets properly bound via the MAUI embedding extension instead of being
left as a stub.
Touched files:
- src/PostIt/Directory.Packages.props:
- Avalonia 12.1.1 -> 11.3.20 (Avalonia, Avalonia.Android,
Avalonia.AvaloniaEdit, Avalonia.Browser, Avalonia.Desktop,
Avalonia.Fonts.Inter, Avalonia.Themes.Fluent).
- New: Avalonia.Maui 11.3.0.
- Microsoft.Maui.Essentials 10.0.100 -> 8.0.100 (aligns with the
Microsoft.Maui.Controls 8.0.x family that Avalonia.Maui 11.3.0
pulls transitively).
- New: Microsoft.Maui.Controls 8.0.100.
- src/PostIt/PostIt.Android/PostIt.Android.csproj:
- TargetFramework net10.0-android -> net9.0-android36.0 (Avalonia
Android 11.3.20 targets net8.0-android34.0, but the
compat-ascending fallback lets us consume from net9.0-android36.0
against the API-36 platform that's already installed locally).
- Add Avalonia.Maui PackageReference.
- Restore Microsoft.Maui.Essentials PackageReference (left in
earlier removal during the MAUI-cause-of-crash hypothesis check).
- src/PostIt/PostIt/PostIt.csproj: TargetFramework net10.0 -> net9.0
(must match what PostIt.Android consumes transitively, otherwise
the build chain breaks).
- src/Yavsc.Abstract/Yavsc.Abstract.csproj: net10.0 -> net9.0 (same
reason; six other consumers stay on net10.0 and pick up the new
assembly via standard TFM compat).
- src/Yavsc.Api.Client/Yavsc.Api.Client.csproj: net10.0 -> net9.0
(same reason).
- src/PostIt/PostIt.Android/Application.cs: rewritten as a bare
Android.App.Application. Avalonia 11 no longer ships
AvaloniaAndroidApplication<TApp> (that was an Avalonia 12
introduction); the Avalonia init now lives in MainActivity via
AvaloniaMainActivity<TApp>. The class is kept only so the
[Application] manifest entry remains.
- src/PostIt/PostIt.Android/MainActivity.cs: now inherits
AvaloniaMainActivity<App> and overrides CustomizeAppBuilder to
chain .UseMaui<PostIt.Maui.MauiEmbeddingApp>(this) before
.WithInterFont(). AvaloniaMainActivity<TApp> handles the
AvaloniaView initialisation in OnCreate.
- src/PostIt/PostIt/Maui/MauiEmbeddingApp.cs (new): empty
Microsoft.Maui.Controls.Application used as the embedding host for
Avalonia.Maui. The MAUI window it produces is consumed by the
Avalonia.Maui embedding pipeline and never surfaces to the user;
Avalonia owns the actual visual tree.
Build status: 11 errors remaining, all CS0234 / CS0246 against
'Avalonia.Controls.ContentPage'. Avalonia 11 did not ship
ContentPage (it's an Avalonia 12 type, presumably for MAUI parity).
All seven PostIt pages inherit ContentPage today and need to be
reworked to UserControl before the build can complete. Follow-up
commit will do that.
Reference: AGENTS.md, 'PostIt.Android boot crash sur AVD x86_64 :
investigation par couches', couche 3.
The earlier commit removed the client_secret and wired
MainActivity.OnNewIntent to AndroidOidcCallbackSink, but
IdentityModel.OidcClient.LoginAsync still had no IBrowser to drive
the user-agent half of the flow. Without it, the desktop / browser
projects continue to fail at login with 'No browser is available'.
Android now plugs in Chrome Custom Tabs:
* PostIt.Android/Services/AndroidSystemBrowser.cs implements
IBrowser.InvokeAsync using CustomTabsIntent.LaunchUrl and waits
for MainActivity.AndroidOidcCallbackSink to deliver the deep-link
Intent (android://postit-signin?code=...&state=...).
* PostIt/Services/Platform.cs is a tiny static indirection the
shared library uses to ask the running platform for an
IBrowser and the appropriate default RedirectUri, without
referencing any UI framework from the shared assembly.
* LoginPageViewModel reads Platform.DefaultRedirectUri and
Platform.CreateBrowser().Invoke() before calling LoginAsync.
* PostIt.Android/PlatformBootstrap.cs wires the Android side at
startup, and MainActivity.OnCreate calls EnsureInitialized().
* Xamarin.AndroidX.Browser 1.8.0 added to the central package
versions so CustomTabsIntent resolves.
PostIt is a desktop/mobile app talking to Yavsc.Org
(https://yavsc.pschneider.fr) as an OIDC identity provider. The
previous grant used the client_credentials flow with a client_secret
embedded in postit-settings.json: this was both insecure (secret
travels with the binary) and inappropriate for an interactive app
(token had no user identity, so the API could not scope or audit).
The new flow is Authorization Code + PKCE:
* PostIt client (Settings/AuthenticationSettings.cs): the
ClientSecret property is removed; GetOidcClientOptions now drops
the secret and accepts an optional IBrowser supplied per-platform.
* Settings.cs: new AndroidRedirectUri constant ('android://postit-signin')
that the Android app uses; RedirectUri is no longer hard-coded in
MainViewModel.
* MainViewModel.cs: the manual discovery + client_credentials POST is
replaced with OidcClient.LoginAsync (Authorization Code + PKCE).
* Settings sample: Authority points at the real Yavsc.Org OP, not at
a non-existent Keycloak-style realm path.
* Yavsc.Org/Extensions/HostingExtensions.cs: the 'postit' client seed
is now idempotent (MigratePostItClientToPublic) and detects
legacy state on existing ConfigurationDb rows - flips
RequireClientSecret=false, RequirePkce=true, drops any ClientSecret
row, and replaces the legacy RedirectUris
(https://yavsc.pschneider.fr/, yavsc://callback) with the current
set (http://127.0.0.1:7890/, android://postit-signin).
PostIt.Android:
* MainActivity: explicit Name attribute so the activity alias can
target a stable component; LaunchMode.SingleTask so the existing
instance receives the deep-link Intent; OnNewIntent forwards the
callback URI through AndroidOidcCallbackSink.
* AndroidManifest.xml: activity-alias PostIt.Android.OidcCallbackActivity
exposing scheme=android host=postit-signin to Android, so the OP
redirect lands back in the running PostIt instance.
The IdentityModel.OidcClient.Browser.SystemBrowser package and a
thin AndroidSystemBrowser implementation are added in a follow-up so
OidcClient.LoginAsync can actually drive Chrome Custom Tabs and
consume AndroidOidcCallbackSink.