📄 ui/src/Services/DesktopServices.cs
using System;
using System.IO;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Platform.Storage;

namespace Cfa.Ui.Services;

public interface IDesktopServices
{
    Task<FileInfo?> PickSolutionAsync();
    Task ShowErrorAsync(string message);
    Task OpenSourceAsync(string link);
}

public sealed class DesktopServices(Window owner) : IDesktopServices
{
    public async Task<FileInfo?> PickSolutionAsync()
    {
        var files = await owner.StorageProvider.OpenFilePickerAsync(
            new FilePickerOpenOptions
            {
                Title = "Open Solution",
                AllowMultiple = false,
                FileTypeFilter = [new FilePickerFileType("C# solutions") { Patterns = ["*.sln", "*.slnx", "*.slnf"] }],
            }
        );
        return files.Count > 0 && files[0].TryGetLocalPath() is { } path ? new FileInfo(path) : null;
    }

    public async Task OpenSourceAsync(string link)
    {
        if (!await owner.Launcher.LaunchUriAsync(new Uri(link)))
        {
            throw new InvalidOperationException("The operating system could not open this source link: " + link);
        }
    }

    public Task ShowErrorAsync(string message)
    {
        var close = new Button { Content = "Close", HorizontalAlignment = HorizontalAlignment.Right };
        var dialog = new Window
        {
            Title = "Code Flow Analysis",
            Width = 560,
            Height = 260,
            CanResize = true,
            WindowStartupLocation = WindowStartupLocation.CenterOwner,
            Content = new Grid
            {
                Margin = new Thickness(24),
                RowDefinitions = new RowDefinitions("*,Auto"),
                Children =
                {
                    new ScrollViewer
                    {
                        Content = new TextBlock { Text = message, TextWrapping = Avalonia.Media.TextWrapping.Wrap },
                    },
                    close,
                },
            },
        };
        Grid.SetRow(close, 1);
        close.Click += (_, _) => dialog.Close();
        return dialog.ShowDialog(owner);
    }
}