using System.Collections.ObjectModel; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using AdvancedCalculator.Models; using AdvancedCalculator.Services; using CSMic; using System; using System.Reflection; namespace AdvancedCalculator.ViewModels; public partial class MainViewModel : ViewModelBase { private readonly ICalculatorService _calculatorService; public event EventHandler? CopyRequested; public MainViewModel() : this(new CalculatorService()) { } public MainViewModel(ICalculatorService calculatorService) { _calculatorService = calculatorService; History = new ObservableCollection(); Variables = new ObservableCollection(); } [ObservableProperty] private string _inputText = string.Empty; [ObservableProperty] private bool _isFunctionsPanelOpen; [ObservableProperty] private int _selectedHistoryIndex = -1; // Caret position in the input TextBox [ObservableProperty] private int _caretIndex; public ObservableCollection History { get; } public ObservableCollection Variables { get; } // App version string, sourced from AssemblyInformationalVersion public string AppVersion { get; } = typeof(MainViewModel).Assembly .GetCustomAttribute()? .InformationalVersion ?? "0.0.0"; [RelayCommand] private void ToggleFunctions() { IsFunctionsPanelOpen = !IsFunctionsPanelOpen; } // Insert helpers for touch: appends tokens to the input box [RelayCommand] private void InsertVariable(VariableItem variableItem) { if (variableItem == null) return; if (string.IsNullOrWhiteSpace(variableItem.VariableName)) return; InsertToken(variableItem.VariableName); } [RelayCommand] private void InsertFunction(FunctionDefinitionItem functionDefintionItem) { if (functionDefintionItem == null) return; if (string.IsNullOrWhiteSpace(functionDefintionItem.FunctionName)) return; InsertToken(functionDefintionItem.FunctionName + "()"); // Place caret inside the parentheses if (!string.IsNullOrEmpty(InputText)) { CaretIndex = Math.Max(0, InputText.Length - 1); } // Close the functions flyout after insertion for smoother UX IsFunctionsPanelOpen = false; } private void InsertToken(string token) { if (string.IsNullOrEmpty(InputText)) { InputText = token; return; } if (!char.IsWhiteSpace(InputText[^1])) InputText += " "; InputText += token; } [RelayCommand(AllowConcurrentExecutions = false)] private async Task Submit() { var text = InputText?.Trim(); if (string.IsNullOrEmpty(text)) return; var result = await _calculatorService.InterpretAsync(text); History.Add(new HistoryItem { Input = text, Output = result.Output }); Variables.Clear(); foreach (var v in result.Variables) { Variables.Add(v); } InputText = string.Empty; SelectedHistoryIndex = History.Count - 1; } // Copy helpers (MVVM-pure): build text in VM, View handles clipboard via CopyRequested [RelayCommand] private void CopyHistoryInput(HistoryItem? item) { if (item is null || string.IsNullOrWhiteSpace(item.Input)) return; CopyRequested?.Invoke(this, item.Input); } [RelayCommand] private void CopyHistoryOutput(HistoryItem? item) { if (item is null || string.IsNullOrWhiteSpace(item.Output)) return; CopyRequested?.Invoke(this, item.Output); } [RelayCommand] private void CopyHistoryBoth(HistoryItem? item) { if (item is null) return; var input = item.Input?.Trim(); var output = item.Output?.Trim(); if (string.IsNullOrEmpty(input) && string.IsNullOrEmpty(output)) return; if (string.IsNullOrEmpty(input)) { CopyRequested?.Invoke(this, output!); return; } if (string.IsNullOrEmpty(output)) { CopyRequested?.Invoke(this, input!); return; } CopyRequested?.Invoke(this, $"{input} = {output}"); } }