/// <summary> /// Initializes a new instance of the <see cref="ImageEditorViewModel" /> class. /// </summary> /// <param name="imageService">Service for image loading and manipulation.</param> public ImageEditorViewModel(IImageService imageService = null) { _imageService = imageService ?? Locator.Current.GetService <IImageService>(); IObservable <ImageHandle> selectionChanges = this.WhenAnyValue(x => x.SelectedImage).Publish().RefCount(); ImageExplorer = new ImageExplorerViewModel(_source.AsObservableCache()); ImagePreview = new ImagePreviewViewModel(selectionChanges); Settings = new ImageSettingsViewModel(selectionChanges); SelectFolder = new Interaction <string, string>(); LoadImage = ReactiveCommand.CreateFromObservable <string, ImageHandle>(x => _imageService.LoadImage(x)); ExportSelected = ReactiveCommand.CreateFromObservable(ExportSelectedImpl); ExportAll = ReactiveCommand.CreateFromObservable(ExportAllImpl); _calculatePreview = ReactiveCommand.CreateFromObservable <ImageHandle, ImageHandle>(x => _imageService.CalculatePreview(x)); var connection = _source.Connect(); this.WhenActivated(d => { // Dispose handles removed from the source collection connection .DisposeMany() .ObserveOn(RxApp.MainThreadScheduler) .Bind(out _images) .Subscribe() .DisposeWith(d); // Recaluclate image when quality changes connection.WhenPropertyChanged(x => x.ManipulationState.Quality, false) .Throttle(TimeSpan.FromMilliseconds(50)) .Select(x => x.Sender) .InvokeCommand(_calculatePreview) .DisposeWith(d); // Notify about successful export. ExportSelected .Do(name => this.Notify().PublishInfo($"Image export to {name}", "Export completed!", TimeSpan.FromSeconds(3))) .Subscribe() .DisposeWith(d); // Notify about successful export. ExportAll .Do(path => this.Notify().PublishInfo($"All images exported to {path}", "Export completed!", TimeSpan.FromSeconds(3))) .Subscribe() .DisposeWith(d); // Add loaded images to source LoadImage .ObserveOn(RxApp.TaskpoolScheduler) .Where(x => x != null) .SelectMany(x => _calculatePreview.Execute(x)) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(handle => _source.AddOrUpdate(handle)) .DisposeWith(d); // Show image loading error LoadImage.ThrownExceptions .OfType <ImageLoadingException>() .Subscribe(ex => this.Notify() .PublishError($"Sorry. \"{ex.FilePath}\" does not have a supported file format.", "Error", TimeSpan.FromSeconds(5))) .DisposeWith(d); // Pipe loadings to property Observable.CombineLatest( LoadImage.IsExecuting, ExportSelected.IsExecuting, ExportAll.IsExecuting, _calculatePreview.IsExecuting, (a, b, c, d) => a || b || c || d) .ObserveOn(RxApp.MainThreadScheduler) .ToPropertyEx(this, x => x.IsLoading) .DisposeWith(d); // React on close requests from explorer view this.WhenAnyObservable(x => x.ImageExplorer.DeletionRequests) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(x => _source.Remove(x)) .DisposeWith(d); // Select explorer item this.WhenAnyObservable(x => x.ImageExplorer.Selections) .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(x => SelectedImage = x) .DisposeWith(d); }); }