Added first code.
Signed-off-by: Naoriel Sa' Rocí <naoriel@sa-roci.de>
This commit is contained in:
parent
bbf68e1621
commit
4a4fb54143
2
.gitignore
vendored
2
.gitignore
vendored
@ -12,3 +12,5 @@
|
|||||||
# Built Visual Studio Code Extensions
|
# Built Visual Studio Code Extensions
|
||||||
*.vsix
|
*.vsix
|
||||||
|
|
||||||
|
bin/*
|
||||||
|
obj/*
|
||||||
|
|||||||
14
App.axaml
Normal file
14
App.axaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="EinmaleinsTrainer.App"
|
||||||
|
xmlns:local="using:EinmaleinsTrainer"
|
||||||
|
RequestedThemeVariant="Default">
|
||||||
|
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||||
|
|
||||||
|
<Application.DataTemplates>
|
||||||
|
</Application.DataTemplates>
|
||||||
|
|
||||||
|
<Application.Styles>
|
||||||
|
<FluentTheme />
|
||||||
|
</Application.Styles>
|
||||||
|
</Application>
|
||||||
28
App.axaml.cs
Normal file
28
App.axaml.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using EinmaleinsTrainer.Views;
|
||||||
|
using ReactiveUI;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
namespace EinmaleinsTrainer;
|
||||||
|
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
|
||||||
|
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnFrameworkInitializationCompleted()
|
||||||
|
{
|
||||||
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
|
{
|
||||||
|
desktop.MainWindow = new MainWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnFrameworkInitializationCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Assets/avalonia-logo.ico
Normal file
BIN
Assets/avalonia-logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 172 KiB |
28
EinmaleinsTrainer.csproj
Normal file
28
EinmaleinsTrainer.csproj
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Models\" />
|
||||||
|
<AvaloniaResource Include="Assets\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Avalonia" Version="11.3.12" />
|
||||||
|
<PackageReference Include="Avalonia.Desktop" Version="11.3.12" />
|
||||||
|
<PackageReference Include="Avalonia.ReactiveUI" Version="11.3.8" />
|
||||||
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.12" />
|
||||||
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.12" />
|
||||||
|
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||||
|
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.12">
|
||||||
|
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
||||||
|
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
24
EinmaleinsTrainer.sln
Normal file
24
EinmaleinsTrainer.sln
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.2.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EinmaleinsTrainer", "EinmaleinsTrainer.csproj", "{660FFE01-9483-61B8-28ED-58FAD08A78FD}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{660FFE01-9483-61B8-28ED-58FAD08A78FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{660FFE01-9483-61B8-28ED-58FAD08A78FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{660FFE01-9483-61B8-28ED-58FAD08A78FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{660FFE01-9483-61B8-28ED-58FAD08A78FD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {E30069BC-F120-41C6-970C-3AE4CCC2F5C1}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
35
Models/Question.cs
Normal file
35
Models/Question.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace EinmaleinsTrainer.Models;
|
||||||
|
|
||||||
|
public class Question : IEquatable<Question>
|
||||||
|
{
|
||||||
|
public int A { get; set; }
|
||||||
|
public int B { get; set; }
|
||||||
|
|
||||||
|
public int CorrectAnswer => A * B;
|
||||||
|
|
||||||
|
public bool Equals(Question? other)
|
||||||
|
{
|
||||||
|
if (other is null) return false;
|
||||||
|
|
||||||
|
return A == other.A &&
|
||||||
|
B == other.B;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
return Equals(obj as Question);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(A, B);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{A} x {B}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
10
Models/QuizModel.cs
Normal file
10
Models/QuizModel.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace EinmaleinsTrainer.Models;
|
||||||
|
|
||||||
|
public class QuizModel
|
||||||
|
{
|
||||||
|
public List<Question> Questions { get; set; } = [];
|
||||||
|
public bool UseTimer { get; set; }
|
||||||
|
public int Seconds { get; set; } = 10;
|
||||||
|
}
|
||||||
9
Models/ResultModel.cs
Normal file
9
Models/ResultModel.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
namespace EinmaleinsTrainer.Models;
|
||||||
|
|
||||||
|
public class ResultModel
|
||||||
|
{
|
||||||
|
public int Score { get; set; } = 0;
|
||||||
|
public int Total { get; set; } = 0;
|
||||||
|
public TimeSpan? TimeRequired = null;
|
||||||
|
}
|
||||||
23
Program.cs
Normal file
23
Program.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace EinmaleinsTrainer;
|
||||||
|
|
||||||
|
sealed class Program
|
||||||
|
{
|
||||||
|
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||||
|
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||||
|
// yet and stuff might break.
|
||||||
|
[STAThread]
|
||||||
|
public static void Main(string[] args) => BuildAvaloniaApp()
|
||||||
|
.StartWithClassicDesktopLifetime(args);
|
||||||
|
|
||||||
|
// Avalonia configuration, don't remove; also used by visual designer.
|
||||||
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
|
=> AppBuilder.Configure<App>()
|
||||||
|
.UsePlatformDetect()
|
||||||
|
.UseReactiveUI()
|
||||||
|
.WithInterFont()
|
||||||
|
.LogToTrace();
|
||||||
|
}
|
||||||
33
Services/QuestionGenerator.cs
Normal file
33
Services/QuestionGenerator.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using EinmaleinsTrainer.Models;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace EinmaleinsTrainer.Services;
|
||||||
|
|
||||||
|
public class QuestionGenerator
|
||||||
|
{
|
||||||
|
private readonly Random _random = new();
|
||||||
|
|
||||||
|
public List<Question> Generate(List<int> rows, bool includeSquares)
|
||||||
|
{
|
||||||
|
var pool = new List<Question>();
|
||||||
|
|
||||||
|
foreach (var r in rows)
|
||||||
|
{
|
||||||
|
for (int i = 1; i <= 10; i++)
|
||||||
|
pool.Add(new Question { A = r, B = i });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeSquares)
|
||||||
|
{
|
||||||
|
for (int i = 1; i <= 10; i++)
|
||||||
|
pool.Add(new Question { A = i, B = i });
|
||||||
|
}
|
||||||
|
|
||||||
|
return pool.OrderBy(_ => _random.Next())
|
||||||
|
.Take(10)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
139
ViewModels/MainViewModel.cs
Normal file
139
ViewModels/MainViewModel.cs
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
using ReactiveUI;
|
||||||
|
using System.Reactive;
|
||||||
|
using EinmaleinsTrainer.Services;
|
||||||
|
using EinmaleinsTrainer.Models;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace EinmaleinsTrainer.ViewModels;
|
||||||
|
|
||||||
|
public class MainViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
public ObservableCollection<int> SelectedRows { get; } = new();
|
||||||
|
|
||||||
|
public event EventHandler? RedrawRequired;
|
||||||
|
|
||||||
|
private bool _includeSquares;
|
||||||
|
public bool IncludeSquares
|
||||||
|
{
|
||||||
|
get => _includeSquares;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _includeSquares, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _useTimer;
|
||||||
|
public bool UseTimer
|
||||||
|
{
|
||||||
|
get => _useTimer;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
this.RaiseAndSetIfChanged(ref _useTimer, value);
|
||||||
|
RedrawRequired?.Invoke(this, new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private int _secondsPerQuestion = 0;
|
||||||
|
public int SecondsPerQuestion
|
||||||
|
{
|
||||||
|
get => _secondsPerQuestion;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value < 2) value = 2;
|
||||||
|
if (value > 120) value = 120;
|
||||||
|
this.RaiseAndSetIfChanged(ref _secondsPerQuestion, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public ReactiveCommand<Unit, QuizModel> StartCommand { get; }
|
||||||
|
|
||||||
|
public MainViewModel()
|
||||||
|
{
|
||||||
|
StartCommand = ReactiveCommand.Create(
|
||||||
|
execute: () =>
|
||||||
|
{
|
||||||
|
var generator = new QuestionGenerator();
|
||||||
|
|
||||||
|
var questions = generator.Generate(SelectedRows.ToList(), IncludeSquares);
|
||||||
|
|
||||||
|
return new QuizModel(){ Questions = questions, UseTimer = UseTimer, Seconds = SecondsPerQuestion};
|
||||||
|
},
|
||||||
|
canExecute: Observable.Return(true).ObserveOn(RxApp.MainThreadScheduler),
|
||||||
|
outputScheduler: RxApp.MainThreadScheduler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ToggleRow(int row, bool isChecked)
|
||||||
|
{
|
||||||
|
if (isChecked)
|
||||||
|
SelectedRows.Add(row);
|
||||||
|
else
|
||||||
|
SelectedRows.Remove(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsRow1Checked
|
||||||
|
{
|
||||||
|
get => SelectedRows.Contains(1);
|
||||||
|
set => ToggleRow(1, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsRow2Checked
|
||||||
|
{
|
||||||
|
get => SelectedRows.Contains(2);
|
||||||
|
set => ToggleRow(2, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsRow3Checked
|
||||||
|
{
|
||||||
|
get => SelectedRows.Contains(3);
|
||||||
|
set => ToggleRow(3, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsRow4Checked
|
||||||
|
{
|
||||||
|
get => SelectedRows.Contains(4);
|
||||||
|
set => ToggleRow(4, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsRow5Checked
|
||||||
|
{
|
||||||
|
get => SelectedRows.Contains(5);
|
||||||
|
set => ToggleRow(5, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsRow6Checked
|
||||||
|
{
|
||||||
|
get => SelectedRows.Contains(6);
|
||||||
|
set => ToggleRow(6, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsRow7Checked
|
||||||
|
{
|
||||||
|
get => SelectedRows.Contains(7);
|
||||||
|
set => ToggleRow(7, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsRow8Checked
|
||||||
|
{
|
||||||
|
get => SelectedRows.Contains(8);
|
||||||
|
set => ToggleRow(8, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsRow9Checked
|
||||||
|
{
|
||||||
|
get => SelectedRows.Contains(9);
|
||||||
|
set => ToggleRow(9, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsRow10Checked
|
||||||
|
{
|
||||||
|
get => SelectedRows.Contains(10);
|
||||||
|
set => ToggleRow(10, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Init()
|
||||||
|
{
|
||||||
|
SelectedRows.Clear();
|
||||||
|
IncludeSquares = false;
|
||||||
|
UseTimer = false;
|
||||||
|
SecondsPerQuestion = 30;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
73
ViewModels/MainWindowViewModel.cs
Normal file
73
ViewModels/MainWindowViewModel.cs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
using ReactiveUI;
|
||||||
|
using System;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
|
||||||
|
namespace EinmaleinsTrainer.ViewModels;
|
||||||
|
|
||||||
|
public class MainWindowViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private ViewModelBase _current;
|
||||||
|
public ViewModelBase Current
|
||||||
|
{
|
||||||
|
get => _current;
|
||||||
|
set => SetProperty(ref _current, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly MainViewModel MainModel = new();
|
||||||
|
public readonly QuizViewModel QuizModel = new();
|
||||||
|
public readonly ResultViewModel ResultModel = new();
|
||||||
|
private readonly ViewModelBase _dummy = new();
|
||||||
|
|
||||||
|
public MainWindowViewModel()
|
||||||
|
{
|
||||||
|
MainModel.Init();
|
||||||
|
_current = MainModel;
|
||||||
|
|
||||||
|
ResultModel.RestartCommand
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(result =>
|
||||||
|
{
|
||||||
|
// In order to keep the last value if required
|
||||||
|
int secondsPerQuestion = MainModel.SecondsPerQuestion;
|
||||||
|
MainModel.Init();
|
||||||
|
if (result.TimeRequired != null)
|
||||||
|
{
|
||||||
|
MainModel.SecondsPerQuestion = (result.Score >= 10) ?
|
||||||
|
(int)Math.Round(0.15 * result.TimeRequired.Value.TotalSeconds, 0, MidpointRounding.AwayFromZero) :
|
||||||
|
secondsPerQuestion;
|
||||||
|
MainModel.UseTimer = true;
|
||||||
|
}
|
||||||
|
Current = MainModel;
|
||||||
|
});
|
||||||
|
|
||||||
|
QuizModel.Finished
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(result =>
|
||||||
|
{
|
||||||
|
ResultModel.Init(result);
|
||||||
|
Current = ResultModel;
|
||||||
|
});
|
||||||
|
|
||||||
|
QuizModel.RedrawRequired += (s, a) =>
|
||||||
|
{
|
||||||
|
Current = _dummy;
|
||||||
|
Current = QuizModel;
|
||||||
|
};
|
||||||
|
|
||||||
|
MainModel.StartCommand
|
||||||
|
.ObserveOn(RxApp.MainThreadScheduler)
|
||||||
|
.Subscribe(quiz =>
|
||||||
|
{
|
||||||
|
if (quiz.Questions.Count == 0) return;
|
||||||
|
QuizModel.Init(quiz);
|
||||||
|
Current = QuizModel;
|
||||||
|
});
|
||||||
|
|
||||||
|
MainModel.RedrawRequired += (s, a) =>
|
||||||
|
{
|
||||||
|
Current = _dummy;
|
||||||
|
Current = MainModel;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
293
ViewModels/QuizViewModel.cs
Normal file
293
ViewModels/QuizViewModel.cs
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
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<Question> _questions = new();
|
||||||
|
public List<Question> 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<Unit, Unit> SubmitCommand { get; }
|
||||||
|
|
||||||
|
private readonly Subject<ResultModel> _finished = new();
|
||||||
|
public IObservable<ResultModel> 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<Question>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
64
ViewModels/ResultViewModel.cs
Normal file
64
ViewModels/ResultViewModel.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reactive;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using EinmaleinsTrainer.Models;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace EinmaleinsTrainer.ViewModels;
|
||||||
|
|
||||||
|
public class ResultViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
public string Text { get; private set;} = "";
|
||||||
|
public string TimeRequiredText { get; private set;} = "";
|
||||||
|
public bool ShowTimeRequired => !string.IsNullOrEmpty(TimeRequiredText);
|
||||||
|
|
||||||
|
private ResultModel _model = new();
|
||||||
|
public void Init(ResultModel result)
|
||||||
|
{
|
||||||
|
double percent = result.Score * 1.0 / result.Total;
|
||||||
|
if (percent > 0.9)
|
||||||
|
{
|
||||||
|
Text = $"🎉 {result.Score} von {result.Total} richtig!";
|
||||||
|
}
|
||||||
|
else if (percent > 0.5)
|
||||||
|
{
|
||||||
|
Text = $"👍 {result.Score} von {result.Total} richtig.";
|
||||||
|
}
|
||||||
|
else if (percent > 0.25)
|
||||||
|
{
|
||||||
|
Text = $"🤨 {result.Score} von {result.Total} richtig.";
|
||||||
|
}
|
||||||
|
else if (percent > 0.0)
|
||||||
|
{
|
||||||
|
Text = $"😑 {result.Score} von {result.Total} richtig.";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Text = $"😭 {result.Score} von {result.Total} richtig.";
|
||||||
|
}
|
||||||
|
if (result.TimeRequired != null)
|
||||||
|
{
|
||||||
|
int SecondsRequired = (int)Math.Round(result.TimeRequired.Value.TotalSeconds, 0, MidpointRounding.AwayFromZero);
|
||||||
|
TimeRequiredText = $"In {SecondsRequired} s gelöst.";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TimeRequiredText = "";
|
||||||
|
}
|
||||||
|
_model = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReactiveCommand<Unit, ResultModel> RestartCommand { get; }
|
||||||
|
|
||||||
|
public ResultViewModel()
|
||||||
|
{
|
||||||
|
RestartCommand = ReactiveCommand.Create(
|
||||||
|
execute: () =>
|
||||||
|
{
|
||||||
|
return _model;
|
||||||
|
},
|
||||||
|
canExecute: Observable.Return(true).ObserveOn(RxApp.MainThreadScheduler),
|
||||||
|
outputScheduler: RxApp.MainThreadScheduler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
42
ViewModels/ViewModelBase.cs
Normal file
42
ViewModels/ViewModelBase.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace EinmaleinsTrainer.ViewModels;
|
||||||
|
|
||||||
|
public class ViewModelBase : IReactiveObject
|
||||||
|
{
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
public event PropertyChangingEventHandler? PropertyChanging;
|
||||||
|
|
||||||
|
public void RaisePropertyChanged(PropertyChangedEventArgs args)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RaisePropertyChanging(PropertyChangingEventArgs args)
|
||||||
|
{
|
||||||
|
PropertyChanging?.Invoke(this, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnPropertyChanging([CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
if (Equals(field, value)) return false;
|
||||||
|
OnPropertyChanging(propertyName);
|
||||||
|
field = value;
|
||||||
|
OnPropertyChanged(propertyName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
52
Views/MainView.axaml
Normal file
52
Views/MainView.axaml
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<UserControl
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="using:EinmaleinsTrainer.ViewModels"
|
||||||
|
x:Class="EinmaleinsTrainer.Views.MainView"
|
||||||
|
x:DataType="vm:MainViewModel">
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="10" Margin="20">
|
||||||
|
|
||||||
|
<TextBlock FontSize="32" Text="Einmaleins Trainer"/>
|
||||||
|
|
||||||
|
<WrapPanel>
|
||||||
|
<CheckBox FontSize="24" Content="1" IsChecked="{Binding IsRow1Checked}" Width="60" />
|
||||||
|
<CheckBox FontSize="24" Content="2" IsChecked="{Binding IsRow2Checked}" Width="60" />
|
||||||
|
<CheckBox FontSize="24" Content="3" IsChecked="{Binding IsRow3Checked}" Width="60" />
|
||||||
|
<CheckBox FontSize="24" Content="4" IsChecked="{Binding IsRow4Checked}" Width="60" />
|
||||||
|
<CheckBox FontSize="24" Content="5" IsChecked="{Binding IsRow5Checked}" Width="60" />
|
||||||
|
</WrapPanel>
|
||||||
|
|
||||||
|
<WrapPanel>
|
||||||
|
<CheckBox FontSize="24" Content="6" IsChecked="{Binding IsRow6Checked}" Width="60" />
|
||||||
|
<CheckBox FontSize="24" Content="7" IsChecked="{Binding IsRow7Checked}" Width="60" />
|
||||||
|
<CheckBox FontSize="24" Content="8" IsChecked="{Binding IsRow8Checked}" Width="60" />
|
||||||
|
<CheckBox FontSize="24" Content="9" IsChecked="{Binding IsRow9Checked}" Width="60" />
|
||||||
|
<CheckBox FontSize="24" Content="10" IsChecked="{Binding IsRow10Checked}" Width="60" />
|
||||||
|
</WrapPanel>
|
||||||
|
|
||||||
|
<CheckBox FontSize="24" Content="Quadratzahlen" IsChecked="{Binding IncludeSquares}" />
|
||||||
|
|
||||||
|
<WrapPanel>
|
||||||
|
<Grid Height="44">
|
||||||
|
<CheckBox FontSize="24" Content="Zeitlimit" VerticalAlignment="Center"
|
||||||
|
IsChecked="{Binding UseTimer}" Width="140"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<NumericUpDown Minimum="2" Maximum="120" FontSize="24" Value="{Binding SecondsPerQuestion, Mode=TwoWay}" Width="140" FormatString="F0" ParsingNumberStyle="Integer" IsVisible="{Binding UseTimer}" />
|
||||||
|
|
||||||
|
<Grid Height="40">
|
||||||
|
<TextBlock FontSize="24" Text=" s" VerticalAlignment="Center" IsVisible="{Binding UseTimer}" />
|
||||||
|
</Grid>
|
||||||
|
</WrapPanel>
|
||||||
|
|
||||||
|
<StackPanel HorizontalAlignment="Center" >
|
||||||
|
<Button FontSize="24" Content="Start" IsDefault="True"
|
||||||
|
Command="{Binding StartCommand}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</UserControl>
|
||||||
39
Views/MainView.xaml.cs
Normal file
39
Views/MainView.xaml.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using EinmaleinsTrainer.ViewModels;
|
||||||
|
|
||||||
|
namespace EinmaleinsTrainer.Views;
|
||||||
|
|
||||||
|
public partial class MainView : UserControl
|
||||||
|
{
|
||||||
|
public MainView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Row_Checked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is CheckBox cb
|
||||||
|
&& DataContext is MainViewModel vm
|
||||||
|
&& int.TryParse(cb.Content?.ToString(), out int value))
|
||||||
|
{
|
||||||
|
vm.ToggleRow(value, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Row_Unchecked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is CheckBox cb
|
||||||
|
&& DataContext is MainViewModel vm
|
||||||
|
&& int.TryParse(cb.Content?.ToString(), out int value))
|
||||||
|
{
|
||||||
|
vm.ToggleRow(value, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Views/MainWindow.axaml
Normal file
25
Views/MainWindow.axaml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="using:EinmaleinsTrainer.ViewModels"
|
||||||
|
xmlns:views="using:EinmaleinsTrainer.Views"
|
||||||
|
x:Class="EinmaleinsTrainer.Views.MainWindow"
|
||||||
|
x:DataType="vm:MainWindowViewModel">
|
||||||
|
|
||||||
|
<Window.DataTemplates>
|
||||||
|
<DataTemplate DataType="vm:MainViewModel">
|
||||||
|
<views:MainView/>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate DataType="vm:QuizViewModel">
|
||||||
|
<views:QuizView/>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate DataType="vm:ResultViewModel">
|
||||||
|
<views:ResultView/>
|
||||||
|
</DataTemplate>
|
||||||
|
</Window.DataTemplates>
|
||||||
|
|
||||||
|
<ContentControl Content="{Binding Current}" />
|
||||||
|
|
||||||
|
</Window>
|
||||||
|
|
||||||
19
Views/MainWindow.xaml.cs
Normal file
19
Views/MainWindow.xaml.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using EinmaleinsTrainer.ViewModels;
|
||||||
|
|
||||||
|
namespace EinmaleinsTrainer.Views;
|
||||||
|
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
DataContext = new MainWindowViewModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Views/QuizView.axaml
Normal file
29
Views/QuizView.axaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<UserControl
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="using:EinmaleinsTrainer.ViewModels"
|
||||||
|
x:Class="EinmaleinsTrainer.Views.QuizView"
|
||||||
|
x:DataType="vm:QuizViewModel">
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="12" Margin="20">
|
||||||
|
|
||||||
|
<TextBlock FontSize="18"
|
||||||
|
Text="{Binding QuestionNumber, StringFormat='Frage {0}'}" />
|
||||||
|
|
||||||
|
<TextBlock FontSize="36"
|
||||||
|
Text="{Binding CurrentQuestionText}" />
|
||||||
|
|
||||||
|
<TextBox x:Name="AnswerTextBox" FontSize="24" Text="{Binding UserAnswer, Mode=TwoWay}" />
|
||||||
|
|
||||||
|
<Button FontSize="24" Content="OK" IsDefault="True"
|
||||||
|
Command="{Binding SubmitCommand}" />
|
||||||
|
|
||||||
|
<TextBlock FontSize="12" Text="{Binding Score, StringFormat='Punkte: {0}'}" />
|
||||||
|
|
||||||
|
<TextBlock FontSize="18" Text="{Binding RemainingSeconds, StringFormat='Zeit: {0} s'}"
|
||||||
|
IsVisible="{Binding UseTimer}" />
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
26
Views/QuizView.xaml.cs
Normal file
26
Views/QuizView.xaml.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
|
||||||
|
namespace EinmaleinsTrainer.Views;
|
||||||
|
|
||||||
|
public partial class QuizView : UserControl
|
||||||
|
{
|
||||||
|
public QuizView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
this.AttachedToVisualTree += async (_,_) =>
|
||||||
|
{
|
||||||
|
await Dispatcher.UIThread.InvokeAsync(() => { }, DispatcherPriority.Background);
|
||||||
|
|
||||||
|
var tb = this.FindControl<TextBox>("AnswerTextBox");
|
||||||
|
tb?.Focus();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Views/ResultView.axaml
Normal file
29
Views/ResultView.axaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<UserControl
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="using:EinmaleinsTrainer.ViewModels"
|
||||||
|
x:Class="EinmaleinsTrainer.Views.ResultView"
|
||||||
|
x:DataType="vm:ResultViewModel">
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="12" Margin="20">
|
||||||
|
|
||||||
|
<StackPanel HorizontalAlignment="Center" >
|
||||||
|
<TextBlock FontSize="36"
|
||||||
|
Text="{Binding Text}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel HorizontalAlignment="Center" IsVisible="{Binding ShowTimeRequired}" >
|
||||||
|
<TextBlock FontSize="24"
|
||||||
|
Text="{Binding TimeRequiredText}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel HorizontalAlignment="Center" >
|
||||||
|
<Button FontSize="36" Content="Neu starten" IsDefault="True"
|
||||||
|
Command="{Binding RestartCommand}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</UserControl>
|
||||||
18
Views/ResultView.xaml.cs
Normal file
18
Views/ResultView.xaml.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace EinmaleinsTrainer.Views;
|
||||||
|
|
||||||
|
public partial class ResultView : UserControl
|
||||||
|
{
|
||||||
|
public ResultView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
18
app.manifest
Normal file
18
app.manifest
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<!-- This manifest is used on Windows only.
|
||||||
|
Don't remove it as it might cause problems with window transparency and embedded controls.
|
||||||
|
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="SaRociSolutions.OSS.EinmaleinsTrainer"/>
|
||||||
|
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<!-- A list of the Windows versions that this application has been tested on
|
||||||
|
and is designed to work with. Uncomment the appropriate elements
|
||||||
|
and Windows will automatically select the most compatible environment. -->
|
||||||
|
|
||||||
|
<!-- Windows 10 -->
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
</assembly>
|
||||||
Loading…
x
Reference in New Issue
Block a user