using System; using System.IO; using System.Threading.Tasks; using Avalonia.Controls.Notifications; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Platform.Storage; using CommunityToolkit.Mvvm.Input; using DynamicData; using DynamicData.Binding; using Microsoft.Extensions.Logging; using SkiaSharp; using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.Helpers; using StabilityMatrix.Avalonia.Models; using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.ViewModels.Dialogs; using StabilityMatrix.Avalonia.Views.Dialogs; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Models.Database; using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Processes; using StabilityMatrix.Core.Services; using SortDirection = DynamicData.Binding.SortDirection; namespace StabilityMatrix.Avalonia.ViewModels.Inference; [View(typeof(ImageFolderCard))] public partial class ImageFolderCardViewModel : ViewModelBase { private readonly ILogger logger; private readonly IImageIndexService imageIndexService; private readonly ISettingsManager settingsManager; private readonly INotificationService notificationService; /// /// Source of image files to display /// private readonly SourceCache localImagesSource = new(imageFile => imageFile.RelativePath); /// /// Collection of image items to display /// public IObservableCollection Items { get; } = new ObservableCollectionExtended(); /// /// Collection of local image files /// public IObservableCollection LocalImages { get; } = new ObservableCollectionExtended(); public ImageFolderCardViewModel( ILogger logger, IImageIndexService imageIndexService, ISettingsManager settingsManager, INotificationService notificationService ) { this.logger = logger; this.imageIndexService = imageIndexService; this.settingsManager = settingsManager; this.notificationService = notificationService; // var minDatetime = DateTimeOffset.FromUnixTimeMilliseconds(0); /*localImagesSource .Connect() .DeferUntilLoaded() .Transform( imageFile => new ImageFolderCardItemViewModel { LocalImageFile = imageFile, ImagePath = Design.IsDesignMode ? imageFile.RelativePath : imageFile.GetFullPath(settingsManager.ImagesDirectory) } ) .SortBy(x => x.LocalImageFile?.LastModifiedAt ?? minDatetime, SortDirection.Descending) .Bind(Items) .Subscribe();*/ localImagesSource .Connect() .DeferUntilLoaded() .SortBy(file => file.LastModifiedAt, SortDirection.Descending) .Bind(LocalImages) .Subscribe(); } /// public override async Task OnLoadedAsync() { await base.OnLoadedAsync(); await imageIndexService.RefreshIndex("Inference"); var imageFiles = await imageIndexService.GetLocalImagesByPrefix("Inference"); localImagesSource.Edit(x => { x.Load(imageFiles); }); } /// /// Handles image clicks to show preview /// [RelayCommand] private async Task OnImageClick(LocalImageFile item) { if (item.GetFullPath(settingsManager.ImagesDirectory) is not { } imagePath) { return; } var image = new ImageSource(new FilePath(imagePath)); // Preload await image.GetBitmapAsync(); var dialog = new BetterContentDialog { MaxDialogWidth = 1000, MaxDialogHeight = 1000, FullSizeDesired = true, IsFooterVisible = false, ContentVerticalScrollBarVisibility = ScrollBarVisibility.Disabled, Content = new ImageViewerDialog { Width = 1000, Height = 1000, DataContext = new ImageViewerViewModel { ImageSource = image } } }; await dialog.ShowAsync(); } /// /// Handles clicks to the image delete button /// [RelayCommand] private async Task OnImageDelete(LocalImageFile? item) { if (item?.GetFullPath(settingsManager.ImagesDirectory) is not { } imagePath) { return; } // Delete the file var result = await notificationService.TryAsync(new FilePath(imagePath).DeleteAsync()); if (!result.IsSuccessful) { return; } // Remove from source localImagesSource.Remove(item); // Remove from index await imageIndexService.RemoveImage(item); } /// /// Handles clicks to the image delete button /// [RelayCommand] private async Task OnImageCopy(LocalImageFile? item) { if (item?.GetFullPath(settingsManager.ImagesDirectory) is not { } imagePath) { return; } var clipboard = App.Clipboard; var dataObject = new DataObject(); // TODO: Not working currently dataObject.Set(DataFormats.Files, $"file:///{imagePath}"); await clipboard.SetDataObjectAsync(dataObject); } /// /// Handles clicks to the image open-in-explorer button /// [RelayCommand] private async Task OnImageOpen(LocalImageFile? item) { if (item?.GetFullPath(settingsManager.ImagesDirectory) is not { } imagePath) { return; } await ProcessRunner.OpenFileBrowser(imagePath); } /// /// Handles clicks to the image export button /// private async Task ImageExportImpl( LocalImageFile? item, SKEncodedImageFormat format, bool includeMetadata = false ) { if (item?.GetFullPath(settingsManager.ImagesDirectory) is not { } sourcePath) { return; } var sourceFile = new FilePath(sourcePath); var formatName = format.ToString(); var storageFile = await App.StorageProvider.SaveFilePickerAsync( new FilePickerSaveOptions { Title = "Export Image", ShowOverwritePrompt = true, SuggestedFileName = item.FileNameWithoutExtension, DefaultExtension = formatName.ToLowerInvariant(), FileTypeChoices = new FilePickerFileType[] { new(formatName) { Patterns = new[] { $"*.{formatName.ToLowerInvariant()}" }, MimeTypes = new[] { $"image/{formatName.ToLowerInvariant()}" } } } } ); if (storageFile?.TryGetLocalPath() is not { } targetPath) { return; } var targetFile = new FilePath(targetPath); try { if (format is SKEncodedImageFormat.Png) { // For include metadata, just copy the file if (includeMetadata) { await sourceFile.CopyToAsync(targetFile, true); } else { // Otherwise read and strip the metadata var imageBytes = await sourceFile.ReadAllBytesAsync(); imageBytes = PngDataHelper.RemoveMetadata(imageBytes); await targetFile.WriteAllBytesAsync(imageBytes); } } else { await Task.Run(() => { using var fs = sourceFile.Info.OpenRead(); var image = SKImage.FromEncodedData(fs); fs.Dispose(); using var targetStream = targetFile.Info.OpenWrite(); image.Encode(format, 100).SaveTo(targetStream); }); } } catch (IOException e) { logger.LogWarning(e, "Failed to export image"); notificationService.ShowPersistent( "Failed to export image", e.Message, NotificationType.Error ); return; } notificationService.Show( "Image Exported", $"Saved to {targetPath}", NotificationType.Success ); } [RelayCommand] private Task OnImageExportPng(LocalImageFile? item) => ImageExportImpl(item, SKEncodedImageFormat.Png); [RelayCommand] private Task OnImageExportPngWithMetadata(LocalImageFile? item) => ImageExportImpl(item, SKEncodedImageFormat.Png, true); [RelayCommand] private Task OnImageExportJpeg(LocalImageFile? item) => ImageExportImpl(item, SKEncodedImageFormat.Jpeg); [RelayCommand] private Task OnImageExportWebp(LocalImageFile? item) => ImageExportImpl(item, SKEncodedImageFormat.Webp); }