test are green !?!
This commit is contained in:
parent
cbe307033b
commit
d0186d41b0
25 changed files with 145 additions and 230 deletions
23
Makefile
23
Makefile
|
|
@ -165,9 +165,10 @@ LOGCAT_LINES ?= 200
|
||||||
LOGCAT_FOLLOW ?= 0
|
LOGCAT_FOLLOW ?= 0
|
||||||
LOGCAT_BOOT_WAIT ?= 30
|
LOGCAT_BOOT_WAIT ?= 30
|
||||||
|
|
||||||
|
ANDROID_PACKAGE_NAME = fr.pschneider.PostIt
|
||||||
POSTIT_ANDROID_CSPROJ := src/PostIt/PostIt.Android/PostIt.Android.csproj
|
POSTIT_ANDROID_CSPROJ := src/PostIt/PostIt.Android/PostIt.Android.csproj
|
||||||
POSTIT_APK_DIR := src/PostIt/PostIt.Android/bin/$(CONFIG)/net10.0-android/$(POSTIT_RID)
|
POSTIT_APK_DIR := src/PostIt/PostIt.Android/bin/$(CONFIG)/net10.0-android/$(POSTIT_RID)
|
||||||
POSTIT_APK := $(POSTIT_APK_DIR)/fr.pschneider.PostIt-Signed.apk
|
POSTIT_APK := $(POSTIT_APK_DIR)/$(ANDROID_PACKAGE_NAME)-Signed.apk
|
||||||
|
|
||||||
qemu-run:
|
qemu-run:
|
||||||
@echo " Starting AVD $(AVD_NAME) on $(ADB_SERIAL)..."
|
@echo " Starting AVD $(AVD_NAME) on $(ADB_SERIAL)..."
|
||||||
|
|
@ -227,12 +228,12 @@ qemu-install: qemu-build
|
||||||
adb -s $(ADB_SERIAL) install -r "$(POSTIT_APK)" -r
|
adb -s $(ADB_SERIAL) install -r "$(POSTIT_APK)" -r
|
||||||
|
|
||||||
qemu-uninstall:
|
qemu-uninstall:
|
||||||
adb -s $(ADB_SERIAL) uninstall fr.pschneider.PostIt
|
adb -s $(ADB_SERIAL) uninstall $(ANDROID_PACKAGE_NAME)
|
||||||
|
|
||||||
# Dump recent logcat output for the running PostIt.Android process.
|
# Dump recent logcat output for the running PostIt.Android process.
|
||||||
# By default, prints the last $(LOGCAT_LINES) lines (one-shot, with
|
# By default, prints the last $(LOGCAT_LINES) lines (one-shot, with
|
||||||
# `-d`). Set LOGCAT_FOLLOW=1 to follow the stream live instead.
|
# `-d`). Set LOGCAT_FOLLOW=1 to follow the stream live instead.
|
||||||
# Filtering is by PID (pidof fr.pschneider.PostIt), not by tag,
|
# Filtering is by PID (pidof $(ANDROID_PACKAGE_NAME)), not by tag,
|
||||||
# because Mono/Xamarin can emit logs under several tags
|
# because Mono/Xamarin can emit logs under several tags
|
||||||
# (mono, PostIt.Android, Avalonia.Android) and tag-based filtering
|
# (mono, PostIt.Android, Avalonia.Android) and tag-based filtering
|
||||||
# would miss the ones not matching. PID-based filtering is exact.
|
# would miss the ones not matching. PID-based filtering is exact.
|
||||||
|
|
@ -240,17 +241,17 @@ qemu-uninstall:
|
||||||
# silently with no output; that is the expected behaviour for
|
# silently with no output; that is the expected behaviour for
|
||||||
# "no logs yet".
|
# "no logs yet".
|
||||||
qemu-logcat:
|
qemu-logcat:
|
||||||
@PID=$$(adb -s $(ADB_SERIAL) shell pidof fr.pschneider.PostIt 2>/dev/null | tr -d '\r\n'); \
|
@PID=$$(adb -s $(ADB_SERIAL) shell pidof $(ANDROID_PACKAGE_NAME) 2>/dev/null | tr -d '\r\n'); \
|
||||||
if [ -z "$$PID" ]; then \
|
if [ -z "$$PID" ]; then \
|
||||||
echo " fr.pschneider.PostIt is not running on $(ADB_SERIAL)."; \
|
echo " $(ANDROID_PACKAGE_NAME) is not running on $(ADB_SERIAL)."; \
|
||||||
echo " Start the app first (am start -n fr.pschneider.PostIt/PostIt.Android.PostItMainActivity)"; \
|
echo " Start the app first (am start -n $(ANDROID_PACKAGE_NAME)/PostIt.Android.PostItMainActivity)"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi; \
|
fi; \
|
||||||
echo " Following PID $$PID (LOGCAT_FOLLOW=$(LOGCAT_FOLLOW), LOGCAT_LINES=$(LOGCAT_LINES))"; \
|
echo " Following PID $$PID (LOGCAT_FOLLOW=$(LOGCAT_FOLLOW), LOGCAT_LINES=$(LOGCAT_LINES))"; \
|
||||||
if [ "$(LOGCAT_FOLLOW)" = "1" ]; then \
|
if [ "$(LOGCAT_FOLLOW)" = "1" ]; then \
|
||||||
adb -s $(ADB_SERIAL) logcat -v time --pid=$$PID fr.pschneider.PostIt:F; \
|
adb -s $(ADB_SERIAL) logcat -v time --pid=$$PID $(ANDROID_PACKAGE_NAME):F; \
|
||||||
else \
|
else \
|
||||||
adb -s $(ADB_SERIAL) logcat -d -v time -t $(LOGCAT_LINES) --pid=$$PID fr.pschneider.PostIt:F; \
|
adb -s $(ADB_SERIAL) logcat -d -v time -t $(LOGCAT_LINES) --pid=$$PID $(ANDROID_PACKAGE_NAME):F; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Clear logcat, launch PostIt.Android, then dump everything that was
|
# Clear logcat, launch PostIt.Android, then dump everything that was
|
||||||
|
|
@ -264,13 +265,13 @@ LOGCAT_BOOT_WAIT ?= 15
|
||||||
qemu-logcat-boot:
|
qemu-logcat-boot:
|
||||||
@echo " Clearing logcat buffer..."
|
@echo " Clearing logcat buffer..."
|
||||||
adb -s $(ADB_SERIAL) logcat -c
|
adb -s $(ADB_SERIAL) logcat -c
|
||||||
@echo " Launching fr.pschneider.PostIt..."
|
@echo " Launching $(ANDROID_PACKAGE_NAME)..."
|
||||||
adb -s $(ADB_SERIAL) shell am start \
|
adb -s $(ADB_SERIAL) shell am start \
|
||||||
-n fr.pschneider.PostIt/PostIt.Android.PostItMainActivity
|
-n $(ANDROID_PACKAGE_NAME)/PostIt.Android.PostItMainActivity
|
||||||
@echo " Waiting $(LOGCAT_BOOT_WAIT)s for the app to start rendering..."
|
@echo " Waiting $(LOGCAT_BOOT_WAIT)s for the app to start rendering..."
|
||||||
@sleep $(LOGCAT_BOOT_WAIT)
|
@sleep $(LOGCAT_BOOT_WAIT)
|
||||||
@echo " Dumping logcat (PostIt PID + system buffer):"
|
@echo " Dumping logcat (PostIt PID + system buffer):"
|
||||||
@PID=$$(adb -s $(ADB_SERIAL) shell pidof fr.pschneider.PostIt 2>/dev/null | tr -d '\r\n'); \
|
@PID=$$(adb -s $(ADB_SERIAL) shell pidof $(ANDROID_PACKAGE_NAME) 2>/dev/null | tr -d '\r\n'); \
|
||||||
if [ -n "$$PID" ]; then \
|
if [ -n "$$PID" ]; then \
|
||||||
echo " (PID $$PID at dump time)"; \
|
echo " (PID $$PID at dump time)"; \
|
||||||
adb -s $(ADB_SERIAL) logcat -d -v time --pid=$$PID; \
|
adb -s $(ADB_SERIAL) logcat -d -v time --pid=$$PID; \
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
<Project>
|
<Project>
|
||||||
<!-- Pull in shared package versions from the repository root. -->
|
|
||||||
<Import
|
<Import
|
||||||
Project="$([MSBuild]::GetPathOfFileAbove('Directory.Packages.props', '$(MSBuildThisFileDirectory)../'))" />
|
Project="$([MSBuild]::GetPathOfFileAbove('Directory.Packages.props', '$(MSBuildThisFileDirectory)../'))" />
|
||||||
<!-- PostIt-product-specific versions -->
|
<!-- PostIt-product-specific versions -->
|
||||||
<ItemGroup> <!-- Avalonia packages -->
|
<!-- https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- Avalonia packages -->
|
||||||
<!-- Important: keep version in sync! -->
|
<!-- Important: keep version in sync! -->
|
||||||
<PackageVersion Include="Avalonia" Version="12.1.1" />
|
<PackageVersion Include="Avalonia" Version="12.1.1" />
|
||||||
<PackageVersion Include="Avalonia.Themes.Fluent" Version="12.1.1" />
|
<PackageVersion Include="Avalonia.Themes.Fluent" Version="12.1.1" />
|
||||||
|
|
@ -11,28 +15,23 @@
|
||||||
<PackageVersion Include="Avalonia.Desktop" Version="12.1.1" />
|
<PackageVersion Include="Avalonia.Desktop" Version="12.1.1" />
|
||||||
<PackageVersion Include="Avalonia.Browser" Version="12.1.1" />
|
<PackageVersion Include="Avalonia.Browser" Version="12.1.1" />
|
||||||
<PackageVersion Include="Avalonia.Android" Version="12.1.1" />
|
<PackageVersion Include="Avalonia.Android" Version="12.1.1" />
|
||||||
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3" />
|
|
||||||
|
|
||||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.2" />
|
|
||||||
|
|
||||||
<PackageVersion Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1.15" />
|
|
||||||
|
|
||||||
<PackageVersion Include="Xamarin.AndroidX.Core.Core.KtX" Version="1.19.0.1" />
|
|
||||||
<PackageVersion Include="Xamarin.AndroidX.Compose.Runtime.Annotation.Jvm" Version="1.11.4" />
|
|
||||||
<PackageVersion Include="Xamarin.AndroidX.Core" Version="1.19.0.1" />
|
|
||||||
<PackageVersion Include="Xamarin.AndroidX.AppCompat" Version="1.7.1.4" />
|
|
||||||
<PackageVersion Include="Xamarin.AndroidX.Browser" Version="1.10.0.1" />
|
|
||||||
|
|
||||||
<PackageVersion Include="Xamarin.UITest.Core" Version="4.4.2" />
|
|
||||||
<PackageVersion Include="Xamarin.UITest.AndroidX" Version="4.4.2" />
|
|
||||||
<PackageVersion Include="Xamarin.UITest.Queries" Version="4.4.2" />
|
|
||||||
<PackageVersion Include="Xamarin.UITest" Version="4.4.2" />
|
|
||||||
|
|
||||||
<PackageVersion Include="Avalonia.Headless" Version="12.1.1" />
|
<PackageVersion Include="Avalonia.Headless" Version="12.1.1" />
|
||||||
<PackageVersion Include="Avalonia.Headless.Xunit" Version="12.1.1" />
|
<PackageVersion Include="Avalonia.Headless.Xunit" Version="12.1.1" />
|
||||||
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="12.0.0" />
|
<PackageVersion Include="Avalonia.AvaloniaEdit" Version="12.0.0" />
|
||||||
<PackageVersion Include="Material.Avalonia" Version="3.19.0" />
|
<PackageVersion Include="Material.Avalonia" Version="3.19.0" />
|
||||||
|
<PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3" />
|
||||||
|
|
||||||
|
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.2" />
|
||||||
|
|
||||||
|
<PackageVersion Include="Xamarin.AndroidX.Browser" Version="1.10.0.1" />
|
||||||
|
<PackageVersion Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1.15" />
|
||||||
|
<PackageVersion Include="Xamarin.AndroidX.Lifecycle.Runtime" Version="2.11.0.1" />
|
||||||
|
<PackageVersion Include="Xamarin.AndroidX.Lifecycle.Common" Version="2.11.0.1" />
|
||||||
|
<PackageVersion Include="Xamarin.UITest" Version="4.4.2" />
|
||||||
|
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.11" />
|
||||||
<PackageVersion Include="Microsoft.Maui.Essentials" Version="10.0.100" />
|
<PackageVersion Include="Microsoft.Maui.Essentials" Version="10.0.100" />
|
||||||
|
|
||||||
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,12 @@
|
||||||
using Android.Runtime;
|
using Android.Runtime;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Android;
|
using Avalonia.Android;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Styling;
|
||||||
|
using Yavsc.Api.Client;
|
||||||
|
|
||||||
namespace PostIt.Android
|
namespace PostIt.Android
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
using Android.App;
|
using Android.App;
|
||||||
using Android.Content.PM;
|
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
|
using Android.Content.PM;
|
||||||
|
using Avalonia;
|
||||||
using Avalonia.Android;
|
using Avalonia.Android;
|
||||||
using AndroidX.Emoji2.Text;
|
|
||||||
using AndroidX.Core.Provider;
|
|
||||||
using Android;
|
|
||||||
using Android.Graphics;
|
|
||||||
|
|
||||||
namespace PostIt.Android;
|
namespace PostIt.Android;
|
||||||
|
|
||||||
|
|
@ -15,7 +12,6 @@ namespace PostIt.Android;
|
||||||
Theme = "@style/MyTheme.NoActionBar",
|
Theme = "@style/MyTheme.NoActionBar",
|
||||||
Icon = "@drawable/icon",
|
Icon = "@drawable/icon",
|
||||||
MainLauncher = true,
|
MainLauncher = true,
|
||||||
LaunchMode = LaunchMode.SingleTask,
|
|
||||||
ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
|
ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
|
||||||
public class MainActivity : AvaloniaMainActivity
|
public class MainActivity : AvaloniaMainActivity
|
||||||
{
|
{
|
||||||
|
|
@ -33,7 +29,6 @@ public class MainActivity : AvaloniaMainActivity
|
||||||
PlatformBootstrap.EnsureInitialized();
|
PlatformBootstrap.EnsureInitialized();
|
||||||
Current = this;
|
Current = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Receives the deep-link Intent fired by the system browser after the
|
/// Receives the deep-link Intent fired by the system browser after the
|
||||||
/// user completes the OIDC login on https://yavsc.pschneider.fr. The
|
/// user completes the OIDC login on https://yavsc.pschneider.fr. The
|
||||||
|
|
|
||||||
|
|
@ -10,19 +10,22 @@
|
||||||
<AndroidPackageFormat>apk</AndroidPackageFormat>
|
<AndroidPackageFormat>apk</AndroidPackageFormat>
|
||||||
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
|
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidResource Include="Icon.png">
|
<AndroidResource Include="Icon.png">
|
||||||
<Link>Resources\drawable\Icon.png</Link>
|
<Link>Resources\drawable\Icon.png</Link>
|
||||||
</AndroidResource>
|
</AndroidResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\PostIt\PostIt.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia.Android" />
|
<PackageReference Include="Avalonia.Android" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.Core.SplashScreen" />
|
<PackageReference Include="Xamarin.AndroidX.Core.SplashScreen" />
|
||||||
<PackageReference Include="Avalonia.Browser" />
|
|
||||||
<PackageReference Include="Xamarin.AndroidX.Browser" />
|
<PackageReference Include="Xamarin.AndroidX.Browser" />
|
||||||
<PackageReference Include="IdentityModel.OidcClient" />
|
<PackageReference Include="Xamarin.AndroidX.Lifecycle.Runtime" />
|
||||||
|
<PackageReference Include="Xamarin.AndroidX.Lifecycle.Common" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\PostIt\PostIt.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto">
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<application android:label="PostIt" android:icon="@drawable/Icon">
|
<application android:label="PostIt" android:icon="@drawable/Icon">
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,14 @@
|
||||||
<item name="android:windowActionBar">false</item>
|
<item name="android:windowActionBar">false</item>
|
||||||
<item name="android:windowBackground">@null</item>
|
<item name="android:windowBackground">@null</item>
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
|
<item name="android:windowSplashScreenBackground">@color/splash_background</item>
|
||||||
|
<item name="android:windowSplashScreenAnimatedIcon">@drawable/avalonia_anim</item>
|
||||||
|
<item name="android:windowSplashScreenAnimationDuration">1000</item>
|
||||||
|
<item name="postSplashScreenTheme">@style/MyTheme.Main</item>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<style name="MyTheme.Main"
|
||||||
|
parent ="MyTheme.NoActionBar">
|
||||||
|
<item name="android:windowIsTranslucent">true</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="MyTheme.NoActionBar" parent="@style/Theme.AppCompat.DayNight.NoActionBar">
|
<style name="MyTheme.NoActionBar" parent="@style/Theme.AppCompat.DayNight.NoActionBar">
|
||||||
<item name="android:windowBackground">@drawable/splash_screen</item>
|
|
||||||
<item name="android:windowActionBar">false</item>
|
<item name="android:windowActionBar">false</item>
|
||||||
|
<item name="android:windowBackground">@drawable/splash_screen</item>
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,9 @@ public sealed class AndroidSystemBrowser : IBrowser
|
||||||
var callbackTask = MainActivity.AndroidOidcCallbackSink.AwaitNextCallbackAsync();
|
var callbackTask = MainActivity.AndroidOidcCallbackSink.AwaitNextCallbackAsync();
|
||||||
|
|
||||||
var tabsIntent = new CustomTabsIntent.Builder()
|
var tabsIntent = new CustomTabsIntent.Builder()
|
||||||
.SetShowTitle(true)
|
.SetShowTitle(true)!
|
||||||
.Build();
|
.Build();
|
||||||
tabsIntent.LaunchUrl(_activity, uri);
|
tabsIntent!.LaunchUrl(_activity, uri);
|
||||||
|
|
||||||
string responseUri;
|
string responseUri;
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,4 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\PostIt\PostIt.csproj" />
|
<ProjectReference Include="..\PostIt\PostIt.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="GitVersion.MsBuild" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,4 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\PostIt\PostIt.csproj" />
|
<ProjectReference Include="..\PostIt\PostIt.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="GitVersion.MsBuild" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -68,9 +68,6 @@ sealed class Program
|
||||||
public static AppBuilder BuildAvaloniaApp()
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
=> AppBuilder.Configure<App>()
|
=> AppBuilder.Configure<App>()
|
||||||
.UsePlatformDetect()
|
.UsePlatformDetect()
|
||||||
#if DEBUG
|
|
||||||
.WithDeveloperTools()
|
|
||||||
#endif
|
|
||||||
.WithInterFont()
|
.WithInterFont()
|
||||||
.LogToTrace();
|
.LogToTrace();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ public class MainPageButtonsTests
|
||||||
{ }
|
{ }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MainPageViewModel MakeViewModel(BlogPostDto? selectedPost = null)
|
private static MainViewModel MakeViewModel(BlogPostDto? selectedPost = null)
|
||||||
{
|
{
|
||||||
var api = new ThrowingApi();
|
var api = new ThrowingApi();
|
||||||
var blog = new BlogApiClient(api, "http://localhost/");
|
var blog = new BlogApiClient(api, "http://localhost/");
|
||||||
|
|
@ -93,7 +93,7 @@ public class MainPageButtonsTests
|
||||||
services.AddTransient<SignaturePage>();
|
services.AddTransient<SignaturePage>();
|
||||||
services.AddTransient<CirclesPage>();
|
services.AddTransient<CirclesPage>();
|
||||||
services.AddTransient<PostAclDialog>();
|
services.AddTransient<PostAclDialog>();
|
||||||
var vm = new MainPageViewModel(blog, services: services.BuildServiceProvider());
|
var vm = new MainViewModel(blog, services: services.BuildServiceProvider());
|
||||||
if (selectedPost is not null) vm.SelectedPost = selectedPost;
|
if (selectedPost is not null) vm.SelectedPost = selectedPost;
|
||||||
return vm;
|
return vm;
|
||||||
}
|
}
|
||||||
|
|
@ -109,7 +109,7 @@ public class MainPageButtonsTests
|
||||||
/// realised and <c>KeyPressQwerty</c> has a real
|
/// realised and <c>KeyPressQwerty</c> has a real
|
||||||
/// <see cref="TopLevel"/> to dispatch against.
|
/// <see cref="TopLevel"/> to dispatch against.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static (MainWindow window, MainPage page) MountMainPage(MainPageViewModel vm)
|
private static (MainWindow window, MainPage page) MountMainPage(MainViewModel vm)
|
||||||
{
|
{
|
||||||
var window = new MainWindow();
|
var window = new MainWindow();
|
||||||
var page = new MainPage { DataContext = vm };
|
var page = new MainPage { DataContext = vm };
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ public class MainPageSaveTests
|
||||||
var recorder = new CallRecorder();
|
var recorder = new CallRecorder();
|
||||||
var api = new RecordingYavscApiClient(recorder);
|
var api = new RecordingYavscApiClient(recorder);
|
||||||
var blog = new BlogApiClient(api, "http://localhost/");
|
var blog = new BlogApiClient(api, "http://localhost/");
|
||||||
var viewModel = new MainPageViewModel(blog);
|
var viewModel = new MainViewModel(blog);
|
||||||
|
|
||||||
var page = new MainPage { DataContext = viewModel };
|
var page = new MainPage { DataContext = viewModel };
|
||||||
// MainPage is a ContentPage (a Page, not a Control), so it
|
// MainPage is a ContentPage (a Page, not a Control), so it
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ public class PostItViewModelTests
|
||||||
// throws on any call (we never call the API in this test).
|
// throws on any call (we never call the API in this test).
|
||||||
var fakeApi = new ThrowingYavscApiClient();
|
var fakeApi = new ThrowingYavscApiClient();
|
||||||
var blog = new BlogApiClient(fakeApi, "http://localhost/");
|
var blog = new BlogApiClient(fakeApi, "http://localhost/");
|
||||||
var viewModel = new MainPageViewModel(blog);
|
var viewModel = new MainViewModel(blog);
|
||||||
|
|
||||||
viewModel.Posts.Add(new BlogPostDto { Id = 1, Title = "First post", Article = "Hello world", AuthorId = "alice" });
|
viewModel.Posts.Add(new BlogPostDto { Id = 1, Title = "First post", Article = "Hello world", AuthorId = "alice" });
|
||||||
viewModel.Posts.Add(new BlogPostDto { Id = 2, Title = "Second post", Article = "Nothing here", AuthorId = "bob" });
|
viewModel.Posts.Add(new BlogPostDto { Id = 2, Title = "Second post", Article = "Nothing here", AuthorId = "bob" });
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
<Application xmlns="https://github.com/avaloniaui"
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="using:PostIt"
|
xmlns:local="using:PostIt"
|
||||||
x:Class="PostIt.App">
|
x:Class="PostIt.App"
|
||||||
|
RequestedThemeVariant="Default">
|
||||||
|
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||||
|
|
||||||
<!-- ViewLocator is registered in App.axaml.cs with the real DI container. -->
|
<Application.DataTemplates>
|
||||||
|
<local:ViewLocator />
|
||||||
|
</Application.DataTemplates>
|
||||||
|
|
||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
<FluentTheme />
|
<FluentTheme />
|
||||||
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
|
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
|
||||||
|
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
</Application>
|
</Application>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using PostIt.Services;
|
using PostIt.Services;
|
||||||
using Yavsc.Api.Client;
|
|
||||||
using PostIt.ViewModels;
|
using PostIt.ViewModels;
|
||||||
using PostIt.Views;
|
using PostIt.Views;
|
||||||
|
using Yavsc.Api.Client;
|
||||||
|
|
||||||
namespace PostIt;
|
namespace PostIt;
|
||||||
|
|
||||||
|
|
@ -28,9 +28,7 @@ public partial class App : Application
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IServiceProvider? ServiceProvider { get; private set; }
|
public IServiceProvider? ServiceProvider { get; private set; }
|
||||||
|
|
||||||
public App()
|
MainWindow window;
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
|
|
@ -42,101 +40,31 @@ public partial class App : Application
|
||||||
|
|
||||||
public override void OnFrameworkInitializationCompleted()
|
public override void OnFrameworkInitializationCompleted()
|
||||||
{
|
{
|
||||||
// Belt-and-braces 2nd-instance guard. The primary check now
|
|
||||||
// lives in PostIt.Desktop.Program.Main and exits before
|
|
||||||
// Avalonia boots — preventing a flash of the MainWindow on
|
|
||||||
// every postit://callback launch. This block is kept for any
|
|
||||||
// entry point that bypasses Program.Main (PostIt.Browser,
|
|
||||||
// PostIt.Android's process lifecycle, ad-hoc tests that build
|
|
||||||
// App directly) and as defence-in-depth in case the Desktop
|
|
||||||
// build is ever reconfigured to skip the early check.
|
|
||||||
if (TryHandOffCustomSchemeUrl()) return;
|
if (TryHandOffCustomSchemeUrl()) return;
|
||||||
|
|
||||||
this.ServiceProvider = BuildServices(new ServiceCollection());
|
this.ServiceProvider = BuildServices(new ServiceCollection());
|
||||||
AttachServiceProvider(ServiceProvider);
|
|
||||||
var settings = ServiceProvider.GetRequiredService<Settings>();
|
var settings = ServiceProvider.GetRequiredService<Settings>();
|
||||||
|
|
||||||
DataTemplates.Clear();
|
DataTemplates.Clear();
|
||||||
DataTemplates.Add(new ViewLocator(ServiceProvider));
|
DataTemplates.Add(new ViewLocator(ServiceProvider));
|
||||||
|
|
||||||
// Wire the Settings singleton onto the SettingsPage singleton
|
|
||||||
// once, at composition time. The page is registered as a
|
|
||||||
// singleton (see above) precisely so this binding is stable
|
|
||||||
// for the lifetime of the app: every push to / pop from the
|
|
||||||
// navigation stack finds the same ContentPage with the same
|
|
||||||
// DataContext, and the TwoWay bindings inside the page keep
|
|
||||||
// mutating the same in-memory Settings instance that the rest
|
|
||||||
// of the app reads (OidcClientOptions construction, etc.).
|
|
||||||
ServiceProvider.GetRequiredService<SettingsPage>().DataContext = settings;
|
|
||||||
|
|
||||||
// Settings.DarkMode was previously a dead field: it round-
|
|
||||||
// tripped through the settings file and the SettingsPage
|
|
||||||
// CheckBox, but no consumer ever read it. Wire it here to
|
|
||||||
// Application.RequestedThemeVariant so the toggle takes
|
|
||||||
// effect immediately, and seed the initial theme from the
|
|
||||||
// value Load() just populated (so a dark-mode user lands on
|
|
||||||
// a dark window on first launch, not on a default-light
|
|
||||||
// window that flips after the user touches the toggle).
|
|
||||||
ApplyDarkMode(settings);
|
|
||||||
settings.PropertyChanged += (_, e) =>
|
|
||||||
{
|
|
||||||
if (e.PropertyName == nameof(Settings.DarkMode))
|
|
||||||
{
|
|
||||||
ApplyDarkMode(settings);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
desktop.MainWindow = CreateMainWindow();
|
desktop.MainWindow = CreateMainWindow();
|
||||||
}
|
}
|
||||||
else if (ApplicationLifetime is IActivityApplicationLifetime singleViewFactoryApplicationLifetime)
|
else if (ApplicationLifetime is IActivityApplicationLifetime singleViewFactoryApplicationLifetime)
|
||||||
{
|
{
|
||||||
singleViewFactoryApplicationLifetime.MainViewFactory = () => CreateMainWindow();
|
singleViewFactoryApplicationLifetime.MainViewFactory =
|
||||||
|
() => CreateMainWindow();
|
||||||
}
|
}
|
||||||
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
|
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
|
||||||
{
|
{
|
||||||
singleViewPlatform.MainView = CreateMainWindow();
|
singleViewPlatform.MainView = CreateMainWindow();
|
||||||
}
|
}
|
||||||
|
ApplyDarkMode(settings);
|
||||||
base.OnFrameworkInitializationCompleted();
|
base.OnFrameworkInitializationCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow window;
|
|
||||||
private MainWindow CreateMainWindow()
|
|
||||||
{
|
|
||||||
window = new MainWindow();
|
|
||||||
var api = ServiceProvider!.GetRequiredService<YavscApiClient>();
|
|
||||||
window.Opened += async (_, _) => await BootAsync(this.ServiceProvider!, api);
|
|
||||||
var sessionStatus = ServiceProvider!.GetRequiredService<SessionStatusViewModel>();
|
|
||||||
sessionStatus.LogoutCompleted += () =>
|
|
||||||
{
|
|
||||||
window.NavRoot.PopToRootAsync();
|
|
||||||
};
|
|
||||||
|
|
||||||
sessionStatus.LoginSucceeded += () =>
|
|
||||||
{
|
|
||||||
PushMainPageAsync();
|
|
||||||
};
|
|
||||||
|
|
||||||
var homeVm = ServiceProvider!.GetRequiredService<HomePageViewModel>();
|
|
||||||
|
|
||||||
this.PushPageAsync(homeVm).Wait();
|
|
||||||
window.SessionBanner.DataContext = sessionStatus;
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Build the DI container the app uses. Pulled out of
|
|
||||||
/// <see cref="OnFrameworkInitializationCompleted"/> so headless
|
|
||||||
/// tests can construct the same container at <c>TestApp</c> boot
|
|
||||||
/// without going through the full Avalonia desktop lifetime
|
|
||||||
/// (which never runs in a unit test). The container returned is
|
|
||||||
/// the exact one production uses — no test-only fakes, no
|
|
||||||
/// trimmed service list — so a test that exercises a VM, page,
|
|
||||||
/// or service resolves through the same wiring the real app
|
|
||||||
/// does, and a green test is a green contract for prod.
|
|
||||||
/// </summary>
|
|
||||||
internal static IServiceProvider BuildServices(ServiceCollection services)
|
internal static IServiceProvider BuildServices(ServiceCollection services)
|
||||||
{
|
{
|
||||||
var settings = new Settings();
|
var settings = new Settings();
|
||||||
|
|
@ -182,14 +110,13 @@ public partial class App : Application
|
||||||
// ViewModels
|
// ViewModels
|
||||||
services.AddSingleton(settings);
|
services.AddSingleton(settings);
|
||||||
services.AddSingleton<YavscApiClient>(api);
|
services.AddSingleton<YavscApiClient>(api);
|
||||||
services.AddSingleton<IYavscApiClient>(api);
|
|
||||||
services.AddSingleton(client);
|
services.AddSingleton(client);
|
||||||
services.AddSingleton(circleClient);
|
services.AddSingleton(circleClient);
|
||||||
services.AddSingleton(blogAclClient);
|
services.AddSingleton(blogAclClient);
|
||||||
services.AddSingleton(userSearchClient);
|
services.AddSingleton(userSearchClient);
|
||||||
services.AddSingleton<IContactService>(contactService);
|
services.AddSingleton<IContactService>(contactService);
|
||||||
services.AddSingleton<IUserDirectory>(userDirectory);
|
services.AddSingleton<IUserDirectory>(userDirectory);
|
||||||
services.AddTransient<MainPageViewModel>();
|
services.AddTransient<MainViewModel>();
|
||||||
services.AddTransient<HomePageViewModel>();
|
services.AddTransient<HomePageViewModel>();
|
||||||
services.AddTransient<SignaturePageViewModel>();
|
services.AddTransient<SignaturePageViewModel>();
|
||||||
services.AddTransient<CirclesPageViewModel>();
|
services.AddTransient<CirclesPageViewModel>();
|
||||||
|
|
@ -204,21 +131,30 @@ public partial class App : Application
|
||||||
return services.BuildServiceProvider();
|
return services.BuildServiceProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private MainWindow CreateMainWindow()
|
||||||
/// Attach a pre-built DI container to this <see cref="App"/>
|
|
||||||
/// instance. Used by headless tests after
|
|
||||||
/// <see cref="BuildServices"/>; in production this happens
|
|
||||||
/// implicitly via <see cref="OnFrameworkInitializationCompleted"/>.
|
|
||||||
/// Idempotent w.r.t. <see cref="Settings.BindToServiceProvider"/>:
|
|
||||||
/// re-binding from a second App boot is a no-op.
|
|
||||||
/// </summary>
|
|
||||||
internal void AttachServiceProvider(IServiceProvider sp)
|
|
||||||
{
|
{
|
||||||
ServiceProvider = sp;
|
window = new MainWindow();
|
||||||
Settings.BindToServiceProvider(sp);
|
var api = ServiceProvider!.GetRequiredService<YavscApiClient>();
|
||||||
|
window.Opened += async (_, _) => await BootAsync(this.ServiceProvider!, api);
|
||||||
|
var sessionStatus = ServiceProvider!.GetRequiredService<SessionStatusViewModel>();
|
||||||
|
sessionStatus.LogoutCompleted += () =>
|
||||||
|
{
|
||||||
|
window.NavRoot.PopToRootAsync();
|
||||||
|
};
|
||||||
|
|
||||||
|
sessionStatus.LoginSucceeded += () =>
|
||||||
|
{
|
||||||
|
PushMainPageAsync();
|
||||||
|
};
|
||||||
|
|
||||||
|
var homeVm = ServiceProvider!.GetRequiredService<HomePageViewModel>();
|
||||||
|
|
||||||
|
this.PushPageAsync(homeVm).Wait();
|
||||||
|
window.SessionBanner.DataContext = sessionStatus;
|
||||||
|
return window;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test-only hook: bind a concrete <see cref="MainWindow"/> so
|
/// Test-only hook: bind a concrete <see cref="MainWindow"/> so
|
||||||
/// command-driven navigation paths (<see cref="PushPage"/>) can
|
/// command-driven navigation paths (<see cref="PushPage"/>) can
|
||||||
/// push onto a real <see cref="NavigationPage"/> in headless
|
/// push onto a real <see cref="NavigationPage"/> in headless
|
||||||
|
|
@ -266,7 +202,7 @@ public partial class App : Application
|
||||||
public static Task PushMainPageAsync()
|
public static Task PushMainPageAsync()
|
||||||
{
|
{
|
||||||
var app = (App)Current!;
|
var app = (App)Current!;
|
||||||
var mainVm = app.ServiceProvider!.GetRequiredService<MainPageViewModel>();
|
var mainVm = app.ServiceProvider!.GetRequiredService<MainViewModel>();
|
||||||
return app.PushPageAsync(mainVm);
|
return app.PushPageAsync(mainVm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
BIN
src/PostIt/PostIt/Assets/avalonia-logo.ico
Normal file
BIN
src/PostIt/PostIt/Assets/avalonia-logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 172 KiB |
|
|
@ -1,32 +1,14 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
|
||||||
<AssemblyVersion>1.1.0.0</AssemblyVersion>
|
|
||||||
<FileVersion>1.1.0.0</FileVersion>
|
|
||||||
<InformationalVersion>1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3</InformationalVersion>
|
|
||||||
<Version>1.1.0-beta.1</Version>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AvaloniaResource Include="Assets\**" />
|
<AvaloniaResource Include="Assets\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Avalonia" />
|
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" />
|
|
||||||
<PackageReference Include="Avalonia.Fonts.Inter" />
|
|
||||||
<PackageReference Include="Avalonia.AvaloniaEdit" />
|
|
||||||
<PackageReference Include="AvaloniaUI.DiagnosticsSupport">
|
|
||||||
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
|
||||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" />
|
|
||||||
<PackageReference Include="IdentityModel.OidcClient" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
|
||||||
<ProjectReference Include="../../Yavsc.Abstract/Yavsc.Abstract.csproj" />
|
|
||||||
<ProjectReference Include="../../Yavsc.Api.Client/Yavsc.Api.Client.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="postit-settings.json">
|
<Content Include="postit-settings.json">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
|
@ -38,6 +20,21 @@
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="GitVersion.MsBuild" />
|
<PackageReference Include="Avalonia" />
|
||||||
|
<PackageReference Include="Avalonia.Themes.Fluent" />
|
||||||
|
<PackageReference Include="Avalonia.Fonts.Inter" />
|
||||||
|
<PackageReference Include="AvaloniaUI.DiagnosticsSupport">
|
||||||
|
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
||||||
|
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="CommunityToolkit.Mvvm" />
|
||||||
|
<PackageReference Include="Avalonia.AvaloniaEdit" />
|
||||||
|
<PackageReference Include="IdentityModel.OidcClient" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="../../Yavsc.Abstract/Yavsc.Abstract.csproj" />
|
||||||
|
<ProjectReference Include="../../Yavsc.Api.Client/Yavsc.Api.Client.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Templates;
|
using Avalonia.Controls.Templates;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
@ -10,7 +11,9 @@ namespace PostIt;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Given a view model, returns the corresponding view if possible.
|
/// Given a view model, returns the corresponding view if possible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[RequiresUnreferencedCode(
|
||||||
|
"Default implementation of ViewLocator involves reflection which may be trimmed away.",
|
||||||
|
Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")]
|
||||||
public class ViewLocator : IDataTemplate
|
public class ViewLocator : IDataTemplate
|
||||||
{
|
{
|
||||||
private readonly IServiceProvider _services;
|
private readonly IServiceProvider _services;
|
||||||
|
|
@ -32,11 +35,12 @@ public class ViewLocator : IDataTemplate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Control BuildCore(object? data)
|
private Control BuildCore(object? data)
|
||||||
{
|
{
|
||||||
return data switch
|
return data switch
|
||||||
{
|
{
|
||||||
MainPageViewModel => _services.GetRequiredService<MainPage>(),
|
MainViewModel => _services.GetRequiredService<MainPage>(),
|
||||||
Settings => _services.GetRequiredService<SettingsPage>(),
|
Settings => _services.GetRequiredService<SettingsPage>(),
|
||||||
HomePageViewModel => _services.GetRequiredService<HomePage>(),
|
HomePageViewModel => _services.GetRequiredService<HomePage>(),
|
||||||
SignaturePageViewModel => _services.GetRequiredService<SignaturePage>(),
|
SignaturePageViewModel => _services.GetRequiredService<SignaturePage>(),
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ using Yavsc.Api.Client;
|
||||||
|
|
||||||
namespace PostIt.ViewModels;
|
namespace PostIt.ViewModels;
|
||||||
|
|
||||||
public partial class MainPageViewModel : ViewModelBase
|
public partial class MainViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
/// <summary>Window/tab title. Cosmetic — bound by
|
/// <summary>Window/tab title. Cosmetic — bound by
|
||||||
/// <c>MainPage.axaml</c> if at all. Not the post title.</summary>
|
/// <c>MainPage.axaml</c> if at all. Not the post title.</summary>
|
||||||
|
|
@ -120,7 +120,7 @@ public partial class MainPageViewModel : ViewModelBase
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public MainPageViewModel()
|
public MainViewModel()
|
||||||
{
|
{
|
||||||
SettingsModel = new Settings();
|
SettingsModel = new Settings();
|
||||||
Init(SettingsModel);
|
Init(SettingsModel);
|
||||||
|
|
@ -170,7 +170,7 @@ public partial class MainPageViewModel : ViewModelBase
|
||||||
/// <see cref="BlogApiClient"/>. Production code uses the
|
/// <see cref="BlogApiClient"/>. Production code uses the
|
||||||
/// (Settings, BlogApiClient) overload below.
|
/// (Settings, BlogApiClient) overload below.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MainPageViewModel(BlogApiClient blogClient, Settings? settings = null, IServiceProvider? services = null)
|
public MainViewModel(BlogApiClient blogClient, Settings? settings = null, IServiceProvider? services = null)
|
||||||
{
|
{
|
||||||
SettingsModel = new Settings();
|
SettingsModel = new Settings();
|
||||||
BlogClient = blogClient ?? throw new ArgumentNullException(nameof(blogClient)); ;
|
BlogClient = blogClient ?? throw new ArgumentNullException(nameof(blogClient)); ;
|
||||||
|
|
@ -64,24 +64,6 @@ public partial class Settings : ViewModelBase
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the canonical Settings instance previously bound through
|
|
||||||
/// <see cref="BindToServiceProvider"/>, or <c>null</c> when called
|
|
||||||
/// outside a running Avalonia application (tests, CLI tools).
|
|
||||||
/// </summary>
|
|
||||||
public static Settings? GetCurrent() => Volatile.Read(ref s_current);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resolve the canonical Settings instance or throw. Use this in
|
|
||||||
/// production code paths that must not silently fall back to a
|
|
||||||
/// freshly-constructed <see cref="Settings"/> (which used to be
|
|
||||||
/// the root cause of the postit://callback crash: two Settings
|
|
||||||
/// instances racing on PropertyChanged from different threads).
|
|
||||||
/// </summary>
|
|
||||||
public static Settings RequireCurrent() =>
|
|
||||||
GetCurrent() ?? throw new InvalidOperationException(
|
|
||||||
"Settings.Current is not bound. Call App.OnFrameworkInitializationCompleted first.");
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
public partial AuthenticationSettings Authentication { get; set; } = new();
|
public partial AuthenticationSettings Authentication { get; set; } = new();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ namespace PostIt.ViewModels;
|
||||||
|
|
||||||
public abstract partial class ViewModelBase : ObservableObject
|
public abstract partial class ViewModelBase : ObservableObject
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets if the user can navigate to the next page
|
/// Gets if the user can navigate to the next page
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@
|
||||||
xmlns:AvaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
|
xmlns:AvaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
x:Class="PostIt.Views.MainPage"
|
x:Class="PostIt.Views.MainPage"
|
||||||
x:DataType="vm:MainPageViewModel"
|
x:DataType="vm:MainViewModel"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch">
|
VerticalAlignment="Stretch">
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<vm:MainPageViewModel />
|
<vm:MainViewModel />
|
||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
|
|
||||||
<Grid Margin="12" RowSpacing="12"
|
<Grid Margin="12" RowSpacing="12"
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,17 @@
|
||||||
<Window xmlns="https://github.com/avaloniaui"
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:vm="using:PostIt.ViewModels"
|
xmlns:vm="using:PostIt.ViewModels"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:views="using:PostIt.Views"
|
xmlns:views="using:PostIt.Views"
|
||||||
x:DataType="vm:MainPageViewModel"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="PostIt.Views.MainWindow"
|
x:Class="PostIt.Views.MainWindow"
|
||||||
>
|
Icon="/Assets/avalonia-logo.ico"
|
||||||
|
Title="PostIt" >
|
||||||
|
|
||||||
<!--
|
|
||||||
Root layout: persistent session banner on top, navigation
|
|
||||||
surface below. The banner is the single source of truth for
|
|
||||||
"Connecté / Déconnecté" and the logout button — visible on
|
|
||||||
every page (HomePage, MainPage) so the user never has to dig
|
|
||||||
through a menu to find their session state.
|
|
||||||
|
|
||||||
The navigation stack is built programmatically in
|
|
||||||
App.OnFrameworkInitializationCompleted: HomePage is the
|
|
||||||
root, MainPage is pushed on top when the silent refresh
|
|
||||||
succeeds at boot or after a fresh login from HomePage.
|
|
||||||
-->
|
|
||||||
<DockPanel LastChildFill="True">
|
<DockPanel LastChildFill="True">
|
||||||
<views:SessionStatusBanner x:Name="SessionBanner"
|
<views:SessionStatusBanner x:Name="SessionBanner"
|
||||||
DockPanel.Dock="Bottom"/>
|
DockPanel.Dock="Bottom"/>
|
||||||
<NavigationPage x:Name="NavRoot"/>
|
<NavigationPage x:Name="NavRoot"/>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
||||||
</Window>
|
</Window>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue