using FluentTerminal.App.Services; using FluentTerminal.App.Services.Utilities; using FluentTerminal.App.ViewModels.Infrastructure; using FluentTerminal.Models; using GalaSoft.MvvmLight; using GalaSoft.MvvmLight.Command; using Newtonsoft.Json; using System; using System.Threading.Tasks; using System.Windows.Input; namespace FluentTerminal.App.ViewModels { public class ThemeViewModel : ViewModelBase { private readonly IDialogService _dialogService; private readonly ISettingsService _settingsService; private string _author; private string _background; private double _backgroundOpacity; private string _black; private string _blue; private string _brightBlack; private string _brightBlue; private string _brightCyan; private string _brightGreen; private string _brightMagenta; private string _brightRed; private string _brightWhite; private string _brightYellow; private string _cursor; private string _cursorAccent; private string _cyan; private TerminalTheme _fallbackTheme; private string _foreground; private string _green; private bool _inEditMode; private bool _isActive; private string _magenta; private string _name; private bool _isNew; private string _red; private string _selection; private string _white; private string _yellow; private ImageFile _backgroundThemeFile; private readonly IFileSystemService _fileSystemService; private readonly IImageFileSystemService _imageFileSystemService; public event EventHandler BackgroundChanged; public event EventHandler BackgroundImageChanged; public ThemeViewModel(TerminalTheme theme, ISettingsService settingsService, IDialogService dialogService, IFileSystemService fileSystemService, IImageFileSystemService imageFileSystemService, bool isNew) { Model = theme; _settingsService = settingsService; _dialogService = dialogService; _fileSystemService = fileSystemService; _imageFileSystemService = imageFileSystemService; _isNew = isNew; Name = Model.Name; Author = Model.Author; Id = Model.Id; Black = Model.Colors.Black; Red = Model.Colors.Red; Green = Model.Colors.Green; Yellow = Model.Colors.Yellow; Blue = Model.Colors.Blue; Magenta = Model.Colors.Magenta; Cyan = Model.Colors.Cyan; White = Model.Colors.White; BrightBlack = Model.Colors.BrightBlack; BrightRed = Model.Colors.BrightRed; BrightGreen = Model.Colors.BrightGreen; BrightYellow = Model.Colors.BrightYellow; BrightBlue = Model.Colors.BrightBlue; BrightMagenta = Model.Colors.BrightMagenta; BrightCyan = Model.Colors.BrightCyan; BrightWhite = Model.Colors.BrightWhite; Background = Model.Colors.Background; Foreground = Model.Colors.Foreground; Cursor = Model.Colors.Cursor; CursorAccent = Model.Colors.CursorAccent; Selection = Model.Colors.Selection; BackgroundThemeFile = Model.BackgroundImage; SetActiveCommand = new RelayCommand(SetActive); DeleteCommand = new AsyncCommand(DeleteAsync, NotPreInstalled); EditCommand = new RelayCommand(Edit, NotPreInstalled); CancelEditCommand = new AsyncCommand(CancelEditAsync); SaveChangesCommand = new AsyncCommand(SaveChangesAsync); ExportCommand = new AsyncCommand(Export, NotPreInstalled); ChooseBackgroundImageCommand = new AsyncCommand(ChooseBackgroundImageAsync, NotPreInstalled); DeleteBackgroundImageCommand = new AsyncCommand(DeleteBackgroundImageAsync, NotPreInstalled); } public event EventHandler Activated; public event EventHandler Deleted; public TerminalTheme Model { get; } public string Author { get => _author; set => Set(ref _author, value); } public string Background { get => _background; set { Set(ref _background, value); BackgroundChanged?.Invoke(this, value); } } public double BackgroundOpacity { get => _backgroundOpacity; set => Set(ref _backgroundOpacity, value); } public string Black { get => _black; set => Set(ref _black, value); } public string Blue { get => _blue; set => Set(ref _blue, value); } public string BrightBlack { get => _brightBlack; set => Set(ref _brightBlack, value); } public string BrightBlue { get => _brightBlue; set => Set(ref _brightBlue, value); } public string BrightCyan { get => _brightCyan; set => Set(ref _brightCyan, value); } public string BrightGreen { get => _brightGreen; set => Set(ref _brightGreen, value); } public string BrightMagenta { get => _brightMagenta; set => Set(ref _brightMagenta, value); } public string BrightRed { get => _brightRed; set => Set(ref _brightRed, value); } public string BrightWhite { get => _brightWhite; set => Set(ref _brightWhite, value); } public string BrightYellow { get => _brightYellow; set => Set(ref _brightYellow, value); } public ICommand CancelEditCommand { get; } public string Cursor { get => _cursor; set => Set(ref _cursor, value); } public string CursorAccent { get => _cursorAccent; set => Set(ref _cursorAccent, value); } public string Cyan { get => _cyan; set => Set(ref _cyan, value); } public ICommand DeleteCommand { get; } public ICommand EditCommand { get; } public ICommand ExportCommand { get; } public string Foreground { get => _foreground; set => Set(ref _foreground, value); } public string Green { get => _green; set => Set(ref _green, value); } public Guid Id { get; } public bool InEditMode { get => _inEditMode; set => Set(ref _inEditMode, value); } public bool IsActive { get => _isActive; set => Set(ref _isActive, value); } public string Magenta { get => _magenta; set => Set(ref _magenta, value); } public string Name { get => _name; set => Set(ref _name, value); } public string Red { get => _red; set => Set(ref _red, value); } public ICommand SaveChangesCommand { get; } public string Selection { get => _selection; set => Set(ref _selection, value); } public ICommand SetActiveCommand { get; } public string White { get => _white; set => Set(ref _white, value); } public string Yellow { get => _yellow; set => Set(ref _yellow, value); } public ImageFile BackgroundThemeFile { get => _backgroundThemeFile; set { Set(ref _backgroundThemeFile, value); BackgroundImageChanged?.Invoke(this, value); } } public ICommand ChooseBackgroundImageCommand { get; } public ICommand DeleteBackgroundImageCommand { get; } // Requires UI thread private async Task SaveChangesAsync() { Model.Name = Name; Model.Author = Author; Model.Colors.Black = Black; Model.Colors.Red = Red; Model.Colors.Green = Green; Model.Colors.Yellow = Yellow; Model.Colors.Blue = Blue; Model.Colors.Magenta = Magenta; Model.Colors.Cyan = Cyan; Model.Colors.White = White; Model.Colors.BrightBlack = BrightBlack; Model.Colors.BrightRed = BrightRed; Model.Colors.BrightGreen = BrightGreen; Model.Colors.BrightYellow = BrightYellow; Model.Colors.BrightBlue = BrightBlue; Model.Colors.BrightMagenta = BrightMagenta; Model.Colors.BrightCyan = BrightCyan; Model.Colors.BrightWhite = BrightWhite; Model.Colors.Background = Background; Model.Colors.Foreground = Foreground; Model.Colors.Cursor = Cursor; Model.Colors.CursorAccent = CursorAccent; Model.Colors.Selection = Selection; if (Model.BackgroundImage != null && BackgroundThemeFile != Model?.BackgroundImage) { // ConfigureAwait(true) because we're setting some view-model properties afterwards. await _imageFileSystemService .RemoveImportedImageAsync($"{Model.BackgroundImage?.Name}{Model.BackgroundImage?.FileType}") .ConfigureAwait(true); } // ConfigureAwait(true) because we're setting some view-model properties afterwards. BackgroundThemeFile = await SaveBackgroundImageAsync().ConfigureAwait(true); Model.BackgroundImage = BackgroundThemeFile; _settingsService.SaveTheme(Model); InEditMode = false; _isNew = false; } // Requires UI thread private async Task CancelEditAsync() { if (_isNew) { await DeleteAsync().ConfigureAwait(false); return; } TerminalTheme changedTheme = new TerminalTheme() { Name = Name, Author = Author, BackgroundImage = BackgroundThemeFile, Colors = new TerminalColors() { Black = Black, Red = Red, Green = Green, Yellow = Yellow, Blue = Blue, Magenta = Magenta, Cyan = Cyan, White = White, BrightBlack = BrightBlack, BrightRed = BrightRed, BrightGreen = BrightGreen, BrightYellow = BrightYellow, BrightBlue = BrightBlue, BrightMagenta = BrightMagenta, BrightCyan = BrightCyan, BrightWhite = BrightWhite, Background = Background, Foreground = Foreground, Cursor = Cursor, CursorAccent = CursorAccent, Selection = Selection } }; if (!_fallbackTheme.Equals(changedTheme)) { // ConfigureAwait(true) because we're setting some view-model properties afterwards. var result = await _dialogService.ShowMessageDialogAsync(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmDiscardChanges"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true); if (result == DialogButton.OK) { Black = _fallbackTheme.Colors.Black; Red = _fallbackTheme.Colors.Red; Green = _fallbackTheme.Colors.Green; Yellow = _fallbackTheme.Colors.Yellow; Blue = _fallbackTheme.Colors.Blue; Magenta = _fallbackTheme.Colors.Magenta; Cyan = _fallbackTheme.Colors.Cyan; White = _fallbackTheme.Colors.White; BrightBlack = _fallbackTheme.Colors.BrightBlack; BrightRed = _fallbackTheme.Colors.BrightRed; BrightGreen = _fallbackTheme.Colors.BrightGreen; BrightYellow = _fallbackTheme.Colors.BrightYellow; BrightBlue = _fallbackTheme.Colors.BrightBlue; BrightMagenta = _fallbackTheme.Colors.BrightMagenta; BrightCyan = _fallbackTheme.Colors.BrightCyan; BrightWhite = _fallbackTheme.Colors.BrightWhite; Background = _fallbackTheme.Colors.Background; Foreground = _fallbackTheme.Colors.Foreground; Cursor = _fallbackTheme.Colors.Cursor; CursorAccent = _fallbackTheme.Colors.CursorAccent; Selection = _fallbackTheme.Colors.Selection; Name = _fallbackTheme.Name; Author = _fallbackTheme.Author; BackgroundThemeFile = _fallbackTheme.BackgroundImage; InEditMode = false; await _imageFileSystemService.RemoveTemporaryBackgroundThemeImageAsync().ConfigureAwait(false); } } else { InEditMode = false; } } private bool NotPreInstalled() { return !Model.PreInstalled; } // Requires UI thread private async Task DeleteAsync() { // ConfigureAwait(true) because we need to trigger Deleted event in the calling (UI) thread. var result = await _dialogService.ShowMessageDialogAsync(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmDeleteTheme"), DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true); if (result == DialogButton.OK) { // ConfigureAwait(true) because we need to trigger Deleted event in the calling (UI) thread. await DeleteBackgroundImageIfExistsAsync().ConfigureAwait(true); // ConfigureAwait(true) because we need to trigger Deleted event in the calling (UI) thread. await _imageFileSystemService.RemoveTemporaryBackgroundThemeImageAsync().ConfigureAwait(true); Deleted?.Invoke(this, EventArgs.Empty); } } // Requires UI thread private async Task DeleteBackgroundImageAsync() { // ConfigureAwait(true) because we're setting some view-model properties afterwards. var result = await _dialogService.ShowMessageDialogAsync(I18N.Translate("PleaseConfirm"), I18N.Translate("ConfirmDeleteBackgroundImage"), DialogButton.OK, DialogButton.Cancel) .ConfigureAwait(true); if (result == DialogButton.OK) { BackgroundThemeFile = null; } } private void Edit() { _fallbackTheme = new TerminalTheme(Model); InEditMode = true; } private void SetActive() { Activated?.Invoke(this, EventArgs.Empty); } private Task Export() { var encodedImage = _imageFileSystemService.EncodeImage(BackgroundThemeFile); var exportedTheme = new ExportedTerminalTheme(Model, encodedImage); var content = JsonConvert.SerializeObject(exportedTheme, Formatting.Indented, new JsonSerializerSettings { ContractResolver = new TerminalThemeContractResolver() }); return _fileSystemService.SaveTextFileAsync(Name, "Fluent Terminal Theme", ".flutecolors", content); } private async Task SaveBackgroundImageAsync() { if (BackgroundThemeFile == null) { return BackgroundThemeFile; } if (BackgroundThemeFile == Model.BackgroundImage) { return BackgroundThemeFile; } var importedBackgroundThemeFile = await _fileSystemService.SaveImageInRoamingAsync(BackgroundThemeFile).ConfigureAwait(false); await _imageFileSystemService.RemoveTemporaryBackgroundThemeImageAsync().ConfigureAwait(false); return importedBackgroundThemeFile; } // Requires UI thread private async Task ChooseBackgroundImageAsync() { // ConfigureAwait(true) because we're setting some view-model properties afterwards. var chosenImage = await _imageFileSystemService .ImportTemporaryImageFileAsync(new[] {".jpeg", ".png", ".jpg"}).ConfigureAwait(true); if(chosenImage != null) { BackgroundThemeFile = chosenImage; } } // Requires UI thread private async Task DeleteBackgroundImageIfExistsAsync() { if (BackgroundThemeFile != null) { var imageFile = Model.BackgroundImage; BackgroundThemeFile = null; if (imageFile != null) { await _imageFileSystemService.RemoveImportedImageAsync($"{imageFile.Name}{imageFile.FileType}") .ConfigureAwait(false); } } } } }