/// <summary> /// コンストラクタ /// </summary> /// <example> /// <code> /// public ConcreteViewModel() : base(new ConcreteRecipe()) { /// } /// </code> /// </example> /// <param name="settings"></param> /// <param name="logger"></param> /// <param name="recipe">具象クラスで生成したレシピモデルインスタンス</param> protected RecipeViewModelBase(IBaseSettings settings, ILogger logger, IRecipe recipe) { this._settings = settings; this._logger = logger; this.Recipe = recipe.AddTo(this.CompositeDisposable); // Property this.IngredientDisplayMode = this._settings.States.IngredientDisplayMode.ToReadOnlyReactiveProperty().AddTo(this.CompositeDisposable); this.Id = this.Recipe.Id.ToReactivePropertyAsSynchronized(x => x.Value).AddTo(this.CompositeDisposable); this.Url = this.Recipe.Url.ToReactivePropertyAsSynchronized(x => x.Value).AddTo(this.CompositeDisposable); this.Title = this.Recipe.Title.ToReactivePropertyAsSynchronized(x => x.Value).AddTo(this.CompositeDisposable); this.Photo = this.Recipe.Photo.ToReactivePropertyAsSynchronized(x => x.Value).AddTo(this.CompositeDisposable); this.PhotoFilePath = this.Recipe .PhotoFilePath .Where(x => x != null) .Select(x => Path.Combine(this._settings.GeneralSettings.ImageDirectoryPath, x)) .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); this.Thumbnail = this.Recipe.Thumbnail.ToReactivePropertyAsSynchronized(x => x.Value).AddTo(this.CompositeDisposable); this.ThumbnailFilePath = this.Recipe .ThumbnailFilePath .Where(x => x != null) .Select(x => Path.Combine(this._settings.GeneralSettings.ImageDirectoryPath, x)) .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); this.Description = this.Recipe.Description.ToReactivePropertyAsSynchronized(x => x.Value).AddTo(this.CompositeDisposable); this.Yield = this.Recipe.Yield.ToReactivePropertyAsSynchronized(x => x.Value).AddTo(this.CompositeDisposable); this.AdjustmentedYield = this.Recipe.AdjustedYeild.ToReadOnlyReactiveProperty().AddTo(this.CompositeDisposable); this.Ingredients = this.Recipe.Ingredients.ToReadOnlyReactiveCollection().AddTo(this.CompositeDisposable); this.ShoppingList = this.Recipe.ShoppingList.ToReadOnlyReactiveProperty().AddTo(this.CompositeDisposable); this.ShoppingInformationIncludedIngredients = this.Recipe.ShoppingInformationIncludedIngredients.ToReadOnlyReactiveProperty().AddTo(this.CompositeDisposable); this.Steps = this.Recipe.Steps.ToReadOnlyReactiveCollection().AddTo(this.CompositeDisposable); this.Adjustment = this.Recipe.Adjustment.ToReactivePropertyAsSynchronized(x => x.Value).AddTo(this.CompositeDisposable); this.Memo = this.Recipe.Memo.ToReactivePropertyAsSynchronized(x => x.Value).AddTo(this.CompositeDisposable); this.RequiredTime = this.Recipe.RequiredTime.ToReactivePropertyAsSynchronized(x => x.Value).AddTo(this.CompositeDisposable); this.RegistrationDate = this.Recipe.RegistrationDate.ToReactivePropertyAsSynchronized(x => x.Value).AddTo(this.CompositeDisposable); this.Tags = this.Recipe.Tags.ToReactivePropertyAsSynchronized(x => x.Value).AddTo(this.CompositeDisposable); this.Ratings = this.Recipe.Ratings.ToReadOnlyReactiveCollection().AddTo(this.CompositeDisposable); this.IsArchived = this.Recipe.IsArchived.ToReactivePropertyAsSynchronized(x => x.Value).AddTo(this.CompositeDisposable); // Command // レシピダウンロード this.DownloadCommand.Subscribe(async messenger => { var disposable = this.Recipe.FailedNotification .Where(x => x.Behavior == Behavior.Download) .Subscribe(x => { this._logger.Log(LogLevel.Warning, $"レシピダウンロード失敗 レシピURL={x.Sender.Url.Value}", x.Exception); using (var vm = new DialogWindowViewModel("失敗通知", "レシピのダウンロードに失敗しました。", DialogWindowViewModel.DialogResult.Ok)) { (messenger ?? this.Messenger).Raise(new TransitionMessage(vm, "OpenDialogWindow")); } }); using (disposable) { await this.Recipe.DownloadRecipeAsync(); } }).AddTo(this.CompositeDisposable); // レシピ登録 this.RegisterRecipeCommand.Subscribe(async messenger => { var disposable = this.Recipe.FailedNotification .Where(x => x.Behavior == Behavior.Register) .Subscribe(x => { this._logger.Log(LogLevel.Warning, $"レシピ登録失敗 レシピID={x.Sender.Id.Value}", x.Exception); using (var vm = new DialogWindowViewModel("失敗通知", "レシピの登録に失敗しました。", DialogWindowViewModel.DialogResult.Ok)) { (messenger ?? this.Messenger).Raise(new TransitionMessage(vm, "OpenDialogWindow")); } }); using (disposable) { await this.Recipe.RegistAsync(); } }).AddTo(this.CompositeDisposable); // レシピ削除 this.DeleteRecipeCommand.Subscribe(async messenger => { var disposable = this.Recipe.FailedNotification .Where(x => x.Behavior == Behavior.Delete) .Subscribe(x => { this._logger.Log(LogLevel.Warning, $"レシピ削除失敗 レシピID={x.Sender.Id.Value}", x.Exception); using (var vm = new NotifyWindowViewModel("このレシピを使用している食事があるため、レシピの削除に失敗しました。", 5)) { (messenger ?? this.Messenger).Raise(new TransitionMessage(vm, "OpenNotifyWindow")); } }); using (disposable) { await this.Recipe.DeleteAsync(); } }).AddTo(this.CompositeDisposable); // レシピビュワーで表示 this.OpenRecipeDetailCommand.Subscribe(() => { settings.States.RecipesInRecipeViewer.Add(new RecipeInformation(this.Recipe.Id.Value, this.Adjustment.Value)); }).AddTo(this.CompositeDisposable); // レシピ読み込み this.LoadRecipeCommand = this.Id.Select(x => x != 0).ToReactiveCommand().AddTo(this.CompositeDisposable); this.LoadRecipeCommand.ObserveOnUIDispatcher().Subscribe(_ => this.Recipe.Load()).AddTo(this.CompositeDisposable); // レシピ材料追加 this.InsertIngredientCommand.Subscribe(index => { this.Recipe.InsertIngredient(index); }).AddTo(this.CompositeDisposable); // レシピ手順追加 this.InsertStepCommand.Subscribe(index => { this.Recipe.InsertStep(index); }).AddTo(this.CompositeDisposable); // レシピ材料削除 this.RemoveIngredientCommand.Subscribe(index => { this.Recipe.RemoveIngredientAt(index); }).AddTo(this.CompositeDisposable); // レシピ手順削除 this.RemoveStepCommand.Subscribe(index => { this.Recipe.RemoveStepAt(index); }).AddTo(this.CompositeDisposable); // アーカイブ this.ArchiveCommand = this.IsArchived.Select(x => !x).ToReactiveCommand().AddTo(this.CompositeDisposable); this.ArchiveCommand.Subscribe(this.Recipe.Archive).AddTo(this.CompositeDisposable); // アーカイブ解除 this.UnarchiveCommand = this.IsArchived.ToReactiveCommand().AddTo(this.CompositeDisposable); this.UnarchiveCommand.Subscribe(this.Recipe.Unarchive).AddTo(this.CompositeDisposable); // エラー時対処 this.Recipe.FailedNotification.Where(x => x.Behavior == Behavior.Load).Subscribe(x => { // 抽象化の諦め // 他クライアントからの変更通知で例外が発生した場合、メッセージを表示するための画面を特定できず、LivetのMessengerを渡すのが困難なため。 this._logger.Log(LogLevel.Warning, $"レシピ読み込み失敗 レシピID={x.Sender.Id.Value}", x.Exception); using (var vm = new DialogWindowViewModel("失敗通知", "レシピの読み込みに失敗しました。", DialogWindowViewModel.DialogResult.Ok)) { var dialog = new DialogWindow() { DataContext = vm }; DispatcherHelper.UIDispatcher.Invoke(() => { dialog.ShowDialog(); }); } }).AddTo(this.CompositeDisposable); }