using System.Collections.ObjectModel;
using Microsoft.Extensions.Logging;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using GSS2.Core.Analysis.AmineContent.Database;
using GSS2.UI.Core.ViewModels;
using GSS2.UI.Core.Services;
namespace GSS2.ViewModels.AmineContent;
public partial class BrandCalibrationViewModel : ViewModelBase
{
///
/// Внутренняя модель элементов меню
///
public partial class MenuItemViewModel : ViewModelBase
{
[ObservableProperty] public partial int Id { get; set; }
[ObservableProperty] public partial string BrandName { get; set; }
[ObservableProperty] public partial string MixtureName { get; set; }
public MenuItemViewModel(int id, string brandName, string mixtureName)
{
Id = id;
BrandName = brandName;
MixtureName = mixtureName;
}
}
///
/// Внутренняя модель записи
///
public partial class RecordViewModel : ViewModelBase
{
///
///
///
[ObservableProperty] public partial int Id { get; set; } = 0;
///
///
///
[ObservableProperty] public partial string Uuid { get; set; } = Guid.NewGuid().ToString();
///
///
///
[ObservableProperty] public partial DateTime CreateDateTime { get; set; } = DateTime.UtcNow;
///
///
///
[ObservableProperty] public partial DateTime EditDateTime { get; set; } = DateTime.UtcNow;
///
///
///
[ObservableProperty] public partial string BrandName { get; set; } = "";
///
///
///
[ObservableProperty] public partial string MixtureName { get; set; } = "";
///
///
///
[ObservableProperty] public partial double MixtureNormalRate { get; set; } = -1;
///
///
///
[ObservableProperty] public partial string Comment { get; set; } = "";
}
///
/// Логгер
///
private readonly ILogger _logger;
///
/// Контекст базы данных
///
private readonly Context _context;
///
/// ViewLocator для получения других View по их моделям
///
private readonly DependencyInjectionViewLocator _viewLocator;
///
/// Сервис навигации между экранами
///
private readonly NavigationService _navigation;
///
/// Меню открыто
///
[ObservableProperty] public partial bool MenuOpened { get; set; } = false;
///
/// Элементы меню
///
[ObservableProperty] public partial ObservableCollection MenuItems { get; set; } = new();
///
/// Текущий выбранный элемент меню. Равен -1 если элемент не выбран
///
[ObservableProperty] public partial int MenuSelectedIndex { get; set; } = -1;
///
/// Запись
///
[ObservableProperty] public partial RecordViewModel Record { get; set; } = new();
///
/// Текущий экран информационной секции
///
[ObservableProperty] public partial int InfoSectionCurrentIndex { get; set; } = 0;
///
/// Можно ли переходить на следующий экран информационной секции
///
[ObservableProperty] public partial bool InfoSectionCanScrollBackward { get; set; } = false;
///
/// Можно ли переходить на предыдущий экран информационной секции
///
[ObservableProperty] public partial bool InfoSectionCanScrollForward { get; set; } = false;
///
/// Происходит ли переход между экранами информационной секции
///
[ObservableProperty] public partial bool InfoSectionIsScrolling { get; set; } = false;
///
/// Можно ли взаимодействовать с объектом
///
[ObservableProperty] public partial bool CanInteract { get; set; } = true;
public BrandCalibrationViewModel(
ILogger logger,
Context context,
DependencyInjectionViewLocator viewLocator,
NavigationService navigation
)
{
// Устанавливаем readonly поля
_logger = logger;
_context = context;
_viewLocator = viewLocator;
_navigation = navigation;
_logger.LogInformation("Инициализация");
// Получаем записи из базы данных
MenuItems = new ObservableCollection(_context.BrandRecords.Select(r => new MenuItemViewModel(r.Id, r.BrandName, r.MixtureName)));
if (MenuItems.Count() > 0) // Если записи есть,
MenuSelectedIndex = 0; // Выбираем первую запись
else // Если записей нет,
MenuSelectedIndex = -1; // Устанавливаем, что запись не выбрана
// Подписываемся на изменения свойств для обновления CanScroll
PropertyChanged += (_, ea) =>
{
if (ea.PropertyName == nameof(MenuOpened) ||
ea.PropertyName == nameof(InfoSectionIsScrolling))
UpdateCanInteract();
};
_logger.LogInformation("Инициализировано");
}
///
/// Вызывается при изменении
///
partial void OnMenuSelectedIndexChanged(int value)
{
// Если значение выходит за верхнюю границу списка элементов меню, то логгируем как ошибку
if (value >= MenuItems.Count())
_logger.LogError("Значение {}({}) больше количества элементов {}({})", nameof(MenuSelectedIndex), MenuSelectedIndex, nameof(MenuItems), MenuItems.Count());
// Если значение больше или равно 0, то значит выбран один из элементов меню
if (value >= 0 && value < MenuItems.Count())
{
_logger.LogTrace("Выбран элемент из списка: {}", value);
// Находим запись в базе данных
var selectedRecord = _context.BrandRecords.Find(MenuItems[value].Id);
// Если запись найдена, то заполняем поля из базы данных
if (selectedRecord is not null)
{
Record.Id = selectedRecord.Id;
Record.Uuid = selectedRecord.Uuid;
Record.CreateDateTime = selectedRecord.CreateDateTime;
Record.EditDateTime = selectedRecord.EditDateTime;
Record.BrandName = selectedRecord.BrandName;
Record.MixtureName = selectedRecord.MixtureName;
Record.MixtureNormalRate = selectedRecord.MixtureNormalRate;
Record.Comment = selectedRecord.Comment;
return;
}
// Если значение не найдено в базе данных
else
{
// Логгируем как ошибку
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", MenuItems[value].Id, nameof(_context.BrandRecords));
var nonExistingIndex = MenuSelectedIndex; // Сохраняем несуществующий индекс
// Устанавливаем, что запись не выбрана, этот метод вызовется повторно и заполнит поля пустыми значениями
MenuSelectedIndex = -1;
// Удаляем несуществующий элемент из списка
MenuItems.RemoveAt(nonExistingIndex);
return;
}
}
// Если элемент не выбран, заполняем поля пустыми значениями
_logger.LogTrace("Выбран новый элемент");
var dateTime = DateTime.UtcNow;
Record.Id = 0;
Record.Uuid = Guid.NewGuid().ToString();
Record.CreateDateTime = dateTime;
Record.EditDateTime = dateTime;
Record.BrandName = "";
Record.MixtureName = "";
Record.MixtureNormalRate = -1;
Record.Comment = "";
}
///
/// Обновить значение
///
private void UpdateCanInteract()
{
CanInteract = !MenuOpened;
CanInteract &= !InfoSectionIsScrolling;
}
// Команды управления меню
///
/// Открыть меню
///
[RelayCommand] private void OpenMenu() => MenuOpened = true;
///
/// Закрыть меню
///
[RelayCommand] private void CloseMenu() => MenuOpened = false;
///
/// Переключить видимость меню
///
[RelayCommand] private void ToggleMenu() => MenuOpened = !MenuOpened;
///
/// Переключить информационную секцию на следующую
///
[RelayCommand] private void InfoSectionPrevious() => InfoSectionCurrentIndex--;
///
/// Переключить информационную секцию на предыдущую
///
[RelayCommand] private void InfoSectionNext() => InfoSectionCurrentIndex++;
///
/// Создать новую запись
///
[RelayCommand]
private void CreateRecord()
{
_logger.LogInformation("Создание новой записи");
// Если уже создана новая запись, то MenuSelectedIndex может быть равен -1 и
// при простом присвоении MenuSelectedIndex = -1 событие OnMenuSelectedIndexChanged не вызовется,
// потому что событие срабатывает только при изменении значения, а значение было -1 и привилось ему -1,
// поэтому если SelectedRecord != -1, то нужно явно вызвать метод OnMenuSelectedIndexChanged
if (MenuSelectedIndex != -1)
MenuSelectedIndex = -1;
else
OnMenuSelectedIndexChanged(-1);
}
///
/// Удалить выбранную запись из базы данных или очистить значения, если запись не выбрана
///
[RelayCommand]
private void DeleteRecord()
{
// TODO: Добавить определение и отображение количества затрагиваемых, записей которые будут повреждены при удалении этой записи
try
{
_logger.LogTrace("Удаление записи: {}={}, {}={}, {}={}", nameof(BrandRecord.Id), Record.Id, nameof(BrandRecord.BrandName), Record.BrandName, nameof(BrandRecord.MixtureName), Record.MixtureName);
// Если запись не выбрана, то просто создаём новую запись
if (MenuSelectedIndex < 0)
{
_logger.LogTrace("Запись не выбрана, создаём новую");
CreateRecord();
return;
}
// Создаём модель для экрана подтверждения
var confirmViewModel = new DeleteConfirmViewModel(_navigation)
{
Question = "Вы уверены, что хотите удалить запись?"
};
// Создаём коллбек который вызовется при экрана подтверждения
void OnConfirmCloses(object? sender, bool result)
{
// Если пользователь отменил удаление, то выходим из коллбека
if (!result)
{
_logger.LogTrace("Пользователь отменил удаление");
return;
}
// Если запись не выбрана, то просто создаём новую запись
if (MenuSelectedIndex < 0)
{
_logger.LogTrace("Запись не выбрана, создаём новую");
CreateRecord();
return;
}
// Сохраняем индекс удалённый записи и находим её в базе данных
var deletedMenuIndex = MenuSelectedIndex;
var deletedRecordId = Record.Id;
var deletedRecord = _context.BrandRecords.Find(deletedRecordId);
// Создаём новую запись
CreateRecord();
// Удаляем запись из списка
MenuItems.RemoveAt(deletedMenuIndex);
// Если удалённая запись не найдена в базе данных, то логгируем как ошибку
if (deletedRecord is null)
{
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", deletedRecordId, nameof(_context.BrandRecords));
return;
}
// Удаляем запись из базы данных и сохраняем изменения в базе данных
_context.BrandRecords.Remove(deletedRecord);
_context.SaveChanges();
_logger.LogTrace("Запись удалена");
// Отписываемся от события закрытия экрана подтверждения
confirmViewModel.Closes -= OnConfirmCloses;
}
// Создаём View экрана подтверждения
var confirmView = _viewLocator.Build(confirmViewModel);
// Если View не найден, то логгируем как ошибку
if (confirmView is null)
{
_logger.LogError("Не удалось найти View для экрана подтверждения");
return;
}
_logger.LogTrace("Задаём вопрос");
// Подписываемся на событие закрытия экрана подтверждения
confirmViewModel.Closes += OnConfirmCloses;
// Преходим на экран подтверждения, при закрытии он сам вернёт экран обратно на текущий
_navigation.NavigateTo(confirmView);
}
catch (Exception ex)
{
_logger.LogError(ex, "Исключение при удалении записи");
}
}
///
/// Сохранить текущие значения в новую запись или перезаписать значения в выдранной записи
///
[RelayCommand]
private void SaveRecord()
{
try
{
_logger.LogTrace("Сохранение записи: {}={}, {}={}, {}={}", nameof(BrandRecord.Id), Record.Id, nameof(BrandRecord.BrandName), Record.BrandName, nameof(BrandRecord.MixtureName), Record.MixtureName);
// Если запись не выбрана
if (MenuSelectedIndex < 0)
{
_logger.LogTrace("Создаём новую запись");
var dateTime = DateTime.UtcNow;
// Создаём новую запись
var newRecord = new BrandRecord(
uuid: Record.Uuid,
createDateTime: dateTime,
editDateTime: dateTime,
brandName: Record.BrandName,
mixtureName: Record.MixtureName,
mixtureNormalRate: Record.MixtureNormalRate,
comment: Record.Comment
);
// Добавляем запись в базу данных
var attachedRecord = _context.BrandRecords.Add(newRecord);
// Сохраняем изменения в базе данных
_context.SaveChanges();
_logger.LogTrace("Запись сохранена");
// Добавляем новый элемент в меню
MenuItems.Add(new MenuItemViewModel(attachedRecord.Entity.Id, attachedRecord.Entity.BrandName, attachedRecord.Entity.MixtureName));
// Выбираем последний элемент из меню
MenuSelectedIndex = MenuItems.Count() - 1;
}
// Если запись выбрана
else
{
// Находим запись в базе данных
var attachedRecord = _context.BrandRecords.Find(Record.Id);
// Если запись не найдена, то логгируем как ошибку
if (attachedRecord is null)
{
_logger.LogError("Не удалось найти элемент с Id({}) в таблице {}", Record.Id, nameof(_context.BrandRecords));
return;
}
_logger.LogTrace("Записываем изменения в базу данных");
// Записываем изменения
attachedRecord.EditDateTime = DateTime.UtcNow;
attachedRecord.BrandName = Record.BrandName;
attachedRecord.MixtureName = Record.MixtureName;
attachedRecord.MixtureNormalRate = Record.MixtureNormalRate;
attachedRecord.Comment = Record.Comment;
// Сохраняем изменения в базе данных
_context.SaveChanges();
_logger.LogTrace("Запись сохранена");
// Записываем изменения в меню
_logger.LogTrace("Записываем изменения в меню");
MenuItems[MenuSelectedIndex].BrandName = Record.BrandName;
MenuItems[MenuSelectedIndex].MixtureName = Record.MixtureName;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Исключение при сохранении записи");
}
}
}