using System.Text.Json; using XFEEDToolBox.Core.Notifications; using XFEEDToolBox.Services.Arrival; using XFEEDToolBox.Views; using Windows.Graphics.Imaging; namespace XFEEDToolBox; public partial class App { private ArrivalNotifications? arrivalNotifications; private IDisposable? arrivalSubscription; private readonly GameAnnouncementObserver announcementObserver=new(); private void OnGameAnnouncement(XFEEDToolBox.Core.Data.ObservedJournalEvent observed,XFEEDToolBox.Core.Models.PlayerState player) { var notice=announcementObserver.Observe(observed,DateTimeOffset.UtcNow); if(notice is null)return; Runtime.Log("游戏公告",notice.Message); MainWindow.DispatcherQueue.TryEnqueue(()=>arrivalNotifications?.Send(notice)); } private void InitializeArrivalNotifications(bool enabled) { if(!enabled)return; arrivalNotifications=new(Runtime,MainWindow.DispatcherQueue,page=> { AppShellPage.Current?.Navigate(page switch{"home"=>typeof(MainPage),"current-system"=>typeof(CurrentSystemPage),_=>typeof(NavigationPage)}); if(MainWindow.AppWindow.Presenter is Microsoft.UI.Windowing.OverlappedPresenter presenter &&presenter.State==Microsoft.UI.Windowing.OverlappedPresenterState.Minimized)presenter.Restore(); MainWindow.Activate(); }); if(arrivalNotifications.Start()) { Runtime.LiveJournalEvent+=OnGameAnnouncement; arrivalSubscription=ViewModel.ObserveArrivals( (arrival,pid)=>arrivalNotifications.Send(arrival,pid,()=>Runtime.Player), (scene,pid)=>arrivalNotifications.CaptureStar(scene,pid,()=>Runtime.Player), (ready,pid)=>arrivalNotifications.Send(ready,pid,()=>Runtime.Player)); } } private void StopArrivalNotifications(){Runtime.LiveJournalEvent-=OnGameAnnouncement;arrivalSubscription?.Dispose();arrivalNotifications?.Dispose();} private static async Task RunArrivalNotificationProbeAsync(string path) { var directory=Path.GetDirectoryName(Path.GetFullPath(path))!;Directory.CreateDirectory(directory); string? failure=null;object? result=null; try { if(!ViewModel.OfflinePreview)throw new InvalidOperationException("Notification preview requires an isolated offline data directory."); using var source=File.OpenRead(Path.Combine(AppContext.BaseDirectory,"Assets","Home","navigation-orbit.png")); var decoder=await BitmapDecoder.CreateAsync(source.AsRandomAccessStream()); var data=await decoder.GetPixelDataAsync(BitmapPixelFormat.Bgra8,BitmapAlphaMode.Ignore,new BitmapTransform(),ExifOrientationMode.IgnoreExifOrientation,ColorManagementMode.DoNotColorManage); var frame=new ArrivalFrame((int)decoder.PixelWidth,(int)decoder.PixelHeight,data.DetachPixelData()); var arrival=new NavigationArrival(Guid.NewGuid().ToString("N"),42,"Sol","Sol · 离线样例",false,false,"fixture",DateTimeOffset.UtcNow,DateTimeOffset.UtcNow); var image=Path.Combine(directory,"arrival-hero.png");await ArrivalHeroCard.SaveAsync(ArrivalHeroCard.Render(frame,arrival),image); var longTitle=arrival with{Destination="Col 285 Sector MC-I b11-2 · 长名称预览"}; await ArrivalHeroCard.SaveAsync(ArrivalHeroCard.Render(frame,longTitle),Path.Combine(directory,"arrival-long-name.png")); await File.WriteAllTextAsync(Path.Combine(directory,"arrival-toast.xml"),ArrivalToastContent.Build(arrival,image)); var ready=new SessionReadyNotification(Guid.NewGuid().ToString("N"),"已重新进入游戏","一键大退已完成 · Sol · 离线样例",arrival); await ArrivalHeroCard.SaveAsync(ArrivalHeroCard.Render(frame,ready),Path.Combine(directory,"session-ready-hero.png")); await File.WriteAllTextAsync(Path.Combine(directory,"session-ready-toast.xml"),ArrivalToastContent.Build(ready)); bool? platformSupported=null;string? platformError=null; try{platformSupported=Microsoft.Windows.AppNotifications.AppNotificationManager.IsSupported();}catch(Exception error){platformError=error.Message;} string captureStatus="尚未确认";string? captureError=null;object? captureMetrics=null; try { MainWindow.Content=new Grid{Background=new Microsoft.UI.Xaml.Media.SolidColorBrush(Windows.UI.Color.FromArgb(255,4,12,22)),Children= {new Image{Source=new Microsoft.UI.Xaml.Media.Imaging.BitmapImage(new Uri(Path.Combine(AppContext.BaseDirectory,"Assets","Home","navigation-orbit.png"))),Stretch=Microsoft.UI.Xaml.Media.Stretch.UniformToFill}}}; MainWindow.AppWindow.Move(new(-30000,-30000));MainWindow.AppWindow.Resize(new(1100,650));MainWindow.AppWindow.Show(false); await Task.Delay(400);((FrameworkElement)MainWindow.Content).UpdateLayout(); var captured=await ArrivalWindowCapture.CaptureOwnPreviewAsync(CancellationToken.None); if(captured is not null) { captureStatus="captured";captureMetrics=new{captured.Width,captured.Height,bytes=captured.Bgra.Length}; await ArrivalHeroCard.SaveAsync(ArrivalHeroCard.Render(captured,arrival),Path.Combine(directory,"window-capture-hero.png")); } } catch(Exception error){captureError=error.ToString();} finally{MainWindow.AppWindow.Hide();} var nativeRegistered=false;var nativeShown=false;var nativeHistory=false;uint nativeId=0;string? nativeError=null;var nativeClicks=0; if(Environment.GetCommandLineArgs().Contains("--test-windows-notification")) { var manager=Microsoft.Windows.AppNotifications.AppNotificationManager.Default; void Invoked(Microsoft.Windows.AppNotifications.AppNotificationManager sender,Microsoft.Windows.AppNotifications.AppNotificationActivatedEventArgs args)=>Interlocked.Increment(ref nativeClicks); manager.NotificationInvoked+=Invoked; try { manager.Register("XFE ED 工具箱",new Uri(Path.Combine(AppContext.BaseDirectory,"Assets","icon.png")));nativeRegistered=true; var content=System.Xml.Linq.XElement.Parse(ArrivalToastContent.Build(arrival,image)); content.Descendants("text").First().Value="测试通知 · 已抵达 Sol(离线样例)"; var notification=new Microsoft.Windows.AppNotifications.AppNotification(content.ToString(System.Xml.Linq.SaveOptions.DisableFormatting)) {Tag="arrival-test",Group="arrival-test",Expiration=DateTimeOffset.UtcNow.AddMinutes(5)}; manager.Show(notification);nativeShown=true;nativeId=notification.Id; nativeHistory=(await manager.GetAllAsync()).Any(n=>n.Id==nativeId&&n.Id>0); await File.WriteAllTextAsync(Path.Combine(directory,"notification-queued.json"),JsonSerializer.Serialize(new{nativeId,nativeHistory,previewOnly=true})); await Task.Delay(12000); } catch(Exception error){nativeError=error.ToString();} finally { if(nativeId>0)try{await manager.RemoveByIdAsync(nativeId);}catch(Exception error){nativeError??=error.ToString();} manager.NotificationInvoked-=Invoked;if(nativeRegistered)manager.Unregister(); } } result=new{image,width=ArrivalHeroCard.Width,height=ArrivalHeroCard.Height,fixtureOnly=true, backgroundSource="Bundled offline artwork, not a game screenshot",nativeRegistrationInvoked=nativeRegistered,nativeShowInvoked=nativeShown,gameCaptureInvoked=false, nativeId,nativeHistory,nativeError,nativeClicks,platformSupported,platformError,captureStatus,captureError,captureMetrics,actualWindowsPresentation="尚未确认",gameInputsInvoked=false}; } catch(Exception error){failure=error.ToString();} await File.WriteAllTextAsync(path,JsonSerializer.Serialize(new{result,failure},new JsonSerializerOptions{WriteIndented=true}));MainWindow.Close(); } }