Files
Hua.Todo/src/Hua.Todo.Maui/Views/MainPage.xaml.cs
T
2026-04-06 22:59:16 +08:00

151 lines
5.6 KiB
C#

using Microsoft.Maui.Controls;
using Hua.Todo.Maui.Models;
using Hua.Todo.Maui.Services;
namespace Hua.Todo.Maui.Views
{
/// <summary>
/// 应用程序主页面。
/// 该页面承载 WebView(前端 UI),并通过 JavaScript 注入与事件机制实现与 MAUI 的通讯。
/// </summary>
public partial class MainPage : ContentPage
{
private readonly AppSettings _appSettings;
private readonly IEmbeddedWebServerService? _webServer;
/// <summary>
/// 创建 <see cref="MainPage"/>。
/// </summary>
/// <param name="appSettings">应用配置。</param>
/// <param name="webServer">嵌入式 Web 服务器服务(在不同平台可能为不同实现)。</param>
public MainPage(AppSettings appSettings, IEmbeddedWebServerService webServer)
{
InitializeComponent();
_appSettings = appSettings;
_webServer = webServer;
SetupWebViewSource();
SetupWebViewCommunication();
SetupKeyboardHandler();
}
/// <summary>
/// 设置 WebView 数据源。
/// 当启用嵌入式服务器且使用静态文件时,直接指向本地服务器;否则使用配置的前端 URL。
/// </summary>
private void SetupWebViewSource()
{
if (_appSettings.WebServer.IsUsingStatic)
{
if (_webServer != null)
{
MainWebView.Source = _webServer.BaseUrl;
return;
}
}
MainWebView.Source = NormalizeUrl(_appSettings.WebServer.ForEndUrl);
}
/// <summary>
/// 设置键盘处理器(平台差异通过分部类实现)。
/// </summary>
private void SetupKeyboardHandler()
{
PlatformSetupKeyboardHandler();
}
/// <summary>
/// 当 Esc 键按下时的回调
/// </summary>
private void OnEscKeyPressed(object? sender, EventArgs e)
{
var window = Microsoft.Maui.Controls.Application.Current?.Windows.FirstOrDefault();
if (window != null)
{
PlatformOnEscKeyPressed(window);
}
}
/// <summary>
/// 设置 WebView 通讯逻辑。
/// 约定:
/// - 通过 window.__API_BASE_URL__ 注入后端 API 基地址(仅在嵌入式服务器运行时)
/// - 通过自定义事件在 Web 与 MAUI 间传递热键配置等数据
/// </summary>
private void SetupWebViewCommunication()
{
MainWebView.Navigated += async (s, e) =>
{
#if DEBUG
if (e.Result != WebNavigationResult.Success)
{
await DisplayAlertAsync("加载失败", $"{e.Url}\n{e.Result}", "OK");
}
#endif
if (_webServer is { IsRunning: true })
{
var apiBase = $"{_webServer.BaseUrl.TrimEnd('/')}/api";
await MainWebView.EvaluateJavaScriptAsync($"window.__API_BASE_URL__ = '{apiBase}';");
}
// 注入前端与 MAUI 的通讯桥(事件名/字段名属于协议的一部分,修改需同步前端)。
await MainWebView.EvaluateJavaScriptAsync(@"
window.mauiInterop = {
onHotKeyConfigUpdated: null,
openHotKeySettings: function(config) {
const event = new CustomEvent('openHotKeySettings', { detail: config });
window.dispatchEvent(event);
},
updateHotKeyConfig: function(modifiers, key, isEnabled) {
const event = new CustomEvent('updateHotKeyConfig', {
detail: { modifiers, key, isEnabled }
});
window.dispatchEvent(event);
}
};
window.addEventListener('hotKeyConfigChanged', function(e) {
if (window.mauiInterop.onHotKeyConfigUpdated) {
window.mauiInterop.onHotKeyConfigUpdated(e.detail);
}
});
");
};
}
/// <summary>
/// 规格化 URL(针对 Android 模拟器处理 localhost)。
/// Android 模拟器中 localhost 指向模拟器自身,需要替换为 10.0.2.2 才能访问宿主机服务。
/// </summary>
private static string NormalizeUrl(string url)
{
if (string.IsNullOrWhiteSpace(url)) return url;
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)) return url;
var host = uri.Host;
if (host != "localhost" && host != "127.0.0.1") return url;
if (DeviceInfo.Platform == DevicePlatform.Android && DeviceInfo.DeviceType == DeviceType.Virtual)
{
var builder = new UriBuilder(uri) { Host = "10.0.2.2" };
return builder.Uri.ToString();
}
return url;
}
// 平台特定分部方法声明(实现位于 Platforms/* 目录)
/// <summary>
/// 平台特定的键盘处理器初始化。
/// </summary>
partial void PlatformSetupKeyboardHandler();
/// <summary>
/// 平台特定的 Esc 键处理逻辑。
/// </summary>
/// <param name="window">当前窗口。</param>
partial void PlatformOnEscKeyPressed(Window window);
}
}