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();
}
}
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();
}
}