using ReactiveUI; using System; using System.Collections.Generic; using System.Reactive; using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Reactive.Subjects; using EinmaleinsTrainer.Models; namespace EinmaleinsTrainer.ViewModels; public class QuizViewModel : ViewModelBase { // ========================= // Basisdaten // ========================= private QuizModel _model = new(); private List _questions = new(); public List Questions { get => _questions; private set { RxApp.MainThreadScheduler.Schedule(() => { this.RaiseAndSetIfChanged(ref _questions, value); this.RaisePropertyChanged(nameof(Total)); // Wenn sich Questions ändern → Index resetten Index = 0; }); } } public int Total => Questions.Count; private int _index; public int Index { get => _index; private set { RxApp.MainThreadScheduler.Schedule(() => { bool changed = _index == value; this.RaiseAndSetIfChanged(ref _index, value); if (!changed) { this.RaisePropertyChanged(); } UpdateDerivedProperties(); }); } } // ========================= // Abgeleitete Properties // ========================= private Question _currentQuestion = new() { A = 0, B = 0 }; public Question CurrentQuestion { get => _currentQuestion; private set { RxApp.MainThreadScheduler.Schedule(() => { this.RaiseAndSetIfChanged(ref _currentQuestion, value); }); } } private int _questionNumber; public int QuestionNumber { get => _questionNumber; private set { RxApp.MainThreadScheduler.Schedule(() => { this.RaiseAndSetIfChanged(ref _questionNumber, value); }); } } private string _currentQuestionText = "0 × 0"; public string CurrentQuestionText { get => _currentQuestionText; private set { RxApp.MainThreadScheduler.Schedule(() => { this.RaiseAndSetIfChanged(ref _currentQuestionText, value); }); } } // ========================= // User Input / Status // ========================= private string _userAnswer = ""; public string UserAnswer { get => _userAnswer; set { RxApp.MainThreadScheduler.Schedule(() => { this.RaiseAndSetIfChanged(ref _userAnswer, value); }); } } private int _remaining; public int RemainingSeconds { get => _remaining; private set { RxApp.MainThreadScheduler.Schedule(() => { this.RaiseAndSetIfChanged(ref _remaining, value); if (value > 0) RedrawRequired?.Invoke(this, new()); }); } } private int _score; public int Score { get => _score; private set { RxApp.MainThreadScheduler.Schedule(() => { this.RaiseAndSetIfChanged(ref _score, value); }); } } public bool UseTimer { get; private set; } private TimeSpan TimeRequired = TimeSpan.Zero; private DateTime Started = DateTime.MinValue; // ========================= // Commands / Events // ========================= public ReactiveCommand SubmitCommand { get; } private readonly Subject _finished = new(); public IObservable Finished => _finished.AsObservable(); public event EventHandler? RedrawRequired; private IDisposable? _timerSub; // ========================= // ctor // ========================= public QuizViewModel() { SubmitCommand = ReactiveCommand.Create( Submit, outputScheduler: RxApp.MainThreadScheduler ); } // ========================= // Init // ========================= public void Init(QuizModel model) { _timerSub?.Dispose(); _model = model; TimeRequired = TimeSpan.Zero; Questions = new List(model.Questions); Score = 0; UserAnswer = ""; UseTimer = model.UseTimer && model.Seconds > 1; RemainingSeconds = UseTimer ? model.Seconds : 0; // Abgeleitete Properties initial setzen UpdateDerivedProperties(); if (UseTimer) StartTimer(model.Seconds); } // ========================= // Ableitungen zentral // ========================= private void UpdateDerivedProperties() { if (Questions.Count == 0 || Index < 0 || Index >= Questions.Count) { CurrentQuestion = new Question { A = 0, B = 0 }; QuestionNumber = 0; CurrentQuestionText = "0 × 0"; return; } var q = Questions[Index]; CurrentQuestion = q; // Direkt zuweisen QuestionNumber = Index + 1; CurrentQuestionText = $"{q.A} × {q.B}"; } // ========================= // Timer // ========================= private void StartTimer(int seconds) { Started = DateTime.Now; RemainingSeconds = seconds; _timerSub?.Dispose(); _timerSub = Observable .Interval(TimeSpan.FromSeconds(1), RxApp.MainThreadScheduler) .Select(i => seconds - (int)i - 1) .TakeWhile(s => s >= 0) .Subscribe(s => { RemainingSeconds = s; if (s == 0) SubmitInternal(false); }); } // ========================= // Submit // ========================= private void Submit() { bool correct = int.TryParse(UserAnswer, out int a) && a == CurrentQuestion.CorrectAnswer; SubmitInternal(correct); } private void SubmitInternal(bool correct) { if (correct) RxApp.MainThreadScheduler.Schedule(() => Score++); _timerSub?.Dispose(); if (UseTimer) TimeRequired += DateTime.Now - Started; RxApp.MainThreadScheduler.Schedule(() => { Index++; if (Index >= Questions.Count) { _finished.OnNext(new ResultModel { Score = Score, Total = Total, TimeRequired = UseTimer ? TimeRequired : null }); return; } // UserAnswer zurücksetzen UserAnswer = ""; // Update der abgeleiteten Properties (CurrentQuestionText, QuestionNumber) UpdateDerivedProperties(); RedrawRequired?.Invoke(this, new()); }); if (UseTimer && Index < Questions.Count) StartTimer(_model.Seconds); } }