├── .gitignore
├── Assets
└── Images
│ ├── Dark
│ ├── Calculator-Dark.png
│ ├── Date-Dark.png
│ ├── Grid-Dark-Filled.png
│ ├── Grid-Dark.png
│ ├── List-Dark-Filled.png
│ ├── List-Dark.png
│ └── Weighting-Dark.png
│ ├── Light
│ ├── Calculator-Light.png
│ ├── Date-Light.png
│ ├── Grid-Light-Filled.png
│ ├── Grid-Light.png
│ ├── List-Light-Filled.png
│ ├── List-Light.png
│ └── Weighting-Light.png
│ └── Logo.png
├── GradeManagement.csproj
├── GradeManagement.sln
├── NOTICE.md
├── README.md
└── src
├── App.axaml
├── App.axaml.cs
├── Converters
├── FloatStringConverter.cs
├── IntStringConverter.cs
└── MonthConverter.cs
├── CustomControls
├── CustomDragButton.cs
└── PanelTemplateSelector.cs
├── Enums
├── AddPageAction.cs
├── DateType.cs
├── ElementType.cs
├── SelectedButtonStyle.cs
└── ThemeMode.cs
├── ExtensionCollection
├── CollectionExtensions.cs
├── ColorExtensions.cs
├── EnumExtensions.cs
├── FloatExtensions.cs
└── StringExtensions.cs
├── Interfaces
├── IAddViewModel.cs
├── IElement.cs
├── IGradable.cs
├── IGradesContainer.cs
├── IListViewModel.cs
└── ITargetGrade.cs
├── Models
├── ColorRepresentation.cs
├── DataManager.cs
├── DragControl.cs
├── Elements
│ ├── BaseClasses
│ │ └── ColorableElement.cs
│ ├── Grade.cs
│ ├── GradeGroup.cs
│ ├── SchoolYear.cs
│ └── Subject.cs
├── MonthRepresentation.cs
└── Settings
│ ├── Preferences.cs
│ └── SettingsManager.cs
├── Program.cs
├── Resources
├── BaseResources.axaml
├── DarkTheme.axaml
├── DarkTheme.cs
├── LightTheme.axaml
├── LightTheme.cs
├── Resources.cs
└── Theme.cs
├── Styles
├── AddPagesStyle.axaml
├── CalendarStyle.axaml
├── ContextMenuStyle.axaml
├── DialogStyle.axaml
├── ListViewStyle.axaml
├── MainStyle.axaml
├── MainWindowStyle.axaml
└── TargetGradeStyle.axaml
├── UtilityCollection
├── ColorUtilities.cs
├── DateUtilities.cs
├── UniversalUtilities.cs
└── WindowUtilities.cs
├── ViewLocator.cs
├── ViewModels
├── AddPages
│ ├── AddGradeViewModel.cs
│ ├── AddSubjectViewModel.cs
│ └── AddYearViewModel.cs
├── BaseClasses
│ ├── AddViewModelBase.cs
│ ├── DialogBase.cs
│ ├── ListViewModelBase.cs
│ └── ViewModelBase.cs
├── Dialogs
│ └── ConfirmationDialogViewModel.cs
├── Lists
│ ├── GradeListViewModel.cs
│ ├── SubjectListViewModel.cs
│ └── YearListViewModel.cs
├── MainWindowViewModel.cs
└── TargetGrade
│ ├── AverageGradeViewModel.cs
│ ├── TargetGradeViewModel.cs
│ └── TargetGradeWindowModel.cs
└── Views
├── AddPages
├── AddGradeWindow.axaml
├── AddGradeWindow.axaml.cs
├── AddSubjectWindow.axaml
├── AddSubjectWindow.axaml.cs
├── AddYearWindow.axaml
└── AddYearWindow.axaml.cs
├── Dialogs
├── ConfirmationDialogView.axaml
└── ConfirmationDialogView.axaml.cs
├── Lists
├── ElementButtonControls
│ ├── ButtonStyleBase.cs
│ ├── GridButton.cs
│ └── ListButton.cs
├── GradeListView.axaml
├── GradeListView.axaml.cs
├── SubjectListView.axaml
├── SubjectListView.axaml.cs
├── YearListView.axaml
└── YearListView.axaml.cs
├── MainWindow.axaml
├── MainWindow.axaml.cs
└── TargetGrade
├── AverageGradeView.axaml
├── AverageGradeView.axaml.cs
├── TargetGradeView.axaml
├── TargetGradeView.axaml.cs
├── TargetGradeWindow.axaml
└── TargetGradeWindow.axaml.cs
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vscode/
3 | .vs/
4 |
5 | bin/
6 | obj/
7 |
8 | *.user
--------------------------------------------------------------------------------
/Assets/Images/Dark/Calculator-Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duck-dev/Grade-Management/24e6ceaedfa9b63c0a5544d716556277d22e774a/Assets/Images/Dark/Calculator-Dark.png
--------------------------------------------------------------------------------
/Assets/Images/Dark/Date-Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duck-dev/Grade-Management/24e6ceaedfa9b63c0a5544d716556277d22e774a/Assets/Images/Dark/Date-Dark.png
--------------------------------------------------------------------------------
/Assets/Images/Dark/Grid-Dark-Filled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duck-dev/Grade-Management/24e6ceaedfa9b63c0a5544d716556277d22e774a/Assets/Images/Dark/Grid-Dark-Filled.png
--------------------------------------------------------------------------------
/Assets/Images/Dark/Grid-Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duck-dev/Grade-Management/24e6ceaedfa9b63c0a5544d716556277d22e774a/Assets/Images/Dark/Grid-Dark.png
--------------------------------------------------------------------------------
/Assets/Images/Dark/List-Dark-Filled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duck-dev/Grade-Management/24e6ceaedfa9b63c0a5544d716556277d22e774a/Assets/Images/Dark/List-Dark-Filled.png
--------------------------------------------------------------------------------
/Assets/Images/Dark/List-Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duck-dev/Grade-Management/24e6ceaedfa9b63c0a5544d716556277d22e774a/Assets/Images/Dark/List-Dark.png
--------------------------------------------------------------------------------
/Assets/Images/Dark/Weighting-Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duck-dev/Grade-Management/24e6ceaedfa9b63c0a5544d716556277d22e774a/Assets/Images/Dark/Weighting-Dark.png
--------------------------------------------------------------------------------
/Assets/Images/Light/Calculator-Light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duck-dev/Grade-Management/24e6ceaedfa9b63c0a5544d716556277d22e774a/Assets/Images/Light/Calculator-Light.png
--------------------------------------------------------------------------------
/Assets/Images/Light/Date-Light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duck-dev/Grade-Management/24e6ceaedfa9b63c0a5544d716556277d22e774a/Assets/Images/Light/Date-Light.png
--------------------------------------------------------------------------------
/Assets/Images/Light/Grid-Light-Filled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duck-dev/Grade-Management/24e6ceaedfa9b63c0a5544d716556277d22e774a/Assets/Images/Light/Grid-Light-Filled.png
--------------------------------------------------------------------------------
/Assets/Images/Light/Grid-Light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duck-dev/Grade-Management/24e6ceaedfa9b63c0a5544d716556277d22e774a/Assets/Images/Light/Grid-Light.png
--------------------------------------------------------------------------------
/Assets/Images/Light/List-Light-Filled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duck-dev/Grade-Management/24e6ceaedfa9b63c0a5544d716556277d22e774a/Assets/Images/Light/List-Light-Filled.png
--------------------------------------------------------------------------------
/Assets/Images/Light/List-Light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duck-dev/Grade-Management/24e6ceaedfa9b63c0a5544d716556277d22e774a/Assets/Images/Light/List-Light.png
--------------------------------------------------------------------------------
/Assets/Images/Light/Weighting-Light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duck-dev/Grade-Management/24e6ceaedfa9b63c0a5544d716556277d22e774a/Assets/Images/Light/Weighting-Light.png
--------------------------------------------------------------------------------
/Assets/Images/Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duck-dev/Grade-Management/24e6ceaedfa9b63c0a5544d716556277d22e774a/Assets/Images/Logo.png
--------------------------------------------------------------------------------
/GradeManagement.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | WinExe
4 | net7.0
5 | enable
6 | 11
7 | true
8 | osx-x64;win-x64;win-x86;linux-x64
9 |
10 |
11 | true
12 |
13 |
14 | GradeManagement
15 | GradeManagement
16 | com.duck-dev.grade-management
17 | 1.0.1
18 | 1.0.1
19 | APPL
20 | ????
21 | GradeManagement
22 | GradeManagementIcon.icns
23 | NSApplication
24 | true
25 |
26 |
27 | none
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | App.axaml
42 | Code
43 |
44 |
45 | GradeListView.axaml
46 | Code
47 |
48 |
49 | SubjectListView.axaml
50 | Code
51 |
52 |
53 | YearListView.axaml
54 | Code
55 |
56 |
57 | AddSubjectWindow.axaml
58 | Code
59 |
60 |
61 | AddGradeWindow.axaml
62 | Code
63 |
64 |
65 | WishGradeWindow.axaml
66 | Code
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/GradeManagement.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GradeManagement", "GradeManagement.csproj", "{24F8B115-AEED-452C-BE37-87FD8B1ABD59}"
4 | EndProject
5 | Global
6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
7 | Debug|Any CPU = Debug|Any CPU
8 | Release|Any CPU = Release|Any CPU
9 | EndGlobalSection
10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
11 | {24F8B115-AEED-452C-BE37-87FD8B1ABD59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
12 | {24F8B115-AEED-452C-BE37-87FD8B1ABD59}.Debug|Any CPU.Build.0 = Debug|Any CPU
13 | {24F8B115-AEED-452C-BE37-87FD8B1ABD59}.Release|Any CPU.ActiveCfg = Release|Any CPU
14 | {24F8B115-AEED-452C-BE37-87FD8B1ABD59}.Release|Any CPU.Build.0 = Release|Any CPU
15 | {24F8B115-AEED-452C-BE37-87FD8B1ABD59}.Release|Any CPU.Deploy.0 = Release|Any CPU
16 | EndGlobalSection
17 | EndGlobal
18 |
--------------------------------------------------------------------------------
/NOTICE.md:
--------------------------------------------------------------------------------
1 | # Avalonia
2 |
3 | The MIT License (MIT)
4 |
5 | Copyright (c) .NET Foundation and Contributors All Rights Reserved
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Grade Management
2 |
3 |
4 | A modern, highly customizable and extraordinarily feature-rich solution for storing, organizing and managing your school grades.
5 | This distinguishes it enormously from equivalent products. And... it's 100% **free** and **open-source**!
6 |
7 | Keep track of all your grades, divided into different semesters and subjects, as well as averages and more information about your grades.
8 | Organize your grades however you prefer it and let them get displayed according to your preferences.
9 | Play around with different views, colors and plenty of settings.
10 |
11 | ## A non-exhaustive list of the most important features
12 |
13 | Despite a very high freedom in terms of customization and functions, simplicity hasn't been omitted in the development of **Grade Management**.
14 | If you like to keep it simple, this tool offers you everything you need.
15 | If you need more than that, make use of the freedoms **Grade Management** offers you...
16 |
17 | ### Functions
18 | - **Organize and split up:** Divide your grades into subjects and these in turn into semesters.
19 | - **Much more than just a grade-value:** If only the grade and the name of a grade aren't enough for you,
20 | how about specifying the **Date** of an exam as well as the **Weighting** (for grades and subjects)
21 | and actually considering it in the average calculation?
22 | - **Calculate Target-Grade:**
23 | You want to know how much you're supposed to learn for the next exam in order to reach a certain average?
24 | Sure, let **Grade Management** calculate it for you.
25 | - **Calculate Average:**
26 | The same goes for calculating the hypothetical average with your next assumed grade.
27 | - **Many convenient features:** Have you made yourself comfortable?
28 | Duplicate, copy, paste, create and delete semesters, subjects or grades with a few mouse clicks within a fraction of a second.
29 | - **Import/Export Data:** Are you afraid of losing your data?
30 | Don't fear anymore, export your semesters or let them be automatically exported at a certain specified time. (Coming soon)
31 | - **Many settings:** Customize your **Grade Management** with a wide range of settings. (Coming soon)
32 | - **Order elements:** Order the elements according to many different parameters
33 | or just drag your own preferred order whenever you feel like it. (Coming soon)
34 | - **Multi-Lingual:** Do you speak English... oder Deutsch... ou Français?
35 | We offer you freedom in the choice of your favored language. (Coming soon)
36 |
37 | ### Look and Design
38 | - **Sooo
39 | colorful:**
40 | Assign a **color** to each semester and subject to recognize them at first glance.
41 | - **Intuitive and simple, yet so powerful:** Incredibly **intuitive** layout and navigation.
42 | - **Different views:**
43 | Do you prefer to view your semesters, subjects or grades in a **grid**- or **list**-pattern? Whatever, you can decide for yourself!
44 | - **Themes:** Light Theme or Dark Theme? We say **both** and even **more**. (Coming soon)
45 |
46 | ### Privacy
47 | Grade Management collects no personal data and requires no internet connection for any of the features. It is a 100% "local"
48 | application without any hidden or visible data transfers. Solely your semesters/subjects/grades and settings are being saved
49 | **locally**, but are not visible to anyone except the user of the local device. Your grades are incognito, hooray!
50 |
51 | ## Development
52 | This tool is being developed with the open-source .NET XAML UI-framework [Avalonia](https://avaloniaui.net/) and C#.
53 |
54 | ## Gallery
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/App.axaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/App.axaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using Avalonia;
5 | using Avalonia.Controls.ApplicationLifetimes;
6 | using Avalonia.Markup.Xaml;
7 | using Avalonia.Markup.Xaml.Styling;
8 | using GradeManagement.Enums;
9 | using GradeManagement.ViewModels;
10 | using GradeManagement.Views;
11 |
12 | namespace GradeManagement
13 | {
14 | public class App : Application
15 | {
16 | private const string ResourcesPath = "avares://GradeManagement/src/Resources/";
17 |
18 | private ThemeMode _theme = ThemeMode.None;
19 | private StyleInclude? _currentThemeStyle;
20 | private readonly Dictionary _themeSourcesCollection = new()
21 | {
22 | { ThemeMode.Light, new Uri(Path.Combine(ResourcesPath, "LightTheme.axaml")) },
23 | { ThemeMode.Dark, new Uri(Path.Combine(ResourcesPath, "DarkTheme.axaml")) }
24 | };
25 |
26 | public App() => SetTheme(ThemeMode.Light); // TODO: Get from saved settings
27 |
28 | public override void Initialize()
29 | {
30 | AvaloniaXamlLoader.Load(this);
31 | }
32 |
33 | public override void OnFrameworkInitializationCompleted()
34 | {
35 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
36 | {
37 | desktop.MainWindow = new MainWindow
38 | {
39 | DataContext = new MainWindowViewModel { AppInstance = this }
40 | };
41 | }
42 |
43 | base.OnFrameworkInitializationCompleted();
44 | }
45 |
46 | internal void SetTheme(ThemeMode theme)
47 | {
48 | if (theme == _theme)
49 | return;
50 | _theme = theme;
51 |
52 | if (_currentThemeStyle is not null)
53 | this.Styles.Remove(_currentThemeStyle);
54 | _currentThemeStyle = new StyleInclude(_themeSourcesCollection[theme])
55 | {
56 | Source = _themeSourcesCollection[theme]
57 | };
58 | this.Styles.Add(_currentThemeStyle);
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/src/Converters/FloatStringConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics.CodeAnalysis;
3 | using System.Globalization;
4 | using Avalonia.Data.Converters;
5 |
6 | namespace GradeManagement.Converters
7 | {
8 | public class FloatStringConverter : IValueConverter
9 | {
10 | public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
11 | => value?.ToString();
12 |
13 | [SuppressMessage("ReSharper", "HeapView.BoxingAllocation")] // Unfortunately, I can't change it
14 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
15 | {
16 | if (value is string stringValue && float.TryParse(stringValue, out float floatValue))
17 | return floatValue;
18 |
19 | return 0;
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/src/Converters/IntStringConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics.CodeAnalysis;
3 | using System.Globalization;
4 | using Avalonia.Data.Converters;
5 |
6 | namespace GradeManagement.Converters
7 | {
8 | public class IntStringConverter : IValueConverter
9 | {
10 | public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
11 | => value?.ToString();
12 |
13 | [SuppressMessage("ReSharper", "HeapView.BoxingAllocation")] // Unfortunately, I can't change it
14 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
15 | {
16 | if (value is string stringValue && int.TryParse(stringValue, out int intValue))
17 | return intValue;
18 |
19 | return -1;
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/src/Converters/MonthConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using Avalonia.Data.Converters;
4 | using GradeManagement.Enums;
5 | using GradeManagement.ExtensionCollection;
6 | using GradeManagement.Models;
7 | using GradeManagement.UtilityCollection;
8 | using GradeManagement.ViewModels.AddPages;
9 |
10 | namespace GradeManagement.Converters
11 | {
12 | public class MonthConverter : IValueConverter
13 | {
14 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
15 | {
16 | var instance = AddGradeViewModel.Instance;
17 | return instance is null ? string.Empty : instance.SelectedMonth.MonthName;
18 | }
19 |
20 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
21 | {
22 | if (value is not string monthName)
23 | return ConversionFailed();
24 |
25 | // ReSharper disable once InlineOutVariableDeclaration
26 | int monthNumber;
27 | if (int.TryParse(monthName, out monthNumber))
28 | {
29 | if (!Utilities.ValidateDate(1, monthNumber, Utilities.TodaysYear, out var protocol) // Day and year aren't important
30 | && protocol.CustomHasFlag(DateType.Month))
31 | {
32 | return ConversionFailed();
33 | }
34 | return new MonthRepresentation(monthNumber);
35 | }
36 | else if (TryParseMonth(monthName, out monthNumber) &&
37 | Utilities.ValidateDate(1, monthNumber, Utilities.TodaysYear, out _)) // Day and year aren't important
38 | return new MonthRepresentation(monthName);
39 |
40 | return ConversionFailed();
41 | }
42 |
43 | internal static string ConvertMonth(int month)
44 | {
45 | var date = new DateTime(Utilities.TodaysYear, month, 1); // Day and year aren't important
46 | // TODO: Use currently selected language as culture once it has been implemented
47 | return date.ToString("MMMM", CultureInfo.CurrentCulture);
48 | }
49 |
50 | internal static bool TryParseMonth(string month, out int monthNumber)
51 | {
52 | monthNumber = 0;
53 | // TODO: Use currently selected language as culture once it has been implemented
54 | if (!DateTime.TryParseExact(month, "MMMM", CultureInfo.CurrentCulture,
55 | DateTimeStyles.None, out var dateTime))
56 | return false;
57 |
58 | monthNumber = dateTime.Month;
59 | return true;
60 | }
61 |
62 | private static MonthRepresentation ConversionFailed() => new(string.Empty);
63 | }
64 | }
--------------------------------------------------------------------------------
/src/CustomControls/CustomDragButton.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Avalonia.Controls;
3 | using Avalonia.Input;
4 | using Avalonia.Styling;
5 |
6 | namespace GradeManagement.CustomControls
7 | {
8 | public sealed class CustomDragButton : Button, IStyleable
9 | {
10 | public event EventHandler PointerPressedPreview = delegate { };
11 |
12 | Type IStyleable.StyleKey => typeof(Button);
13 |
14 | protected override void OnPointerPressed(PointerPressedEventArgs args)
15 | {
16 | PointerPressedPreview(this, args);
17 | base.OnPointerPressed(args);
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/src/CustomControls/PanelTemplateSelector.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Avalonia.Controls;
3 | using Avalonia.Controls.Templates;
4 | using Avalonia.Metadata;
5 |
6 | namespace GradeManagement.CustomControls
7 | {
8 | public class PanelTemplateSelector : IDataTemplate
9 | {
10 | [Content]
11 | public Dictionary Templates { get; } = new();
12 | public IControl Build(object data) => Templates[(bool)data].Build(data);
13 | public bool Match(object data) => data is bool;
14 | }
15 | }
--------------------------------------------------------------------------------
/src/Enums/AddPageAction.cs:
--------------------------------------------------------------------------------
1 | namespace GradeManagement.Enums
2 | {
3 | public enum AddPageAction
4 | {
5 | Create,
6 | Edit
7 | }
8 | }
--------------------------------------------------------------------------------
/src/Enums/DateType.cs:
--------------------------------------------------------------------------------
1 | namespace GradeManagement.Enums
2 | {
3 | [System.Flags]
4 | public enum DateType
5 | {
6 | None = 0,
7 | Day = 1,
8 | Month = 2,
9 | Year = 4
10 | }
11 | }
--------------------------------------------------------------------------------
/src/Enums/ElementType.cs:
--------------------------------------------------------------------------------
1 | namespace GradeManagement.Enums
2 | {
3 | public enum ElementType
4 | {
5 | SchoolYear,
6 | Subject,
7 | Grade,
8 | }
9 | }
--------------------------------------------------------------------------------
/src/Enums/SelectedButtonStyle.cs:
--------------------------------------------------------------------------------
1 | namespace GradeManagement.Enums
2 | {
3 | public enum SelectedButtonStyle
4 | {
5 | Grid = 0,
6 | List = 1
7 | }
8 | }
--------------------------------------------------------------------------------
/src/Enums/ThemeMode.cs:
--------------------------------------------------------------------------------
1 | namespace GradeManagement.Enums
2 | {
3 | public enum ThemeMode
4 | {
5 | None,
6 | Light,
7 | Dark
8 | }
9 | }
--------------------------------------------------------------------------------
/src/ExtensionCollection/CollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace GradeManagement.ExtensionCollection
6 | {
7 | public static partial class Extensions
8 | {
9 | ///
10 | /// Add the element to the collection only if it doesn't contain it already.
11 | ///
12 | /// The collection to which the element should be added.
13 | /// The element to be added.
14 | public static void SafeAdd(this ICollection list, T element)
15 | {
16 | if(!list.Contains(element))
17 | list.Add(element);
18 | }
19 |
20 | ///
21 | /// Remove the element from the collection only if it already contains it.
22 | ///
23 | /// The collection, the element should be removed from.
24 | /// The element to be removed.
25 | public static void SafeRemove(this ICollection list, T element)
26 | {
27 | if (list.Contains(element))
28 | list.Remove(element);
29 | }
30 |
31 | ///
32 | /// Create a deep copy of a collection.
33 | ///
34 | /// The list to be cloned.
35 | /// The type of the elements inside the collection. All of them ought to inherit
36 | ///
37 | /// The cloned collection.
38 | public static IEnumerable Clone(this IEnumerable list) where T : ICloneable
39 | => list.Select(x => (T)x.Clone());
40 | }
41 | }
--------------------------------------------------------------------------------
/src/ExtensionCollection/ColorExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics.CodeAnalysis;
3 | using Avalonia.Media;
4 |
5 | namespace GradeManagement.ExtensionCollection
6 | {
7 | [SuppressMessage("ReSharper", "InconsistentNaming")]
8 | public static partial class Extensions
9 | {
10 | ///
11 | /// Linear interpolation between two colors.
12 | ///
13 | /// Start value (a)
14 | /// End value (b)
15 | /// Amount (t)
16 | /// Linearly interpolated value.
17 | public static Color Lerp(this Color color, Color to, float amount)
18 | {
19 | byte a = (byte) ((float) color.A).Lerp(to.A, amount),
20 | r = (byte) ((float) color.R).Lerp(to.R, amount),
21 | g = (byte) ((float) color.G).Lerp(to.G, amount),
22 | b = (byte) ((float) color.B).Lerp(to.B, amount);
23 | return Color.FromArgb(a, r, g, b);
24 | }
25 |
26 | ///
27 | /// Chooses the specified dark or light tint, based on the background's brightness.
28 | /// Dark background => Light tint and vice-versa.
29 | ///
30 | /// The background color, whose brightness determines the foreground tint.
31 | /// The dark tint.
32 | /// The light tint.
33 | /// The threshold, at which the color becomes dark upwards and light downwards.
34 | /// Default value: 110
35 | /// The adjusted foreground color.
36 | public static Color AdjustForegroundBrightness(this Color backgroundColor, Color darkColor, Color lightColor, int threshold = 110)
37 | => ((PerceivedBrightness(backgroundColor) > threshold) ? darkColor : lightColor);
38 |
39 | ///
40 | /// Calculate the brightness of a color.
41 | ///
42 | /// The passed .
43 | /// The brightness represented as an integer.
44 | [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
45 | public static int PerceivedBrightness(this Color color)
46 | {
47 | return (int)Math.Sqrt(
48 | color.R * color.R * .299 +
49 | color.G * color.G * .587 +
50 | color.B * color.B * .114);
51 | }
52 |
53 | ///
54 | /// Darkens the specified color by
55 | ///
56 | /// The color to be darkened.
57 | /// How much darker should the color become?
58 | /// The adjusted color.
59 | public static Color DarkenColor(this Color color, float amount) => AdjustTint(color, Colors.Black, amount);
60 |
61 | ///
62 | /// Brightens the specified color by
63 | ///
64 | /// The color to be brightened.
65 | /// How much brighter should the color become?
66 | /// The adjusted color.
67 | public static Color BrightenColor(this Color color, float amount) => AdjustTint(color, Colors.White, amount);
68 |
69 | ///
70 | /// Adjust the specified color by to be closer to the color.
71 | ///
72 | /// The color to be adjusted.
73 | /// The goal color, to which the tends to go.
74 | /// How close should the be to the color?
75 | /// The adjusted color.
76 | public static Color AdjustTint(this Color color, Color goal, float amount) => color.Lerp(goal, amount);
77 |
78 | ///
79 | /// Return the hexadecimal representation of a .
80 | ///
81 | /// The color to use for the hexadecimal representation.
82 | /// The hexadecimal representation of this
83 | public static string ToHexString(this Color color) => $"#{color.A:X2}{color.R:X2}{color.G:X2}{color.B:X2}";
84 |
85 | public static Color WithR(this Color color, byte r) =>
86 | new (color.A, r, color.G, color.B);
87 |
88 | public static Color WithRG(this Color color, byte r, byte g)
89 | => new(color.A, r, g, color.B);
90 |
91 | public static Color WithRB(this Color color, byte r, byte b)
92 | => new(color.A, r, color.G, b);
93 |
94 | public static Color WithRA(this Color color, byte r, byte a)
95 | => new(a, r, color.G, color.B);
96 |
97 | public static Color WithRGB(this Color color, byte r, byte g, byte b)
98 | => new(color.A, r, g, b);
99 |
100 | public static Color WithRGA(this Color color, byte r, byte g, byte a)
101 | => new(a, r, g, color.B);
102 |
103 | public static Color WithRBA(this Color color, byte r, byte b, byte a)
104 | => new(a, r, color.G, b);
105 |
106 | public static Color WithG(this Color color, byte g)
107 | => new(color.A, color.R, g, color.B);
108 |
109 | public static Color WithGB(this Color color, byte g, byte b)
110 | => new(color.A, color.R, g, b);
111 |
112 | public static Color WithGA(this Color color, byte g, byte a)
113 | => new(a, color.R, g, color.B);
114 |
115 | public static Color WithGBA(this Color color, byte g, byte b, byte a)
116 | => new(a, color.R, g, b);
117 |
118 | public static Color WithB(this Color color, byte b)
119 | => new(color.A, color.R, color.G, b);
120 |
121 | public static Color WithBA(this Color color, byte b, byte a)
122 | => new(a, color.R, color.G, b);
123 |
124 | public static Color WithA(this Color color, byte a)
125 | => new(a, color.R, color.G, color.B);
126 |
127 | public static Color With(this Color color, byte r, byte g, byte b, byte a)
128 | => new(a, r, g, b);
129 | }
130 | }
--------------------------------------------------------------------------------
/src/ExtensionCollection/EnumExtensions.cs:
--------------------------------------------------------------------------------
1 | using GradeManagement.Enums;
2 |
3 | namespace GradeManagement.ExtensionCollection
4 | {
5 | public static partial class Extensions
6 | {
7 | ///
8 | /// Faster version of HasFlag
9 | ///
10 | /// The extended enum.
11 | /// The flag value.
12 | /// Has flag?
13 | public static bool CustomHasFlag(this DateType enumeration, DateType value)
14 | => (enumeration & value) == value;
15 | }
16 | }
--------------------------------------------------------------------------------
/src/ExtensionCollection/FloatExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace GradeManagement.ExtensionCollection
4 | {
5 | public static partial class Extensions
6 | {
7 | ///
8 | /// Linear interpolation between two float's.
9 | ///
10 | /// Start value (a)
11 | /// End value (b)
12 | /// Amount (t)
13 | /// Linearly interpolated value.
14 | public static float Lerp(this float start, float end, float amount)
15 | {
16 | amount = Math.Clamp(amount, 0, 1);
17 | return start + (end - start) * amount;
18 | }
19 |
20 | ///
21 | /// Test equality between two nullable floats.
22 | ///
23 | /// Nullable float A
24 | /// Nullable float B
25 | /// The tolerance for deviation.
26 | /// Returns `true` if the absolute difference is smaller than the tolerance, otherwise it returns `false`.
27 | public static bool AlmostEquals(this float? a, float? b, float tolerance)
28 | {
29 | // ReSharper disable once CompareOfFloatsByEqualityOperator
30 | if (a is not { } aFloat || b is not { } bFloat)
31 | return a == b;
32 |
33 | return AlmostEquals(aFloat, bFloat, tolerance);
34 | }
35 |
36 | ///
37 | /// Test equality between two floats.
38 | ///
39 | /// Float a
40 | /// Float b
41 | /// The tolerance for deviation.
42 | /// Returns `true` if the absolute difference is smaller than the tolerance, otherwise it returns `false`.
43 | public static bool AlmostEquals(this float a, float b, float tolerance)
44 | => Math.Abs(a - b) < tolerance;
45 | }
46 | }
--------------------------------------------------------------------------------
/src/ExtensionCollection/StringExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Text.RegularExpressions;
2 |
3 | namespace GradeManagement.ExtensionCollection
4 | {
5 | public static partial class Extensions
6 | {
7 | ///
8 | /// Split a string with a camelCase (or PascalCase) pattern into each word and join them with a space together.
9 | ///
10 | /// The input string that's supposed to be split.
11 | /// The separator, which will be used to separate each word in the final string.
12 | /// Default: Single Space.
13 | /// An array of all separate words.
14 | public static string SplitCamelCase(this string input, string separator = " ")
15 | {
16 | var words = Regex.Split(input, "([A-Z][a-z]+)");
17 | return string.Join(separator, words).Trim();
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/src/Interfaces/IAddViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace GradeManagement.Interfaces
2 | {
3 | public interface IAddViewModel where T : IElement
4 | {
5 | void EditElement(T element);
6 | }
7 | }
--------------------------------------------------------------------------------
/src/Interfaces/IElement.cs:
--------------------------------------------------------------------------------
1 | using GradeManagement.Views.Lists.ElementButtonControls;
2 |
3 | namespace GradeManagement.Interfaces
4 | {
5 | public interface IElement : IGradable
6 | {
7 | string Name { get; }
8 | ButtonStyleBase? ButtonStyle { get; set; }
9 |
10 | T? Duplicate(bool save = true) where T : class, IElement;
11 | void Save(T? element = null) where T : class, IElement;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Interfaces/IGradable.cs:
--------------------------------------------------------------------------------
1 | namespace GradeManagement.Interfaces
2 | {
3 | public interface IGradable
4 | {
5 | float GradeValue { get; }
6 | float Weighting { get; }
7 | bool Counts { get; }
8 | int ElementCount { get; }
9 | }
10 | }
--------------------------------------------------------------------------------
/src/Interfaces/IGradesContainer.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using GradeManagement.Models.Elements;
3 |
4 | namespace GradeManagement.Interfaces;
5 |
6 | public interface IGradesContainer
7 | {
8 | IGradesContainer? ParentContainer { get; }
9 | List Grades { get; }
10 | }
--------------------------------------------------------------------------------
/src/Interfaces/IListViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 |
3 | namespace GradeManagement.Interfaces
4 | {
5 | public interface IListViewModel where T : class, IElement
6 | {
7 | ObservableCollection? Items { get; set; }
8 | bool EmptyCollection { get; }
9 |
10 | void Duplicate(IElement element);
11 | }
12 | }
--------------------------------------------------------------------------------
/src/Interfaces/ITargetGrade.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using GradeManagement.Models.Elements;
3 |
4 | namespace GradeManagement.Interfaces
5 | {
6 | public interface ITargetGrade
7 | {
8 | IEnumerable? Grades { get; set; }
9 | }
10 | }
--------------------------------------------------------------------------------
/src/Models/ColorRepresentation.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Media;
2 | using ReactiveUI;
3 |
4 | namespace GradeManagement.Models
5 | {
6 | public class ColorRepresentation : ReactiveObject
7 | {
8 | private bool _selected;
9 |
10 | public ColorRepresentation(Color color)
11 | {
12 | ElementColor = color;
13 | ElementColorBrush = new SolidColorBrush(color);
14 | }
15 |
16 | internal Color ElementColor { get; }
17 | internal SolidColorBrush ElementColorBrush { get; }
18 | internal bool Selected
19 | {
20 | get => _selected;
21 | set => this.RaiseAndSetIfChanged(ref _selected, value);
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/src/Models/DataManager.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using System.Text.Json;
4 | using GradeManagement.Models.Elements;
5 | using GradeManagement.UtilityCollection;
6 |
7 | namespace GradeManagement.Models
8 | {
9 | public static class DataManager
10 | {
11 | internal static List SchoolYears { get; private set; } = new();
12 |
13 | private static string FilePath => Path.Combine(Utilities.FilesParentPath, "SchoolYears.json");
14 |
15 | internal static void LoadData()
16 | {
17 | if (!File.Exists(FilePath))
18 | return;
19 |
20 | string content = File.ReadAllText(FilePath);
21 | var deserializedList = JsonSerializer.Deserialize>(content);
22 | if(deserializedList is not null)
23 | SchoolYears = deserializedList;
24 | }
25 |
26 | internal static void SaveData()
27 | {
28 | var options = new JsonSerializerOptions { WriteIndented = true };
29 | string jsonString = JsonSerializer.Serialize(SchoolYears, options);
30 | File.WriteAllText(FilePath, jsonString);
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/src/Models/DragControl.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Input;
3 | using Avalonia.Interactivity;
4 |
5 | namespace GradeManagement.Models
6 | {
7 | public abstract class DragControl : UserControl
8 | {
9 | protected DragControl()
10 | {
11 | AddHandler(DragDrop.DropEvent, Drop);
12 | AddHandler(DragDrop.DragEnterEvent, DragEnter);
13 | AddHandler(DragDrop.DragLeaveEvent, DragLeave);
14 | }
15 |
16 | internal async void BeginDrag(object? sender, PointerPressedEventArgs args)
17 | {
18 | if (sender is not Button button)
19 | return;
20 |
21 | DataObject data = new();
22 | data.Set("Button", button);
23 | await DragDrop.DoDragDrop(args, data, DragDropEffects.Copy);
24 | }
25 |
26 | private void Drop(object? sender, DragEventArgs args)
27 | {
28 | if (sender is not Button button)
29 | return;
30 | }
31 |
32 | private void DragEnter(object? sender, DragEventArgs args)
33 | {
34 | if (sender is not Button button)
35 | return;
36 | }
37 |
38 | private void DragLeave(object? sender, RoutedEventArgs args)
39 | {
40 | if (sender is not Button button)
41 | return;
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/src/Models/Elements/BaseClasses/ColorableElement.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using Avalonia;
3 | using Avalonia.Media;
4 | using GradeManagement.Enums;
5 | using GradeManagement.ExtensionCollection;
6 | using GradeManagement.UtilityCollection;
7 | using ReactiveUI;
8 |
9 | // ReSharper disable once CheckNamespace
10 | namespace GradeManagement.Models.Elements
11 | {
12 | public class ColorableElement : ReactiveObject
13 | {
14 | private const float TitleDarkenFactor = 0.3f;
15 | private const float TitleBrightenFactor = 0.5f;
16 | private const float AdditionalInfoDarkenFactor = 0.25f;
17 | private const float AdditionalInfoBrightenFactor = 0.3f;
18 |
19 | private const int GridThresholdTitle = 110;
20 | private const int ListThresholdTitle = 135;
21 |
22 | private readonly Color _additionalInfoBaseColor = Color.Parse("#999999");
23 | private readonly Color _lightBackground = Color.Parse("#c7cad1");
24 |
25 | private SolidColorBrush? _additionalInfoGrid;
26 | private SolidColorBrush? _additionalInfoList;
27 | private SolidColorBrush? _titleBrushGrid;
28 | private SolidColorBrush? _titleBrushList;
29 | private SelectedButtonStyle _buttonStyle;
30 |
31 | private string _elementColorHex = "#c7cad1";
32 | private SolidColorBrush? _additionalInfoBrush;
33 |
34 | private bool _darkSymbols;
35 |
36 | protected ColorableElement(string elementColorHex) => this.ElementColorHex = elementColorHex;
37 |
38 | [JsonInclude]
39 | public string ElementColorHex
40 | {
41 | get => _elementColorHex;
42 | protected set
43 | {
44 | if (_elementColorHex.Equals(value))
45 | return;
46 | _elementColorHex = value;
47 | ApplyChangedColor();
48 |
49 | this.RaisePropertyChanged(nameof(ElementColor));
50 | this.RaisePropertyChanged(nameof(TitleBrush));
51 | this.RaisePropertyChanged(nameof(BackgroundBrush));
52 | this.RaisePropertyChanged(nameof(BackgroundBrushHover));
53 | this.RaisePropertyChanged(nameof(AdditionalInfoColor));
54 | }
55 | }
56 |
57 | protected virtual int GridThresholdAdditionalInfo { get; }
58 | protected virtual int ListThresholdAdditionalInfo { get; }
59 |
60 | internal Color ElementColor { get; private set; }
61 |
62 | internal SolidColorBrush? TitleBrush { get; private set; }
63 |
64 | internal LinearGradientBrush? BackgroundBrush { get; private set; }
65 |
66 | internal LinearGradientBrush? BackgroundBrushHover { get; private set; }
67 |
68 | internal SolidColorBrush? AdditionalInfoColor
69 | {
70 | get => _additionalInfoBrush;
71 | private set => this.RaiseAndSetIfChanged(ref _additionalInfoBrush, value);
72 | }
73 |
74 | protected bool DarkSymbols
75 | {
76 | get => _darkSymbols;
77 | set => this.RaiseAndSetIfChanged(ref _darkSymbols, value);
78 | }
79 |
80 | private Color DarkTitleTint => ElementColor.DarkenColor(TitleDarkenFactor);
81 | private Color LightTitleTint => ElementColor.BrightenColor(TitleBrightenFactor);
82 |
83 | private Color AdditionalInfoDark => _additionalInfoBaseColor.DarkenColor(AdditionalInfoDarkenFactor);
84 | private Color AdditionalInfoLight => _additionalInfoBaseColor.BrightenColor(AdditionalInfoBrightenFactor);
85 |
86 | protected virtual void ApplyChangedColor()
87 | {
88 | ElementColor = Color.Parse(_elementColorHex);
89 |
90 | var backgroundGradient = Utilities.CreateLinearGradientBrush(
91 | new RelativePoint(0, 1, RelativeUnit.Relative),
92 | new RelativePoint(1, 0, RelativeUnit.Relative),
93 | new[] { ElementColor, _lightBackground },
94 | new[] { 0.2, 1.0 });
95 | BackgroundBrush = backgroundGradient;
96 |
97 | var backgroundGradientHover = Utilities.CreateLinearGradientBrush(
98 | new RelativePoint(0, 1, RelativeUnit.Relative),
99 | new RelativePoint(1, 0, RelativeUnit.Relative),
100 | new[] { ElementColor.DarkenColor(0.075f), _lightBackground.DarkenColor(0.075f) },
101 | new[] { 0.2, 1.0 });
102 | BackgroundBrushHover = backgroundGradientHover;
103 |
104 | SetAdditionalInfoColor();
105 | }
106 |
107 | internal void AdjustTextColors(bool isGrid, bool changeButtonStyle = true)
108 | {
109 | if(changeButtonStyle)
110 | _buttonStyle = isGrid ? SelectedButtonStyle.Grid : SelectedButtonStyle.List;
111 | AdditionalInfoColor = isGrid ? _additionalInfoGrid : _additionalInfoList;
112 | TitleBrush = isGrid ? _titleBrushGrid : _titleBrushList;
113 |
114 | int threshold = isGrid ? GridThresholdAdditionalInfo : ListThresholdAdditionalInfo;
115 | DarkSymbols = ElementColor.PerceivedBrightness() > threshold;
116 | }
117 |
118 | private void SetAdditionalInfoColor()
119 | {
120 | var gridColor = ElementColor.AdjustForegroundBrightness(AdditionalInfoDark, AdditionalInfoLight, GridThresholdAdditionalInfo);
121 | _additionalInfoGrid = new SolidColorBrush(gridColor);
122 | var listColor = ElementColor.AdjustForegroundBrightness(AdditionalInfoDark, AdditionalInfoLight, ListThresholdAdditionalInfo);
123 | _additionalInfoList = new SolidColorBrush(listColor);
124 |
125 | gridColor = ElementColor.AdjustForegroundBrightness(DarkTitleTint, LightTitleTint, GridThresholdTitle);
126 | _titleBrushGrid = new SolidColorBrush(gridColor);
127 | listColor = ElementColor.AdjustForegroundBrightness(DarkTitleTint, LightTitleTint, ListThresholdTitle);
128 | _titleBrushList = new SolidColorBrush(listColor);
129 |
130 | bool isGrid = _buttonStyle == SelectedButtonStyle.Grid;
131 | AdjustTextColors(isGrid, false);
132 | }
133 | }
134 | }
--------------------------------------------------------------------------------
/src/Models/Elements/Grade.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Text.Json.Serialization;
4 | using GradeManagement.Enums;
5 | using GradeManagement.Interfaces;
6 | using GradeManagement.Models.Settings;
7 | using GradeManagement.ViewModels;
8 | using GradeManagement.ViewModels.Lists;
9 | using GradeManagement.Views.Lists.ElementButtonControls;
10 | using ReactiveUI;
11 |
12 | namespace GradeManagement.Models.Elements
13 | {
14 | [JsonPolymorphic(UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)]
15 | [JsonDerivedType(typeof(Grade), "grade")]
16 | [JsonDerivedType(typeof(GradeGroup), "gradeGroup")]
17 | public class Grade : ReactiveObject, IElement, ICloneable
18 | {
19 | private const int MaxNameLength = 64;
20 | private const double EnabledOpacity = 1.0;
21 | private const double DisabledOpacity = 0.4;
22 | private const double DisabledOpacityGrade = 0.4;
23 |
24 | private string _name = string.Empty;
25 | private float _gradeValue = float.NaN;
26 | private float? _scoredPoints;
27 | private float? _maxPoints;
28 | private float _weighting;
29 | private DateTime _date;
30 | private bool _counts;
31 | private ButtonStyleBase? _buttonStyle;
32 |
33 | [JsonConstructor]
34 | public Grade(string name, float gradeValue, float? scoredPoints, float? maxPoints, float weighting, DateTime date, bool counts)
35 | {
36 | if (name.Length > MaxNameLength)
37 | name = name.Substring(0, MaxNameLength);
38 | this.Name = name;
39 | this.GradeValue = gradeValue;
40 | this.ScoredPoints = scoredPoints;
41 | this.MaxPoints = maxPoints;
42 | this.Weighting = weighting;
43 | this.Date = date;
44 | this.Counts = counts;
45 |
46 | var isGrid = SettingsManager.Settings?.GradeButtonStyle == SelectedButtonStyle.Grid;
47 | this.ButtonStyle = isGrid ? new GridButton(this) : new ListButton(this);
48 | }
49 |
50 | [JsonInclude]
51 | public string Name
52 | {
53 | get => _name;
54 | private set => this.RaiseAndSetIfChanged(ref _name, value);
55 | }
56 |
57 | [JsonInclude]
58 | public virtual float GradeValue
59 | {
60 | get => _gradeValue;
61 | private set
62 | {
63 | if (value.Equals(_gradeValue))
64 | return;
65 | _gradeValue = value;
66 | this.RaisePropertyChanged(nameof(RoundedGrade));
67 | }
68 | }
69 |
70 | [JsonInclude]
71 | public float? ScoredPoints
72 | {
73 | get => _scoredPoints;
74 | private set => this.RaiseAndSetIfChanged(ref _scoredPoints, value);
75 | }
76 |
77 | [JsonInclude]
78 | public float? MaxPoints
79 | {
80 | get => _maxPoints;
81 | private set => this.RaiseAndSetIfChanged(ref _maxPoints, value);
82 | }
83 |
84 | [JsonInclude]
85 | public float Weighting
86 | {
87 | get => _weighting;
88 | private set => this.RaiseAndSetIfChanged(ref _weighting, value);
89 | }
90 |
91 | [JsonInclude]
92 | public DateTime Date
93 | {
94 | get => _date;
95 | private set
96 | {
97 | if(value.Equals(_date))
98 | return;
99 | _date = value;
100 | this.RaisePropertyChanged(nameof(DateString));
101 | }
102 | }
103 |
104 | [JsonInclude]
105 | public bool Counts
106 | {
107 | get => _counts;
108 | private set
109 | {
110 | this.RaiseAndSetIfChanged(ref _counts, value);
111 | this.RaisePropertyChanged(nameof(ElementsOpacity));
112 | this.RaisePropertyChanged(nameof(GradeTextOpacity));
113 | }
114 | }
115 |
116 | [JsonIgnore]
117 | public virtual int ElementCount => 1;
118 |
119 | [JsonIgnore]
120 | public ButtonStyleBase? ButtonStyle
121 | {
122 | get => _buttonStyle;
123 | set => this.RaiseAndSetIfChanged(ref _buttonStyle, value);
124 | }
125 |
126 | internal float RoundedGrade => (float)Math.Round(GradeValue, 2);
127 | internal string DateString => Date.ToString("dd.MM.yyyy", CultureInfo.CurrentCulture);
128 | internal double ElementsOpacity => Counts ? EnabledOpacity : DisabledOpacity;
129 | internal double GradeTextOpacity => Counts ? EnabledOpacity : DisabledOpacityGrade;
130 |
131 | internal bool IsMultiGrade => this is GradeGroup;
132 |
133 | internal bool PointsSpecified => !IsMultiGrade && ScoredPoints != null && MaxPoints != null;
134 |
135 | public T? Duplicate(bool save = true) where T : class, IElement
136 | {
137 | if (this.Clone() is not Grade duplicate)
138 | return null;
139 |
140 | if(save)
141 | Save(duplicate);
142 |
143 | return duplicate as T;
144 | }
145 |
146 | public void Save(T? element = null) where T : class, IElement
147 | {
148 | Grade grade = element as Grade ?? this;
149 | IGradesContainer? container = null;
150 | if (MainWindowViewModel.Instance?.Content is GradeListViewModel viewModel)
151 | container = viewModel.GradesContainer;
152 | container?.Grades.Add(grade);
153 | }
154 |
155 | public object Clone() => new Grade(_name, _gradeValue, _scoredPoints, _maxPoints, _weighting, _date, Counts);
156 |
157 | internal void Edit(string newName, float newGrade, float? newScoredPoints, float? newMaxPoints, float newWeighting,
158 | DateTime newDate, bool counts)
159 | {
160 | var oldGrade = this.GradeValue;
161 | var oldCounts = this.Counts;
162 | var oldWeighting = this.Weighting;
163 |
164 | this.Name = newName;
165 | this.GradeValue = newGrade;
166 | this.ScoredPoints = newScoredPoints;
167 | this.MaxPoints = newMaxPoints;
168 | this.Weighting = newWeighting;
169 | this.Date = newDate;
170 | this.Counts = counts;
171 | this.RaisePropertyChanged(nameof(PointsSpecified));
172 |
173 | if (oldCounts == counts && Math.Abs(oldWeighting - newWeighting) < 0.001f && Math.Abs(oldGrade - newGrade) < 0.001f)
174 | return;
175 | var mainInstance = MainWindowViewModel.Instance;
176 | mainInstance?.UpdateAverage();
177 | }
178 | }
179 | }
--------------------------------------------------------------------------------
/src/Models/Elements/GradeGroup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text.Json.Serialization;
5 | using GradeManagement.ExtensionCollection;
6 | using GradeManagement.Interfaces;
7 | using GradeManagement.UtilityCollection;
8 | using GradeManagement.ViewModels;
9 | using GradeManagement.ViewModels.Lists;
10 |
11 | namespace GradeManagement.Models.Elements
12 | {
13 | public class GradeGroup : Grade, IElement, IGradesContainer, ICloneable
14 | {
15 | [JsonConstructor]
16 | public GradeGroup(string name, List grades, float weighting, DateTime date, bool counts)
17 | : base(name, Utilities.GetAverage(grades, false), null, null, weighting, date, counts)
18 | {
19 | this.Grades = grades;
20 | }
21 |
22 | public GradeGroup(string name, ICollection grades, float weighting, DateTime date, bool counts)
23 | : base(name, Utilities.GetAverage(grades, false), null, null, weighting, date, counts)
24 | {
25 | this.Grades = grades.ToList();
26 | }
27 |
28 | [JsonIgnore]
29 | public IGradesContainer? ParentContainer { get; set; }
30 |
31 | [JsonInclude]
32 | public List Grades { get; set; }
33 |
34 | [JsonIgnore]
35 | public override float GradeValue => Utilities.GetAverage(Grades, true);
36 |
37 | [JsonIgnore]
38 | public override int ElementCount => Grades.Count;
39 |
40 | public new object Clone() => new GradeGroup(Name, Grades.Clone().ToList(), Weighting, Date, Counts);
41 |
42 | public new T? Duplicate(bool save = true) where T : class, IElement
43 | {
44 | if (this.Clone() is not GradeGroup duplicate)
45 | return null;
46 |
47 | if(save)
48 | Save(duplicate);
49 | return duplicate as T;
50 | }
51 |
52 | public new void Save(T? element = null) where T : class, IElement
53 | {
54 | GradeGroup grade = element as GradeGroup ?? this;
55 | IGradesContainer? container = null;
56 | if (MainWindowViewModel.Instance?.Content is GradeListViewModel viewModel)
57 | container = viewModel.GradesContainer;
58 | container?.Grades.Add(grade);
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/src/Models/Elements/SchoolYear.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Text.Json.Serialization;
4 | using GradeManagement.Enums;
5 | using GradeManagement.ExtensionCollection;
6 | using GradeManagement.Interfaces;
7 | using GradeManagement.Models.Settings;
8 | using GradeManagement.UtilityCollection;
9 | using GradeManagement.Views.Lists.ElementButtonControls;
10 | using ReactiveUI;
11 |
12 | namespace GradeManagement.Models.Elements
13 | {
14 | public class SchoolYear : ColorableElement, IElement
15 | {
16 | private const int MaxNameLength = 64;
17 |
18 | private string _name = string.Empty;
19 | private List _subjects = new();
20 | private ButtonStyleBase? _buttonStyle;
21 |
22 | public SchoolYear(string name, string elementColorHex) : base(elementColorHex)
23 | {
24 | if (name.Length > MaxNameLength)
25 | name = name.Substring(0, MaxNameLength);
26 | this.Name = name;
27 |
28 | var isGrid = SettingsManager.Settings?.YearButtonStyle == SelectedButtonStyle.Grid;
29 | this.ButtonStyle = isGrid ? new GridButton(this) : new ListButton(this);
30 | AdjustTextColors(isGrid);
31 | }
32 |
33 | [JsonConstructor]
34 | public SchoolYear(string name, string elementColorHex, List subjects) : this(name, elementColorHex)
35 | {
36 | this.Subjects = subjects;
37 | }
38 |
39 | [JsonInclude]
40 | public string Name
41 | {
42 | get => _name;
43 | private set => this.RaiseAndSetIfChanged(ref _name, value);
44 | }
45 |
46 | [JsonInclude]
47 | public List Subjects
48 | {
49 | get => _subjects;
50 | private set
51 | {
52 | if (value.SequenceEqual(_subjects))
53 | return;
54 | this.RaiseAndSetIfChanged(ref _subjects, value);
55 | this.RaisePropertyChanged(nameof(GradeValue));
56 | }
57 | }
58 |
59 | [JsonIgnore]
60 | public ButtonStyleBase? ButtonStyle
61 | {
62 | get => _buttonStyle;
63 | set => this.RaiseAndSetIfChanged(ref _buttonStyle, value);
64 | }
65 |
66 | [JsonIgnore]
67 | public float GradeValue => Utilities.GetAverage(Subjects, true);
68 |
69 | [JsonIgnore]
70 | public float Weighting => 1;
71 |
72 | [JsonIgnore]
73 | public bool Counts => true;
74 |
75 | [JsonIgnore]
76 | public int ElementCount => Subjects.Count;
77 |
78 | protected override int GridThresholdAdditionalInfo => 120;
79 | protected override int ListThresholdAdditionalInfo => 135;
80 |
81 | public T? Duplicate(bool save = true) where T : class, IElement
82 | {
83 | var duplicate = this.Clone();
84 | if(save)
85 | Save(duplicate);
86 |
87 | return duplicate as T;
88 | }
89 |
90 | public void Save(T? element = null) where T : class, IElement
91 | {
92 | SchoolYear year = element as SchoolYear ?? this;
93 | DataManager.SchoolYears.Add(year);
94 | }
95 |
96 | internal void Edit(string newName, string colorHex)
97 | {
98 | this.Name = newName;
99 | this.ElementColorHex = colorHex;
100 | }
101 |
102 | private SchoolYear Clone() => new(_name, ElementColorHex, _subjects.Clone().ToList());
103 | }
104 | }
--------------------------------------------------------------------------------
/src/Models/Elements/Subject.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text.Json.Serialization;
5 | using GradeManagement.Enums;
6 | using GradeManagement.ExtensionCollection;
7 | using GradeManagement.Interfaces;
8 | using GradeManagement.Models.Settings;
9 | using GradeManagement.UtilityCollection;
10 | using GradeManagement.ViewModels;
11 | using GradeManagement.Views.Lists.ElementButtonControls;
12 | using ReactiveUI;
13 |
14 | namespace GradeManagement.Models.Elements
15 | {
16 | public class Subject : ColorableElement, IElement, ICloneable, IGradesContainer
17 | {
18 | private const int MaxNameLength = 64;
19 | private const double EnabledOpacity = 1.0;
20 | private const double DisabledOpacity = 0.6;
21 | private const double DisabledOpacityGrade = 0.4;
22 |
23 | private string _name = string.Empty;
24 | private List _grades = new();
25 | private float _weighting;
26 | private bool _counts;
27 | private ButtonStyleBase? _buttonStyle;
28 |
29 | public Subject(string name, float weighting, string elementColorHex, bool counts) : base(elementColorHex)
30 | {
31 | if (name.Length > MaxNameLength)
32 | name = name.Substring(0, MaxNameLength);
33 | this.Name = name;
34 | this.Weighting = weighting;
35 | this.Counts = counts;
36 |
37 | var isGrid = SettingsManager.Settings?.SubjectButtonStyle == SelectedButtonStyle.Grid;
38 | this.ButtonStyle = isGrid ? new GridButton(this) : new ListButton(this);
39 | AdjustTextColors(isGrid);
40 | }
41 |
42 | [JsonConstructor]
43 | public Subject(string name, float weighting, string elementColorHex, List grades, bool counts)
44 | : this(name, weighting, elementColorHex, counts)
45 | {
46 | this.Grades = grades;
47 | }
48 |
49 | [JsonInclude]
50 | public string Name
51 | {
52 | get => _name;
53 | private set => this.RaiseAndSetIfChanged(ref _name, value);
54 | }
55 |
56 | [JsonIgnore]
57 | public IGradesContainer? ParentContainer { get; }
58 |
59 | [JsonInclude]
60 | public List Grades
61 | {
62 | get => _grades;
63 | private set
64 | {
65 | if (value.SequenceEqual(_grades))
66 | return;
67 | this.RaiseAndSetIfChanged(ref _grades, value);
68 | this.RaisePropertyChanged(nameof(ElementCount));
69 | this.RaisePropertyChanged(nameof(RoundedAverage));
70 | }
71 | }
72 |
73 | [JsonInclude]
74 | public float Weighting
75 | {
76 | get => _weighting;
77 | private set => this.RaiseAndSetIfChanged(ref _weighting, value);
78 | }
79 |
80 | [JsonInclude]
81 | public bool Counts
82 | {
83 | get => _counts;
84 | private set
85 | {
86 | this.RaiseAndSetIfChanged(ref _counts, value);
87 | this.RaisePropertyChanged(nameof(ElementsOpacity));
88 | this.RaisePropertyChanged(nameof(GradeTextOpacity));
89 | }
90 | }
91 |
92 | [JsonIgnore]
93 | public float GradeValue => Utilities.GetAverage(Grades, false);
94 |
95 | [JsonIgnore]
96 | public int ElementCount => Grades.Count;
97 |
98 | [JsonIgnore]
99 | public ButtonStyleBase? ButtonStyle
100 | {
101 | get => _buttonStyle;
102 | set => this.RaiseAndSetIfChanged(ref _buttonStyle, value);
103 | }
104 |
105 | protected override int GridThresholdAdditionalInfo => 120;
106 | protected override int ListThresholdAdditionalInfo => 135;
107 |
108 | internal float RoundedAverage => Utilities.GetAverage(Grades, true);
109 | internal double ElementsOpacity => Counts ? EnabledOpacity : DisabledOpacity;
110 | internal double GradeTextOpacity => Counts ? EnabledOpacity : DisabledOpacityGrade;
111 |
112 | public T? Duplicate(bool save = true) where T : class, IElement
113 | {
114 | if (Clone() is not Subject duplicate)
115 | return null;
116 |
117 | if (save)
118 | Save(duplicate);
119 |
120 | return duplicate as T;
121 | }
122 |
123 | public void Save(T? element = null) where T : class, IElement
124 | {
125 | Subject subject = element as Subject ?? this;
126 | var currentYear = MainWindowViewModel.CurrentYear;
127 | currentYear?.Subjects.Add(subject);
128 | }
129 |
130 | public object Clone() => new Subject(_name, _weighting, ElementColorHex, _grades.Clone().ToList(), Counts);
131 |
132 | internal void Edit(string newName, float newWeighting, string newSubjectColorHex, bool counts)
133 | {
134 | var oldCounts = this.Counts;
135 | var oldWeighting = this.Weighting;
136 |
137 | this.Name = newName;
138 | this.Weighting = newWeighting;
139 | this.Counts = counts;
140 | this.ElementColorHex = newSubjectColorHex;
141 |
142 | if (oldCounts == counts && Math.Abs(oldWeighting - newWeighting) < 0.001f)
143 | return;
144 | var mainInstance = MainWindowViewModel.Instance;
145 | mainInstance?.UpdateAverage();
146 | }
147 | }
148 | }
--------------------------------------------------------------------------------
/src/Models/MonthRepresentation.cs:
--------------------------------------------------------------------------------
1 | using GradeManagement.Converters;
2 |
3 | namespace GradeManagement.Models
4 | {
5 | public class MonthRepresentation
6 | {
7 | private int _month;
8 | private string? _monthName;
9 |
10 | public MonthRepresentation(int month) => this.Month = month;
11 | public MonthRepresentation(string monthName) => this.MonthName = monthName;
12 |
13 | internal int Month
14 | {
15 | get => _month;
16 | private init
17 | {
18 | if (value is <= 0 or > 12)
19 | return;
20 | _month = value;
21 | _monthName = MonthConverter.ConvertMonth(value);
22 | }
23 | }
24 |
25 | internal string MonthName
26 | {
27 | get => _monthName!;
28 | private init
29 | {
30 | if (!MonthConverter.TryParseMonth(value, out int monthNumber))
31 | {
32 | _monthName = string.Empty;
33 | _month = 0;
34 | return;
35 | }
36 |
37 | _monthName = value;
38 | _month = monthNumber;
39 | }
40 | }
41 |
42 | public override string? ToString() => _monthName;
43 | internal void Set(string monthName) => _monthName = monthName;
44 | internal void Set(int monthNum) => _month = monthNum;
45 | }
46 | }
--------------------------------------------------------------------------------
/src/Models/Settings/Preferences.cs:
--------------------------------------------------------------------------------
1 | using GradeManagement.Enums;
2 |
3 | namespace GradeManagement.Models.Settings
4 | {
5 | public class Preferences
6 | {
7 | public SelectedButtonStyle YearButtonStyle { get; set; } = SelectedButtonStyle.Grid;
8 | public SelectedButtonStyle SubjectButtonStyle { get; set; } = SelectedButtonStyle.Grid;
9 | public SelectedButtonStyle GradeButtonStyle { get; set; } = SelectedButtonStyle.Grid;
10 |
11 | public bool ShowRemoveConfirmation { get; set; } = true;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Models/Settings/SettingsManager.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Text.Json;
3 | using System.Text.Json.Serialization;
4 | using GradeManagement.UtilityCollection;
5 |
6 | namespace GradeManagement.Models.Settings
7 | {
8 | public static class SettingsManager
9 | {
10 | internal static Preferences? Settings { get; private set; } = new();
11 | private static string FilePath => Path.Combine(Utilities.FilesParentPath, "Settings.json");
12 |
13 | internal static void LoadSettings()
14 | {
15 | if (!File.Exists(FilePath))
16 | {
17 | SaveSettings();
18 | return;
19 | }
20 |
21 | string content = File.ReadAllText(FilePath);
22 | var options = new JsonSerializerOptions
23 | {
24 | Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }
25 | };
26 | Settings = JsonSerializer.Deserialize(content, options);
27 |
28 | DataManager.LoadData();
29 | }
30 |
31 | internal static void SaveSettings()
32 | {
33 | var options = new JsonSerializerOptions
34 | {
35 | WriteIndented = true,
36 | Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }
37 | };
38 | string jsonString = JsonSerializer.Serialize(Settings, options);
39 | File.WriteAllText(FilePath, jsonString);
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/src/Program.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.ReactiveUI;
3 |
4 | namespace GradeManagement
5 | {
6 | internal static class Program
7 | {
8 | // Initialization code. Don't use any Avalonia, third-party APIs or any
9 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
10 | // yet and stuff might break.
11 | public static void Main(string[] args) => BuildAvaloniaApp()
12 | .StartWithClassicDesktopLifetime(args);
13 |
14 | // Avalonia configuration, don't remove; also used by visual designer.
15 | private static AppBuilder BuildAvaloniaApp()
16 | => AppBuilder.Configure()
17 | .UsePlatformDetect()
18 | .LogToTrace()
19 | .UseReactiveUI();
20 | }
21 | }
--------------------------------------------------------------------------------
/src/Resources/BaseResources.axaml:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/src/Resources/DarkTheme.axaml:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/Resources/DarkTheme.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Media;
3 | using GradeManagement.UtilityCollection;
4 |
5 | namespace GradeManagement.ResourcesNamespace
6 | {
7 | public class DarkTheme : Theme
8 | {
9 | public DarkTheme()
10 | {
11 | SetResources();
12 | }
13 |
14 | protected internal override void SetResources()
15 | {
16 | OppositeAccentBrush =
17 | Utilities.GetResourceFromStyle(Application.Current, "OppositeAccent", StyleIndex)
18 | ?? new SolidColorBrush(Color.Parse("#FFFFFF"));
19 | SameAccentBrush =
20 | Utilities.GetResourceFromStyle(Application.Current, "SameAccent", StyleIndex)
21 | ?? new SolidColorBrush(Color.Parse("#0A0A0A"));
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/src/Resources/LightTheme.axaml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Resources/LightTheme.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Media;
3 | using GradeManagement.UtilityCollection;
4 |
5 | namespace GradeManagement.ResourcesNamespace
6 | {
7 | public class LightTheme : Theme
8 | {
9 | public LightTheme()
10 | {
11 | SetResources();
12 | }
13 |
14 | protected internal override void SetResources()
15 | {
16 | OppositeAccentBrush =
17 | Utilities.GetResourceFromStyle(Application.Current, "OppositeAccent", StyleIndex)
18 | ?? new SolidColorBrush(Color.Parse("#0A0A0A"));
19 | SameAccentBrush =
20 | Utilities.GetResourceFromStyle(Application.Current, "SameAccent", StyleIndex)
21 | ?? new SolidColorBrush(Color.Parse("#FFFFFF"));
22 |
23 | MainBackgroundBrush =
24 | Utilities.GetResourceFromStyle(Application.Current, "MainBackground", StyleIndex)
25 | ?? new SolidColorBrush(Color.Parse("#D8DDE6"));
26 | ElementBackgroundBrush =
27 | Utilities.GetResourceFromStyle(Application.Current, "ElementBackground", StyleIndex)
28 | ?? new SolidColorBrush(Color.Parse("#C7CAD1"));
29 | VariantElementBackgroundBrush =
30 | Utilities.GetResourceFromStyle(Application.Current, "VariantElementBackground", StyleIndex)
31 | ?? new SolidColorBrush(Color.Parse("#B8BABF"));
32 |
33 | StandardGreyBrush =
34 | Utilities.GetResourceFromStyle(Application.Current, "StandardGrey", StyleIndex)
35 | ?? new SolidColorBrush(Color.Parse("#808080"));
36 | VariantStandardGreyBrush =
37 | Utilities.GetResourceFromStyle(Application.Current, "VariantStandardGrey", StyleIndex)
38 | ?? new SolidColorBrush(Color.Parse("#919191"));
39 | DarkGreyBrush =
40 | Utilities.GetResourceFromStyle(Application.Current, "DarkGrey", StyleIndex)
41 | ?? new SolidColorBrush(Color.Parse("#666666"));
42 | AlmostAccentBrush =
43 | Utilities.GetResourceFromStyle(Application.Current, "AlmostAccent", StyleIndex)
44 | ?? new SolidColorBrush(Color.Parse("#E6E6E6"));
45 | DarkerAlmostAccentBrush =
46 | Utilities.GetResourceFromStyle(Application.Current, "DarkerAlmostAccent", StyleIndex)
47 | ?? new SolidColorBrush(Color.Parse("#D4D4D4"));
48 | DarkenedDialogBackgroundBrush =
49 | Utilities.GetResourceFromStyle(Application.Current, "DarkenedDialogBackground", StyleIndex)
50 | ?? new SolidColorBrush(Color.Parse("#66000000"));
51 | HighlyDarkenedBackgroundBrush =
52 | Utilities.GetResourceFromStyle(Application.Current, "HighlyDarkenedBackground", StyleIndex)
53 | ?? new SolidColorBrush(Color.Parse("#99000000"));
54 | InfoButtonGreyBrush =
55 | Utilities.GetResourceFromStyle(Application.Current, "InfoButtonGrey", StyleIndex)
56 | ?? new SolidColorBrush(Color.Parse("#8B8D92"));
57 |
58 | CalendarDayButtonHoverBrush =
59 | Utilities.GetResourceFromStyle(Application.Current, "CalendarDayButtonHover", StyleIndex)
60 | ?? new SolidColorBrush(Color.Parse("#DCDCDC"));
61 | CalendarDayButtonInactiveBrush =
62 | Utilities.GetResourceFromStyle(Application.Current, "CalendarDayButtonInactive", StyleIndex)
63 | ?? new SolidColorBrush(Color.Parse("#C9C9C9"));
64 | CalendarDayButtonInactiveHoverBrush =
65 | Utilities.GetResourceFromStyle(Application.Current, "CalendarDayButtonInactiveHover", StyleIndex)
66 | ?? new SolidColorBrush(Color.Parse("#D1D1D1"));
67 | }
68 | }
69 | }
--------------------------------------------------------------------------------
/src/Resources/Resources.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Avalonia;
3 | using Avalonia.Media;
4 | using GradeManagement.Enums;
5 | using GradeManagement.UtilityCollection;
6 |
7 | namespace GradeManagement.ResourcesNamespace
8 | {
9 | public static partial class Resources
10 | {
11 | private const int StyleIndex = 2;
12 |
13 | private static readonly LightTheme _lightThemeInstance = new();
14 | private static readonly DarkTheme _darkThemeInstance = new();
15 | private static readonly Dictionary _themesLookup = new()
16 | {
17 | { ThemeMode.Light, _lightThemeInstance }, { ThemeMode.Dark, _darkThemeInstance }
18 | };
19 |
20 | public static Theme CurrentTheme { get; private set; } = _lightThemeInstance;
21 |
22 | // SolidColorBrush
23 |
24 | public static readonly SolidColorBrush FullyTransparentBrush =
25 | Utilities.GetResourceFromStyle(Application.Current, "FullyTransparent", StyleIndex)
26 | ?? new SolidColorBrush(Color.Parse("#0000FFFF"));
27 | public static readonly SolidColorBrush AppGreenBrush =
28 | Utilities.GetResourceFromStyle(Application.Current, "AppGreen", StyleIndex)
29 | ?? new SolidColorBrush(Color.Parse("#009B72"));
30 | public static readonly SolidColorBrush VariantAppGreenBrush =
31 | Utilities.GetResourceFromStyle(Application.Current, "VariantAppGreen", StyleIndex)
32 | ?? new SolidColorBrush(Color.Parse("#00AD7F"));
33 | public static readonly SolidColorBrush LightPurpleBrush =
34 | Utilities.GetResourceFromStyle(Application.Current, "LightPurple", StyleIndex)
35 | ?? new SolidColorBrush(Color.Parse("#A5B1CC"));
36 | public static readonly SolidColorBrush InvalidColorBrush =
37 | Utilities.GetResourceFromStyle(Application.Current, "InvalidColor", StyleIndex)
38 | ?? new SolidColorBrush(Color.Parse("#D64045"));
39 |
40 | public static readonly SolidColorBrush LightGreenContextMenuBrush =
41 | Utilities.GetResourceFromStyle(Application.Current, "LightGreenContextMenu", StyleIndex)
42 | ?? new SolidColorBrush(Color.Parse("#58C4A8"));
43 | public static readonly SolidColorBrush DarkerLightGreenContextMenuBrush =
44 | Utilities.GetResourceFromStyle(Application.Current, "DarkerLightGreenContextMenu", StyleIndex)
45 | ?? new SolidColorBrush(Color.Parse("#0EB085"));
46 | public static readonly SolidColorBrush LightRedContextMenuBrush =
47 | Utilities.GetResourceFromStyle(Application.Current, "LightRedContextMenu", StyleIndex)
48 | ?? new SolidColorBrush(Color.Parse("#D4735B"));
49 | public static readonly SolidColorBrush DarkerLightRedContextMenuBrush =
50 | Utilities.GetResourceFromStyle(Application.Current, "DarkerLightRedContextMenu", StyleIndex)
51 | ?? new SolidColorBrush(Color.Parse("#C96047"));
52 |
53 | // Colors
54 | public static readonly Color FullyTransparent = FullyTransparentBrush.Color;
55 | public static readonly Color AppGreen = AppGreenBrush.Color;
56 | public static readonly Color VariantAppGreen = VariantAppGreenBrush.Color;
57 | public static readonly Color LightPurple = LightPurpleBrush.Color;
58 | public static readonly Color InvalidColor = InvalidColorBrush.Color;
59 |
60 | public static readonly Color LightGreenContextMenu = LightGreenContextMenuBrush.Color;
61 | public static readonly Color DarkerLightGreenContextMenu = DarkerLightGreenContextMenuBrush.Color;
62 | public static readonly Color LightRedContextMenu = LightRedContextMenuBrush.Color;
63 | public static readonly Color DarkerLightRedContextMenu = DarkerLightRedContextMenuBrush.Color;
64 |
65 | internal static void SetTheme(ThemeMode themeMode)
66 | {
67 | if (_themesLookup.ContainsKey(themeMode))
68 | CurrentTheme = _themesLookup[themeMode];
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/src/Resources/Theme.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Media;
2 |
3 | namespace GradeManagement.ResourcesNamespace
4 | {
5 | public abstract class Theme
6 | {
7 | protected const int StyleIndex = 2;
8 |
9 | // SolidColorBrush
10 |
11 | protected internal SolidColorBrush OppositeAccentBrush { get; protected set; } = null!;
12 | protected internal SolidColorBrush SameAccentBrush { get; protected set; } = null!;
13 |
14 | protected internal SolidColorBrush MainBackgroundBrush { get; protected set; } = null!;
15 | protected internal SolidColorBrush ElementBackgroundBrush { get; protected set; } = null!;
16 | protected internal SolidColorBrush VariantElementBackgroundBrush { get; protected set; } = null!;
17 |
18 | protected internal SolidColorBrush StandardGreyBrush { get; protected set; } = null!;
19 | protected internal SolidColorBrush VariantStandardGreyBrush { get; protected set; } = null!;
20 | protected internal SolidColorBrush DarkGreyBrush { get; protected set; } = null!;
21 | protected internal SolidColorBrush AlmostAccentBrush { get; protected set; } = null!;
22 | protected internal SolidColorBrush DarkerAlmostAccentBrush { get; protected set; } = null!;
23 | protected internal SolidColorBrush DarkenedDialogBackgroundBrush { get; protected set; } = null!;
24 | protected internal SolidColorBrush HighlyDarkenedBackgroundBrush { get; protected set; } = null!;
25 | protected internal SolidColorBrush InfoButtonGreyBrush { get; protected set; } = null!;
26 |
27 | protected internal SolidColorBrush CalendarDayButtonHoverBrush { get; protected set; } = null!;
28 | protected internal SolidColorBrush CalendarDayButtonInactiveBrush { get; protected set; } = null!;
29 | protected internal SolidColorBrush CalendarDayButtonInactiveHoverBrush { get; protected set; } = null!;
30 |
31 | // Color
32 |
33 | protected internal Color OppositeAccent => OppositeAccentBrush.Color;
34 | protected internal Color SameAccent => SameAccentBrush.Color;
35 |
36 | protected internal Color MainBackground => MainBackgroundBrush.Color;
37 | protected internal Color ElementBackground => ElementBackgroundBrush.Color;
38 | protected internal Color VariantElementBackground => VariantElementBackgroundBrush.Color;
39 |
40 | protected internal Color StandardGrey => StandardGreyBrush.Color;
41 | protected internal Color VariantStandardGrey => VariantStandardGreyBrush.Color;
42 | protected internal Color DarkGrey => DarkGreyBrush.Color;
43 | protected internal Color AlmostAccent => AlmostAccentBrush.Color;
44 | protected internal Color DarkerAlmostAccent => DarkerAlmostAccentBrush.Color;
45 | protected internal Color DarkenedDialogBackground => DarkenedDialogBackgroundBrush.Color;
46 | protected internal Color HighlyDarkenedBackground => HighlyDarkenedBackgroundBrush.Color;
47 | protected internal Color InfoButtonGrey => InfoButtonGreyBrush.Color;
48 |
49 | protected internal Color CalendarDayButtonHover => CalendarDayButtonHoverBrush.Color;
50 | protected internal Color CalendarDayButtonInactive => CalendarDayButtonInactiveBrush.Color;
51 | protected internal Color CalendarDayButtonInactiveHover => CalendarDayButtonInactiveHoverBrush.Color;
52 |
53 | protected internal abstract void SetResources();
54 | }
55 | }
--------------------------------------------------------------------------------
/src/Styles/AddPagesStyle.axaml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
42 |
43 |
44 |
45 |
59 |
62 |
65 |
66 |
69 |
70 |
71 |
72 |
78 |
81 |
85 |
89 |
90 |
91 |
92 |
103 |
106 |
107 |
108 |
109 |
117 |
120 |
123 |
124 |
127 |
--------------------------------------------------------------------------------
/src/Styles/CalendarStyle.axaml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
10 |
11 |
12 |
40 |
43 |
44 |
50 |
51 |
55 |
56 |
61 |
64 |
65 |
69 |
72 |
73 |
74 |
100 |
101 |
107 |
108 |
113 |
116 |
--------------------------------------------------------------------------------
/src/Styles/ContextMenuStyle.axaml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
7 |
10 |
13 |
--------------------------------------------------------------------------------
/src/Styles/DialogStyle.axaml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
9 |
10 |
17 |
20 |
21 |
30 |
31 |
37 |
38 |
44 |
48 |
51 |
55 |
--------------------------------------------------------------------------------
/src/Styles/ListViewStyle.axaml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
13 |
14 |
17 |
18 |
21 |
22 |
27 |
30 |
33 |
34 |
41 |
42 |
43 |
44 |
50 |
51 |
56 |
57 |
63 |
--------------------------------------------------------------------------------
/src/Styles/MainStyle.axaml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
9 |
10 |
11 |
12 |
15 |
16 |
24 |
28 |
31 |
--------------------------------------------------------------------------------
/src/Styles/MainWindowStyle.axaml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
10 |
11 |
17 |
18 |
22 |
23 |
26 |
27 |
28 |
29 |
34 |
37 |
40 |
41 |
45 |
46 |
52 |
53 |
59 |
60 |
61 |
62 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/src/Styles/TargetGradeStyle.axaml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
19 |
20 |
28 |
29 |
37 |
--------------------------------------------------------------------------------
/src/UtilityCollection/ColorUtilities.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Avalonia;
3 | using Avalonia.Media;
4 |
5 | namespace GradeManagement.UtilityCollection
6 | {
7 | public static partial class Utilities
8 | {
9 | ///
10 | /// Create a with variable start-, endpoints and gradient stops.
11 | ///
12 | /// The start point of the gradient as a
13 | /// The end point of the gradient as a
14 | /// A collection of , each of them containing
15 | /// the and Offset of the it's intended for.
16 | /// LinearGradientBrush
17 | public static LinearGradientBrush CreateLinearGradientBrush(RelativePoint startPoint, RelativePoint endPoint,
18 | IEnumerable> gradientStopInfos)
19 | {
20 | var gradientStops = new GradientStops();
21 | foreach (var (key, value) in gradientStopInfos)
22 | gradientStops.Add(new GradientStop(key, value));
23 |
24 | return CreateLinearGradientBrush(startPoint, endPoint, gradientStops);
25 | }
26 |
27 | ///
28 | /// Create a with variable start-, endpoints and gradient stops.
29 | ///
30 | /// The start point of the gradient as a
31 | /// The end point of the gradient as a
32 | /// The color of each
33 | /// The offset of each
34 | /// LinearGradientBrush
35 | public static LinearGradientBrush CreateLinearGradientBrush(RelativePoint startPoint, RelativePoint endPoint,
36 | Color[] colors, double[] offsets)
37 | {
38 | var gradientStops = new GradientStops();
39 | for (int i = 0; i < colors.Length; i++)
40 | {
41 | if(i < offsets.Length)
42 | gradientStops.Add(new GradientStop(colors[i], offsets[i]));
43 | }
44 |
45 | return CreateLinearGradientBrush(startPoint, endPoint, gradientStops);
46 | }
47 |
48 | ///
49 | /// Create a with variable start-, endpoints and gradient stops.
50 | ///
51 | /// The start point of the gradient as a
52 | /// The end point of the gradient as a
53 | /// The Gradient Stops used for the gradient.
54 | /// LinearGradientBrush
55 | public static LinearGradientBrush CreateLinearGradientBrush(RelativePoint startPoint, RelativePoint endPoint,
56 | GradientStops gradientStops)
57 | {
58 | return new LinearGradientBrush
59 | {
60 | StartPoint = startPoint,
61 | EndPoint = endPoint,
62 | GradientStops = gradientStops
63 | };
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/src/UtilityCollection/DateUtilities.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics.CodeAnalysis;
3 | using GradeManagement.Enums;
4 |
5 | namespace GradeManagement.UtilityCollection
6 | {
7 | public static partial class Utilities
8 | {
9 | public static int TodaysDay => DateTime.Today.Day;
10 | public static string TodaysMonth => DateTime.Today.ToString("MMMM");
11 | public static int TodaysYear => DateTime.Today.Year;
12 |
13 | ///
14 | /// Check whether a specific date (day, month, year) is valid.
15 | ///
16 | /// Returns an enum with a flag of the invalid parameters.
17 | /// Whether the date is valid or not.
18 | [SuppressMessage("ReSharper", "InvalidXmlDocComment")]
19 | public static bool ValidateDate(int day, int month, int year, out DateType validationProtocol)
20 | {
21 | validationProtocol = DateType.None;
22 |
23 | bool monthValid = month is > 0 and <= 12;
24 | bool yearValid = year is >= 1 and <= 9999;
25 |
26 | if (!monthValid)
27 | validationProtocol += 2;
28 | if (!yearValid)
29 | validationProtocol += 4;
30 |
31 | if (!monthValid)
32 | {
33 | if(day is < 1 or > 31)
34 | validationProtocol += 1;
35 | return false;
36 | }
37 |
38 | if (!yearValid)
39 | {
40 | if (month == 2)
41 | {
42 | if(day is < 1 or > 29)
43 | validationProtocol += 1;
44 | }
45 | else
46 | {
47 | if ((day <= 0) || (day > DateTime.DaysInMonth(2021, month)))
48 | validationProtocol += 1;
49 | }
50 | return false;
51 | }
52 |
53 | bool dayValid = (day > 0) && (day <= DateTime.DaysInMonth(year, month));
54 | if (!dayValid)
55 | validationProtocol = DateType.Day;
56 |
57 | return dayValid;
58 | }
59 |
60 | ///
61 | ///
62 | /// Check whether a specific date (DateTime) is valid.
63 | ///
64 | public static bool ValidateDate(DateTime? date, out DateType validationProtocol)
65 | {
66 | validationProtocol = DateType.None;
67 |
68 | if (date is null)
69 | return false;
70 |
71 | var newDate = date.Value;
72 | return ValidateDate(newDate.Day, newDate.Month, newDate.Year, out validationProtocol);
73 | }
74 | }
75 | }
--------------------------------------------------------------------------------
/src/UtilityCollection/UniversalUtilities.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics.CodeAnalysis;
4 | using System.IO;
5 | using System.Linq;
6 | using Avalonia.Controls;
7 | using Avalonia.Markup.Xaml.Styling;
8 | using Avalonia.Styling;
9 | using GradeManagement.Interfaces;
10 |
11 | namespace GradeManagement.UtilityCollection
12 | {
13 | public static partial class Utilities
14 | {
15 | ///
16 | /// The parent path of all settings- and data-files
17 | ///
18 | public static string FilesParentPath
19 | {
20 | get
21 | {
22 | var directory = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
23 | return directory ?? throw new Exception("Directory name of the currently executing assembly is null.");
24 | }
25 | }
26 |
27 | public const float EqualityTolerance = 0.000001f;
28 |
29 | ///
30 | /// Calculate the average of several grades with a weighting factor for each grade.
31 | ///
32 | /// A collection of IGradables,
33 | /// whose grades will be used for the average.
34 | /// Determines whether the returned average should be rounded to 2 decimal digits or not.
35 | /// The calculated average, either rounded or exact, based on the passed bool >
36 | [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
37 | public static float GetAverage(IEnumerable gradables, bool round)
38 | {
39 | var enumerable = gradables.Where(x => x.Counts && x.ElementCount > 0 && !float.IsNaN(x.GradeValue) && x.GradeValue != 0);
40 | if (!enumerable.Any())
41 | return 0;
42 | float result = enumerable.Sum(x => x.GradeValue * x.Weighting) / enumerable.Sum(x => x.Weighting);
43 | return round ? (float)Math.Round(result, 2) : result;
44 | }
45 |
46 | ///
47 | /// Log a message to the console (for debugging purposes).
48 | ///
49 | /// The message to be logged as a string.
50 | public static void Log(string? message)
51 | {
52 | Console.WriteLine(message);
53 | System.Diagnostics.Trace.WriteLine(message);
54 | }
55 |
56 | ///
57 | /// Retrieves a resource from a specified and tries to cast it to the specified type.
58 | ///
59 | /// The element to retrieve the resource from.
60 | /// The name of the resource you want to retrieve (key).
61 | /// The actual type of the resource you want to retrieve.
62 | /// The resource as it's actual type.
63 | public static T? GetResource(IResourceNode element, string resourceName)
64 | where T : class
65 | {
66 | element.TryGetResource(resourceName, out object? resource);
67 | return resource as T;
68 | }
69 |
70 | ///
71 | /// Retrieves a resource from a specified , which is in turns contained
72 | /// in the of an , and tries to cast it to the specified type.
73 | ///
74 | /// The element to retrieve the resource from.
75 | /// The name of the resource you want to retrieve (key).
76 | /// The index of the inside the collection
77 | /// of the .
78 | /// The actual type of the resource you want to retrieve.
79 | /// The type of the element to retrieve the resource from.
80 | /// This type must implement .
81 | /// The resource as it's actual type.
82 | public static TResource? GetResourceFromStyle(TElement? element, string resourceName, int styleIndex)
83 | where TResource : class
84 | where TElement : IStyleHost, IResourceNode
85 | {
86 | var styleInclude = element?.Styles[styleIndex] as StyleInclude;
87 | return (styleInclude?.Loaded is Style style ? GetResource(style, resourceName) : null);
88 | }
89 | }
90 | }
--------------------------------------------------------------------------------
/src/UtilityCollection/WindowUtilities.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using Avalonia.Controls;
4 | using GradeManagement.ViewModels.BaseClasses;
5 |
6 | namespace GradeManagement.UtilityCollection
7 | {
8 | public static partial class Utilities
9 | {
10 | public static TWindow? ShowAddPage(out TViewModel? viewModel, Window? parentWindow)
11 | where TWindow : Window, new()
12 | where TViewModel : AddViewModelBase, new()
13 | {
14 | var window = new TWindow();
15 | viewModel = new TViewModel();
16 |
17 | window.DataContext = viewModel;
18 | viewModel.CurrentAddWindow = window;
19 |
20 | return ShowDialog(window, parentWindow);
21 | }
22 |
23 | public static Window? ShowAddPage(Type? windowType, Type? viewModelType, Window? parentWindow, out AddViewModelBase? addViewModel)
24 | {
25 | addViewModel = null;
26 | if (windowType is null || viewModelType is null || Activator.CreateInstance(windowType) is not Window window)
27 | return null;
28 |
29 | var viewModel = (AddViewModelBase)Activator.CreateInstance(viewModelType)!;
30 | addViewModel = viewModel;
31 | window.DataContext = viewModel;
32 | viewModel.CurrentAddWindow = window;
33 |
34 | return ShowDialog(window, parentWindow);
35 | }
36 |
37 | public static T? ShowDialog(T window, Window? parentWindow)
38 | where T : Window
39 | {
40 | if (parentWindow is null)
41 | return null;
42 | window.ShowDialog(parentWindow);
43 | CatchClosingWindow(window);
44 | return window;
45 | }
46 |
47 | public static void CatchClosingWindow(Window window)
48 | {
49 | EventHandler? closingDel = null;
50 | closingDel = delegate
51 | {
52 | window.Closing -= closingDel;
53 | if (window.DataContext is not ViewModelBase viewModel)
54 | return;
55 |
56 | viewModel.EraseData();
57 | };
58 | window.Closing += closingDel;
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/src/ViewLocator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Avalonia.Controls;
3 | using Avalonia.Controls.Templates;
4 | using GradeManagement.ViewModels.BaseClasses;
5 |
6 | namespace GradeManagement
7 | {
8 | public class ViewLocator : IDataTemplate
9 | {
10 | public bool SupportsRecycling => false;
11 |
12 | public IControl Build(object data) => BuildStatic(data);
13 | public bool Match(object data) => data is ViewModelBase;
14 |
15 | internal static IControl BuildStatic(object data) => BuildStatic(data.GetType());
16 |
17 | internal static IControl BuildStatic(Type type)
18 | {
19 | var newType = ReplaceType(type, out string name);
20 | return BuildControl(newType, name);
21 | }
22 |
23 | private static Type? ReplaceType(Type type, out string name)
24 | {
25 | name = type.FullName!.Replace("ViewModel", "View");
26 | var newType = Type.GetType(name);
27 |
28 | return newType;
29 | }
30 |
31 | private static IControl BuildControl(Type? type, string name)
32 | {
33 | if (type != null)
34 | return (Control)Activator.CreateInstance(type)!;
35 |
36 | return new TextBlock { Text = "Not Found: " + name };
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/src/ViewModels/AddPages/AddSubjectViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using System.Linq;
3 | using Avalonia.Media;
4 | using GradeManagement.Enums;
5 | using GradeManagement.ExtensionCollection;
6 | using GradeManagement.Interfaces;
7 | using GradeManagement.Models.Elements;
8 | using GradeManagement.ViewModels.BaseClasses;
9 | using GradeManagement.ViewModels.Lists;
10 |
11 | namespace GradeManagement.ViewModels.AddPages
12 | {
13 | public class AddSubjectViewModel : AddViewModelBase, IAddViewModel
14 | {
15 | public AddSubjectViewModel()
16 | {
17 | BorderBrushes = new SolidColorBrush[] { new(IncompleteColor), new(IncompleteColor) };
18 | WeightingIndex = 1;
19 | EditPageText(AddPageAction.Create, "Subject");
20 | }
21 |
22 | protected override bool DataComplete => !string.IsNullOrEmpty(ElementName) && !string.IsNullOrWhiteSpace(ElementName)
23 | && !float.IsNaN(ElementWeighting)
24 | && DataChanged();
25 |
26 | private Subject? EditedSubject { get; set; }
27 |
28 | public void EditElement(Subject subject)
29 | {
30 | EditedSubject = subject;
31 | EditPageText(AddPageAction.Edit, "Subject", subject.Name);
32 |
33 | ElementName = subject.Name;
34 | ElementWeightingString = subject.Weighting.ToString(CultureInfo.CurrentCulture);
35 | ElementCounts = subject.Counts;
36 |
37 | var colorRepresentation = ElementColorsCollection.FirstOrDefault(x => x.ElementColor.Equals(subject.ElementColor));
38 | if(colorRepresentation is not null)
39 | ChangeColor(colorRepresentation);
40 | }
41 |
42 | protected internal override void EraseData()
43 | {
44 | base.EraseData();
45 | // TODO: Reset color selection
46 | EditedSubject = null;
47 | }
48 |
49 | private void CreateElement()
50 | {
51 | var currentYear = MainWindowViewModel.CurrentYear;
52 | if (ElementName is null || currentYear is null)
53 | return;
54 |
55 | var colorHex = SelectedColor.ElementColor.ToHexString();
56 | if (EditedSubject is null)
57 | {
58 | var viewModel = SubjectListViewModel.Instance;
59 | var subject = new Subject(ElementName, ElementWeighting, colorHex, ElementCounts);
60 |
61 | currentYear.Subjects.SafeAdd(subject);
62 | viewModel?.Items?.Add(subject);
63 | }
64 | else
65 | EditedSubject.Edit(ElementName, ElementWeighting, colorHex, ElementCounts);
66 |
67 | UpdateVisualOnChange();
68 | EditedSubject = null;
69 | }
70 |
71 | private bool DataChanged()
72 | {
73 | if (EditedSubject is null)
74 | return true;
75 |
76 | return ElementName is not null && (!ElementName.Trim().Equals(EditedSubject.Name.Trim())
77 | || !ElementWeighting.Equals(EditedSubject.Weighting)
78 | || ElementCounts != EditedSubject.Counts
79 | || !SelectedColor.ElementColor.Equals(EditedSubject.ElementColor));
80 | }
81 | }
82 | }
--------------------------------------------------------------------------------
/src/ViewModels/AddPages/AddYearViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using Avalonia.Media;
3 | using GradeManagement.Enums;
4 | using GradeManagement.ExtensionCollection;
5 | using GradeManagement.Interfaces;
6 | using GradeManagement.Models;
7 | using GradeManagement.Models.Elements;
8 | using GradeManagement.ViewModels.BaseClasses;
9 | using GradeManagement.ViewModels.Lists;
10 |
11 | namespace GradeManagement.ViewModels.AddPages
12 | {
13 | public class AddYearViewModel : AddViewModelBase, IAddViewModel
14 | {
15 | public AddYearViewModel()
16 | {
17 | BorderBrushes = new SolidColorBrush[] { new(IncompleteColor) };
18 | EditPageText(AddPageAction.Create, "School Year");
19 | }
20 |
21 | protected override bool DataComplete => !string.IsNullOrEmpty(ElementName) && !string.IsNullOrWhiteSpace(ElementName)
22 | && DataChanged();
23 |
24 | private SchoolYear? EditedYear { get; set; }
25 |
26 | public void EditElement(SchoolYear year)
27 | {
28 | EditedYear = year;
29 | EditPageText(AddPageAction.Edit, "School Year", year.Name);
30 | ElementName = year.Name;
31 |
32 | var colorRepresentation = ElementColorsCollection.FirstOrDefault(x => x.ElementColor.Equals(year.ElementColor));
33 | if(colorRepresentation is not null)
34 | ChangeColor(colorRepresentation);
35 | }
36 |
37 | protected internal override void EraseData()
38 | {
39 | base.EraseData();
40 | EditedYear = null;
41 | }
42 |
43 | private void CreateElement()
44 | {
45 | if(ElementName is null)
46 | return;
47 |
48 | var colorHex = SelectedColor.ElementColor.ToHexString();
49 | if (EditedYear is null)
50 | {
51 | var viewModel = YearListViewModel.Instance;
52 | var year = new SchoolYear(ElementName, colorHex);
53 |
54 | DataManager.SchoolYears.Add(year);
55 | viewModel?.Items?.Add(year);
56 | }
57 | else
58 | EditedYear.Edit(ElementName, colorHex);
59 |
60 | UpdateVisualOnChange();
61 | EditedYear = null;
62 | }
63 |
64 | private bool DataChanged()
65 | {
66 | if (EditedYear is null)
67 | return true;
68 |
69 | return ElementName is not null && (!ElementName.Trim().Equals(EditedYear.Name.Trim())
70 | || !SelectedColor.ElementColor.Equals(EditedYear.ElementColor));
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/src/ViewModels/BaseClasses/AddViewModelBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.ObjectModel;
3 | using Avalonia.Media;
4 | using GradeManagement.Enums;
5 | using GradeManagement.Models;
6 | using GradeManagement.ResourcesNamespace;
7 | using GradeManagement.ViewModels.AddPages;
8 | using ReactiveUI;
9 |
10 | namespace GradeManagement.ViewModels.BaseClasses
11 | {
12 | public abstract class AddViewModelBase : ViewModelBase
13 | {
14 | private string? _elementName;
15 | private string? _elementWeightingStr;
16 | private string? _buttonText;
17 | private string? _title;
18 | private bool _elementCounts = true;
19 | private ColorRepresentation _selectedColor;
20 |
21 | private readonly Color[] _elementColors =
22 | {
23 | DefaultElementColor, Color.Parse("#FFAE03"), Color.Parse("#EB8934"), Color.Parse("#D64045"),
24 | Color.Parse("#FF85FB"), Color.Parse("#A326C9"), Color.Parse("#5F8BB0"), Color.Parse("#6FB3BF"),
25 | Color.Parse("#A5B1CC"), Color.Parse("#009B72"), Color.Parse("#74CC31"), Color.Parse("#A8744F")
26 | };
27 |
28 | protected AddViewModelBase()
29 | {
30 | foreach (var color in _elementColors)
31 | {
32 | var colorRepresentation = new ColorRepresentation(color);
33 | ElementColorsCollection.Add(colorRepresentation);
34 | }
35 |
36 | _selectedColor = SelectedColor = ElementColorsCollection[0];
37 | SelectedColor.Selected = true;
38 | }
39 |
40 | // Colors for border (incomplete/complete selection)
41 | protected static Color IncompleteColor => Resources.InvalidColor;
42 |
43 | protected static Color NormalColor => Resources.AppGreen;
44 |
45 | protected static Color InactiveColor => Resources.CurrentTheme.StandardGrey;
46 |
47 | protected virtual bool DataComplete { get; }
48 | protected SolidColorBrush[]? BorderBrushes { get; init; }
49 |
50 | // Indexes of the elements in the UI
51 | protected int WeightingIndex { get; init; }
52 | protected int NameIndex { get; init; }
53 |
54 | protected string? Title
55 | {
56 | get => _title;
57 | set => this.RaiseAndSetIfChanged(ref _title, value);
58 | }
59 |
60 | protected string? ButtonText
61 | {
62 | get => _buttonText;
63 | set => this.RaiseAndSetIfChanged(ref _buttonText, value);
64 | }
65 |
66 | protected string? ElementName
67 | {
68 | get => _elementName;
69 | set
70 | {
71 | this.RaiseAndSetIfChanged(ref _elementName, value);
72 | BorderBrushes![NameIndex].Color = NormalColor;
73 | if (string.IsNullOrEmpty(value) || string.IsNullOrWhiteSpace(value))
74 | BorderBrushes[NameIndex].Color = IncompleteColor;
75 |
76 | this.RaisePropertyChanged(nameof(BorderBrushes));
77 | this.RaisePropertyChanged(nameof(DataComplete));
78 | }
79 | }
80 |
81 | protected float ElementWeighting { get; private set; } = float.NaN;
82 | protected string? ElementWeightingString
83 | {
84 | get => _elementWeightingStr;
85 | set
86 | {
87 | this.RaiseAndSetIfChanged(ref _elementWeightingStr, value);
88 | ElementWeighting = float.NaN;
89 | BorderBrushes![WeightingIndex].Color = NormalColor;
90 |
91 | if (float.TryParse(value, out float weighting))
92 | ElementWeighting = weighting;
93 | else
94 | BorderBrushes![WeightingIndex].Color = IncompleteColor;
95 |
96 | this.RaisePropertyChanged(nameof(BorderBrushes));
97 | this.RaisePropertyChanged(nameof(DataComplete));
98 | }
99 | }
100 |
101 | protected bool ElementCounts
102 | {
103 | get => _elementCounts;
104 | set
105 | {
106 | this.RaiseAndSetIfChanged(ref _elementCounts, value);
107 | this.RaisePropertyChanged(nameof(DataComplete));
108 | }
109 | }
110 |
111 | protected ObservableCollection ElementColorsCollection { get; } = new();
112 |
113 | protected ColorRepresentation SelectedColor
114 | {
115 | get => _selectedColor;
116 | private set
117 | {
118 | _selectedColor = value;
119 | this.RaisePropertyChanged(nameof(DataComplete));
120 | }
121 | }
122 |
123 | private static Color DefaultElementColor => Resources.CurrentTheme.ElementBackground;
124 |
125 | protected internal override void EraseData()
126 | {
127 | ElementName = string.Empty;
128 | ElementWeightingString = string.Empty;
129 | ElementCounts = true;
130 | SelectedColor = ElementColorsCollection[0];
131 | }
132 |
133 | protected void ChangeColor(ColorRepresentation colorRepresentation)
134 | {
135 | SelectedColor.Selected = false;
136 | colorRepresentation.Selected = true;
137 | SelectedColor = colorRepresentation;
138 | }
139 |
140 | internal void EditPageText(AddPageAction action, Type pageType, string suffix = "")
141 | {
142 | string type = pageType switch
143 | {
144 | { } yearType when yearType == typeof(AddYearViewModel) => "School Year",
145 | { } subjectType when subjectType == typeof(AddSubjectViewModel) => "Subject",
146 | { } gradeType when gradeType == typeof(AddGradeViewModel) => "Grade",
147 | _ => throw new ArgumentException($"Value of parameter \"{nameof(pageType)}\" should be an `AddViewModelBase` inheritor.",
148 | nameof(pageType))
149 | };
150 | EditPageText(action, type, suffix);
151 | }
152 |
153 | internal void EditPageText(AddPageAction action, string type, string suffix = "")
154 | {
155 | string prefix = action.ToString();
156 | Title = $"{prefix} {(string.IsNullOrEmpty(suffix) ? type : ($"\"{suffix}\""))}:";
157 | ButtonText = $"{prefix} {type}";
158 | }
159 | }
160 | }
--------------------------------------------------------------------------------
/src/ViewModels/BaseClasses/DialogBase.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using Avalonia.Media;
4 | using GradeManagement.ExtensionCollection;
5 | using GradeManagement.Models.Settings;
6 |
7 | namespace GradeManagement.ViewModels.BaseClasses
8 | {
9 | public abstract class DialogBase : ViewModelBase
10 | {
11 | private static readonly Color _darkBorder = Color.Parse("#696969");
12 | private static readonly Color _lightBorder = Color.Parse("#9C9C9C");
13 |
14 | protected DialogBase(string title,
15 | IEnumerable buttonColors,
16 | IEnumerable buttonTextColors,
17 | IEnumerable buttonTexts)
18 | {
19 | this.Title = title;
20 | this.ButtonColors = buttonColors.ToArray();
21 | this.ButtonTextColors = buttonTextColors.ToArray();
22 | this.ButtonTexts = buttonTexts.ToArray();
23 | }
24 |
25 | protected string Title { get; init; }
26 |
27 | protected SolidColorBrush[] ButtonColors { get; init; }
28 | protected SolidColorBrush[] ButtonColorsHover
29 | => ButtonColors.Select(x => new SolidColorBrush(x.Color.DarkenColor(0.1f))).ToArray();
30 | protected SolidColorBrush[] ButtonTextColors { get; init; }
31 |
32 | protected string[] ButtonTexts { get; init; }
33 |
34 | protected bool IgnoreDialog { get; set; }
35 |
36 | protected static void CloseDialog()
37 | {
38 | if (MainWindowViewModel.Instance is not { } mainInstance)
39 | return;
40 | mainInstance.Content.CurrentDialog = null;
41 | }
42 |
43 | protected void CheckIgnoreDialog()
44 | {
45 | if (!IgnoreDialog || SettingsManager.Settings is not { } settings)
46 | return;
47 | settings.ShowRemoveConfirmation = false;
48 | SettingsManager.SaveSettings();
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/src/ViewModels/BaseClasses/ListViewModelBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Avalonia.Media;
3 | using GradeManagement.Enums;
4 | using GradeManagement.ExtensionCollection;
5 | using GradeManagement.Interfaces;
6 | using GradeManagement.Models.Elements;
7 | using GradeManagement.ViewModels.Dialogs;
8 | using ReactiveUI;
9 |
10 | namespace GradeManagement.ViewModels.BaseClasses
11 | {
12 | public abstract class ListViewModelBase : ViewModelBase
13 | {
14 | private bool _isViewGrid;
15 | private DialogBase? _currentDialog;
16 |
17 | protected internal Type? AddPageType { get; protected init; }
18 | protected internal Type? AddViewModelType { get; protected init; }
19 | protected internal Type? ElementType { get; protected init; }
20 |
21 | protected bool IsViewGrid
22 | {
23 | get => _isViewGrid;
24 | set => this.RaiseAndSetIfChanged(ref _isViewGrid, value);
25 | }
26 |
27 | internal DialogBase? CurrentDialog
28 | {
29 | get => _currentDialog;
30 | set => this.RaiseAndSetIfChanged(ref _currentDialog, value);
31 | }
32 |
33 | protected void DuplicateElement(IElement element) where T : class, IElement
34 | {
35 | var instance = MainWindowViewModel.Instance;
36 | if (instance is null)
37 | return;
38 |
39 | var duplicate = element.Duplicate();
40 | UpdateVisualOnChange();
41 |
42 | if (duplicate is null)
43 | return;
44 |
45 | if (typeof(T) == typeof(GradeGroup))
46 | {
47 | (this as IListViewModel)?.Items?.Add((duplicate as Grade)!);
48 | return;
49 | }
50 | var viewModel = this as IListViewModel;
51 | viewModel?.Items?.Add(duplicate);
52 | }
53 |
54 | protected void RemoveElement(IElement element, ElementType elementType, Action confirmAction)
55 | {
56 | if (SettingsRef is not null && !SettingsRef.ShowRemoveConfirmation)
57 | {
58 | confirmAction.Invoke();
59 | return;
60 | }
61 |
62 | string elementTypeName = elementType.ToString().SplitCamelCase();
63 | string dialogTitle = $"Do you really want to remove the {elementTypeName} \"{element.Name}\"?";
64 | CurrentDialog = new ConfirmationDialogViewModel(dialogTitle,
65 | new [] { Color.Parse("#D64045"), Color.Parse("#808080") },
66 | new[] { Colors.White, Colors.White },
67 | new[] { "Remove", "Cancel" },
68 | confirmAction);
69 | }
70 |
71 | protected internal virtual void ChangeTopbar()
72 | {
73 | if (TopbarTexts is null)
74 | throw new ArgumentNullException();
75 | }
76 |
77 | protected internal void ChangeButtonView(bool isGrid)
78 | {
79 | IsViewGrid = isGrid;
80 | AdjustTextColors(isGrid);
81 | }
82 |
83 | private void AdjustTextColors(bool isGrid)
84 | {
85 | if (this is IListViewModel {Items: { }} subjectViewModel)
86 | {
87 | foreach (var item in subjectViewModel.Items)
88 | item.AdjustTextColors(isGrid);
89 | return;
90 | }
91 |
92 | if (this is IListViewModel {Items: { }} yearViewModel)
93 | {
94 | foreach (var item in yearViewModel.Items)
95 | item.AdjustTextColors(isGrid);
96 | }
97 | }
98 | }
99 | }
--------------------------------------------------------------------------------
/src/ViewModels/BaseClasses/ViewModelBase.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using GradeManagement.Models;
3 | using GradeManagement.Models.Settings;
4 | using GradeManagement.Views;
5 | using ReactiveUI;
6 |
7 | namespace GradeManagement.ViewModels.BaseClasses
8 | {
9 | public abstract class ViewModelBase : ReactiveObject
10 | {
11 | protected MainWindow? MainWindowInstance { get; private set; }
12 | protected Preferences? SettingsRef { get; }
13 |
14 | protected Controls? TopbarTexts { get; private set; }
15 | protected internal Window? CurrentAddWindow { get; internal set; }
16 |
17 | protected ViewModelBase()
18 | => this.SettingsRef = SettingsManager.Settings;
19 |
20 | protected void InitializeTopbarElements()
21 | {
22 | if (MainWindow.Instance is null)
23 | return;
24 |
25 | this.MainWindowInstance = MainWindow.Instance;
26 | this.TopbarTexts = this.MainWindowInstance.Get("Topbar-Grid").Children;
27 | }
28 |
29 | protected void UpdateVisualOnChange()
30 | {
31 | CloseAddWindow();
32 | DataManager.SaveData();
33 | }
34 |
35 | protected internal virtual void EraseData() { }
36 |
37 | private void CloseAddWindow()
38 | {
39 | CurrentAddWindow?.Close();
40 | CurrentAddWindow = null;
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/src/ViewModels/Dialogs/ConfirmationDialogViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Avalonia.Media;
5 | using GradeManagement.ViewModels.BaseClasses;
6 |
7 | namespace GradeManagement.ViewModels.Dialogs
8 | {
9 | public class ConfirmationDialogViewModel : DialogBase
10 | {
11 | private enum ActionType
12 | {
13 | Confirm,
14 | Cancel
15 | }
16 |
17 | private readonly Action? _confirmAction;
18 | private readonly Action? _cancelAction;
19 |
20 | public ConfirmationDialogViewModel(string title,
21 | IEnumerable buttonColors,
22 | IEnumerable buttonTextColors,
23 | IEnumerable buttonTexts,
24 | Action? confirmAction,
25 | Action? cancelAction = null) : base(title, buttonColors, buttonTextColors, buttonTexts)
26 | {
27 | _confirmAction = confirmAction;
28 | _cancelAction = cancelAction;
29 | }
30 |
31 | public ConfirmationDialogViewModel(string title,
32 | IEnumerable buttonColors,
33 | IEnumerable buttonTextColors,
34 | IEnumerable buttonTexts,
35 | Action? confirmAction,
36 | Action? cancelAction = null)
37 | : this(title,
38 | buttonColors.Select(x => new SolidColorBrush(x)),
39 | buttonTextColors.Select(x => new SolidColorBrush(x)),
40 | buttonTexts,
41 | confirmAction,
42 | cancelAction)
43 | { }
44 |
45 | private void Command(ActionType actionType)
46 | {
47 | CloseDialog();
48 | switch (actionType)
49 | {
50 | case ActionType.Confirm:
51 | CheckIgnoreDialog();
52 | _confirmAction?.Invoke();
53 | break;
54 | case ActionType.Cancel:
55 | _cancelAction?.Invoke();
56 | break;
57 | }
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/src/ViewModels/Lists/GradeListViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 | using Avalonia.Controls;
6 | using GradeManagement.Enums;
7 | using GradeManagement.Interfaces;
8 | using GradeManagement.Models.Elements;
9 | using GradeManagement.Models.Settings;
10 | using GradeManagement.UtilityCollection;
11 | using GradeManagement.ViewModels.AddPages;
12 | using GradeManagement.ViewModels.BaseClasses;
13 | using GradeManagement.ViewModels.TargetGrade;
14 | using GradeManagement.Views.AddPages;
15 | using GradeManagement.Views.TargetGrade;
16 | using ReactiveUI;
17 |
18 | namespace GradeManagement.ViewModels.Lists
19 | {
20 | public class GradeListViewModel : ListViewModelBase, IListViewModel
21 | {
22 | private ObservableCollection? _items;
23 | private TargetGradeWindowModel? _targetGradeWindowModel;
24 |
25 | public GradeListViewModel(IEnumerable items, IGradesContainer gradesContainer)
26 | {
27 | Instance = this;
28 | AddPageType = typeof(AddGradeWindow);
29 | AddViewModelType = typeof(AddGradeViewModel);
30 | ElementType = typeof(Grade);
31 | GradesContainer = gradesContainer;
32 |
33 | bool isGrid = SettingsManager.Settings?.GradeButtonStyle == SelectedButtonStyle.Grid;
34 | ChangeButtonView(isGrid);
35 |
36 | var mainInstance = MainWindowViewModel.Instance;
37 | Items = new ObservableCollection(items);
38 | Items.CollectionChanged += (sender, args) =>
39 | {
40 | this.RaisePropertyChanged(nameof(EmptyCollection));
41 | mainInstance?.UpdateAverage();
42 | };
43 | InitializeTopbarElements();
44 | }
45 |
46 | public ObservableCollection? Items
47 | {
48 | get => _items;
49 | set
50 | {
51 | this.RaiseAndSetIfChanged(ref _items, value);
52 | this.RaisePropertyChanged(nameof(EmptyCollection));
53 | }
54 | }
55 |
56 | public bool EmptyCollection => Items?.Count == 0;
57 |
58 | internal IGradesContainer? GradesContainer { get; set; }
59 |
60 | internal static GradeListViewModel? Instance { get; private set; }
61 |
62 | public void Duplicate(IElement element)
63 | {
64 | if(element is GradeGroup)
65 | DuplicateElement(element);
66 | else
67 | DuplicateElement(element);
68 | }
69 |
70 | protected internal override void ChangeTopbar()
71 | {
72 | base.ChangeTopbar();
73 |
74 | int[] additionalGradesIndices = {3, 4};
75 | const int gradeBorder = 3;
76 | bool parentIsGrade = GradesContainer?.ParentContainer is GradeGroup;
77 | bool containerIsGrade = GradesContainer is GradeGroup;
78 | for (int i = 0; i < TopbarTexts!.Count; i++)
79 | {
80 | IControl control = TopbarTexts[i];
81 | control.IsVisible = true;
82 | if (i >= gradeBorder)
83 | control.IsVisible = containerIsGrade;
84 | if (additionalGradesIndices.Contains(i))
85 | control.IsVisible = parentIsGrade;
86 | }
87 | }
88 |
89 | private void RemoveElement(Grade grade)
90 | {
91 | Action action = () =>
92 | {
93 | GradesContainer?.Grades.Remove(grade);
94 | Items?.Remove(grade);
95 | UpdateVisualOnChange();
96 | };
97 | base.RemoveElement(grade, Enums.ElementType.Grade, action);
98 | }
99 |
100 | private void OpenTargetGradeCalc()
101 | {
102 | if (Items is null)
103 | return;
104 | var window = new TargetGradeWindow();
105 | _targetGradeWindowModel ??= new TargetGradeWindowModel(Items);
106 | _targetGradeWindowModel.ClearData();
107 | _targetGradeWindowModel.ConfigureViewModels(Items);
108 | window.DataContext = _targetGradeWindowModel;
109 |
110 | Utilities.ShowDialog(window, MainWindowInstance);
111 | }
112 | }
113 | }
--------------------------------------------------------------------------------
/src/ViewModels/Lists/SubjectListViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using GradeManagement.Enums;
5 | using GradeManagement.Interfaces;
6 | using GradeManagement.Models.Elements;
7 | using GradeManagement.Models.Settings;
8 | using GradeManagement.ViewModels.AddPages;
9 | using GradeManagement.ViewModels.BaseClasses;
10 | using GradeManagement.Views.AddPages;
11 | using ReactiveUI;
12 |
13 | namespace GradeManagement.ViewModels.Lists
14 | {
15 | public class SubjectListViewModel : ListViewModelBase, IListViewModel
16 | {
17 | private readonly bool[] _elementsVisibilities = { true, false, false, false, false, false, false };
18 | private ObservableCollection? _items;
19 |
20 | public SubjectListViewModel(IEnumerable items)
21 | {
22 | Instance = this;
23 | AddPageType = typeof(AddSubjectWindow);
24 | AddViewModelType = typeof(AddSubjectViewModel);
25 | ElementType = typeof(Subject);
26 |
27 | bool isGrid = SettingsManager.Settings?.SubjectButtonStyle == SelectedButtonStyle.Grid;
28 | ChangeButtonView(isGrid);
29 |
30 | var mainInstance = MainWindowViewModel.Instance;
31 | Items = new ObservableCollection(items);
32 | Items.CollectionChanged += (sender, args) =>
33 | {
34 | this.RaisePropertyChanged(nameof(EmptyCollection));
35 | mainInstance?.UpdateAverage();
36 | };
37 | InitializeTopbarElements();
38 | }
39 |
40 | public ObservableCollection? Items
41 | {
42 | get => _items;
43 | set
44 | {
45 | this.RaiseAndSetIfChanged(ref _items, value);
46 | this.RaisePropertyChanged(nameof(EmptyCollection));
47 | }
48 | }
49 |
50 | public bool EmptyCollection => Items?.Count == 0;
51 |
52 | internal static SubjectListViewModel? Instance { get; private set; }
53 |
54 | public void Duplicate(IElement element)
55 | => DuplicateElement(element);
56 |
57 | protected internal override void ChangeTopbar()
58 | {
59 | base.ChangeTopbar();
60 | for (int i = 0; i < TopbarTexts!.Count; i++)
61 | TopbarTexts[i].IsVisible = _elementsVisibilities[i];
62 | }
63 |
64 | private void RemoveElement(Subject subject)
65 | {
66 | var currentYear = MainWindowViewModel.CurrentYear;
67 | Action action = () =>
68 | {
69 | currentYear?.Subjects.Remove(subject);
70 | Items?.Remove(subject);
71 | UpdateVisualOnChange();
72 | };
73 | base.RemoveElement(subject, Enums.ElementType.Subject, action);
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/src/ViewModels/Lists/YearListViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using GradeManagement.Enums;
5 | using GradeManagement.Interfaces;
6 | using GradeManagement.Models;
7 | using GradeManagement.Models.Elements;
8 | using GradeManagement.Models.Settings;
9 | using GradeManagement.ViewModels.AddPages;
10 | using GradeManagement.ViewModels.BaseClasses;
11 | using GradeManagement.Views.AddPages;
12 | using ReactiveUI;
13 |
14 | namespace GradeManagement.ViewModels.Lists
15 | {
16 | public class YearListViewModel : ListViewModelBase, IListViewModel
17 | {
18 | private ObservableCollection? _items;
19 |
20 | public YearListViewModel(IEnumerable years)
21 | {
22 | Instance = this;
23 | AddPageType = typeof(AddYearWindow);
24 | AddViewModelType = typeof(AddYearViewModel);
25 | ElementType = typeof(SchoolYear);
26 |
27 | bool isGrid = SettingsManager.Settings?.YearButtonStyle == SelectedButtonStyle.Grid;
28 | ChangeButtonView(isGrid);
29 |
30 | var mainInstance = MainWindowViewModel.Instance;
31 | Items = new ObservableCollection(years);
32 | Items.CollectionChanged += (sender, args) =>
33 | {
34 | this.RaisePropertyChanged(nameof(EmptyCollection));
35 | mainInstance?.UpdateAverage();
36 | };
37 | InitializeTopbarElements();
38 | }
39 |
40 | public ObservableCollection? Items
41 | {
42 | get => _items;
43 | set
44 | {
45 | this.RaiseAndSetIfChanged(ref _items, value);
46 | this.RaisePropertyChanged(nameof(EmptyCollection));
47 | }
48 | }
49 |
50 | public bool EmptyCollection => Items?.Count == 0;
51 |
52 | internal static YearListViewModel? Instance { get; private set; }
53 |
54 | public void Duplicate(IElement element)
55 | => DuplicateElement(element);
56 |
57 | protected internal override void ChangeTopbar()
58 | {
59 | base.ChangeTopbar();
60 | foreach (var control in TopbarTexts!)
61 | control.IsVisible = false;
62 | }
63 |
64 | private void RemoveElement(SchoolYear year)
65 | {
66 | Action action = () =>
67 | {
68 | DataManager.SchoolYears.Remove(year);
69 | Items?.Remove(year);
70 | UpdateVisualOnChange();
71 | };
72 | base.RemoveElement(year, Enums.ElementType.SchoolYear, action);
73 | }
74 | }
75 | }
--------------------------------------------------------------------------------
/src/ViewModels/TargetGrade/AverageGradeViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using GradeManagement.Interfaces;
5 | using GradeManagement.Models.Elements;
6 | using GradeManagement.UtilityCollection;
7 | using GradeManagement.ViewModels.BaseClasses;
8 | using ReactiveUI;
9 |
10 | namespace GradeManagement.ViewModels.TargetGrade
11 | {
12 | public class AverageGradeViewModel : ViewModelBase, ITargetGrade
13 | {
14 | private string _gradeString = string.Empty;
15 | private float _grade = float.NaN;
16 | private string _weightingString = string.Empty;
17 | private float _weighting = float.NaN;
18 |
19 | public AverageGradeViewModel(IEnumerable grades)
20 | => this.Grades = grades;
21 |
22 | public IEnumerable? Grades { get; set; }
23 |
24 | private string GradeString
25 | {
26 | get => _gradeString;
27 | set
28 | {
29 | _grade = float.TryParse(value, out float grade) ? grade : float.NaN;
30 | this.RaiseAndSetIfChanged(ref _gradeString, value);
31 | this.RaisePropertyChanged(nameof(CalculatedAverage));
32 | }
33 | }
34 |
35 | private string WeightingString
36 | {
37 | get => _weightingString;
38 | set
39 | {
40 | _weighting = float.TryParse(value, out float weighting) ? weighting : float.NaN;
41 | this.RaiseAndSetIfChanged(ref _weightingString, value);
42 | this.RaisePropertyChanged(nameof(CalculatedAverage));
43 | }
44 | }
45 |
46 | private string CalculatedAverage
47 | {
48 | get
49 | {
50 | if (float.IsNaN(_grade) || float.IsNaN(_weighting) || this.Grades is null)
51 | return "-";
52 |
53 | var newGrades = new List(Grades)
54 | {
55 | new Grade("[TempGrade]", _grade, null, null, _weighting, DateTime.Today, true)
56 | };
57 | float result = Utilities.GetAverage(newGrades, true);
58 | return float.IsNaN(result) ? "-" : result.ToString(CultureInfo.InvariantCulture);
59 | }
60 | }
61 |
62 | protected internal override void EraseData()
63 | {
64 | Grades = null;
65 | GradeString = string.Empty;
66 | WeightingString = string.Empty;
67 | }
68 | }
69 | }
--------------------------------------------------------------------------------
/src/ViewModels/TargetGrade/TargetGradeViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using GradeManagement.Interfaces;
5 | using GradeManagement.Models.Elements;
6 | using GradeManagement.ViewModels.BaseClasses;
7 | using ReactiveUI;
8 |
9 | namespace GradeManagement.ViewModels.TargetGrade
10 | {
11 | public class TargetGradeViewModel : ViewModelBase, ITargetGrade
12 | {
13 | private string _targetAverageString = string.Empty;
14 | private float _targetAverage = float.NaN;
15 | private string _weightingString = string.Empty;
16 | private float _weighting = float.NaN;
17 |
18 | public IEnumerable? Grades { get; set; }
19 |
20 | public TargetGradeViewModel(IEnumerable grades)
21 | => this.Grades = grades;
22 |
23 | private string TargetAverageString
24 | {
25 | get => _targetAverageString;
26 | set
27 | {
28 | _targetAverage = float.TryParse(value, out float average) ? average : float.NaN;
29 | this.RaiseAndSetIfChanged(ref _targetAverageString, value);
30 | this.RaisePropertyChanged(nameof(NeededGrade));
31 | }
32 | }
33 |
34 | private string WeightingString
35 | {
36 | get => _weightingString;
37 | set
38 | {
39 | _weighting = float.TryParse(value, out float weighting) ? weighting : float.NaN;
40 | this.RaiseAndSetIfChanged(ref _weightingString, value);
41 | this.RaisePropertyChanged(nameof(NeededGrade));
42 | }
43 | }
44 |
45 | private string NeededGrade
46 | {
47 | get
48 | {
49 | if (float.IsNaN(_targetAverage) || float.IsNaN(_weighting) || this.Grades is null)
50 | return "-";
51 |
52 | var previousGrades = Grades.Sum(x => x.GradeValue * x.Weighting);
53 | var weightingSum = Grades.Sum(x => x.Weighting);
54 | var result = (_targetAverage * (weightingSum + _weighting) - previousGrades) / _weighting;
55 | return float.IsNaN(result) ? "-" : result.ToString("F2");
56 | }
57 | }
58 |
59 | protected internal override void EraseData()
60 | {
61 | Grades = null;
62 | TargetAverageString = string.Empty;
63 | WeightingString = string.Empty;
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/src/ViewModels/TargetGrade/TargetGradeWindowModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 | using Avalonia.Media;
6 | using GradeManagement.ExtensionCollection;
7 | using GradeManagement.Interfaces;
8 | using GradeManagement.Models.Elements;
9 | using GradeManagement.ViewModels.BaseClasses;
10 | using ReactiveUI;
11 |
12 | namespace GradeManagement.ViewModels.TargetGrade
13 | {
14 | public class TargetGradeWindowModel : ViewModelBase
15 | {
16 | private const string TargetGradeTitle = "Calculate Target Grade";
17 | private const string AverageTitle = "Calculate Average";
18 |
19 | private static readonly Color _greenColor = new(255,0,155,114);
20 | private static readonly Color _whiteColor = new(255, 255, 255, 255);
21 | private static readonly Color _blackColor = new(255, 0, 0, 0);
22 | private static readonly Color _darkerGreenColor = _greenColor.DarkenColor(0.075f);
23 | private static readonly Color _darkerDefaultColor = Color.Parse("#D8DDE6").DarkenColor(0.075f);
24 |
25 | private readonly List _viewModels = new();
26 | private ViewModelBase? _content;
27 | private string _windowTitle = TargetGradeTitle;
28 | private int _currentButton;
29 |
30 | public TargetGradeWindowModel(IEnumerable grades)
31 | {
32 | var enumerable = grades as Grade[] ?? grades.ToArray();
33 | var viewModel = new TargetGradeViewModel(enumerable);
34 | _content = Content = viewModel;
35 | _viewModels.SafeAdd(_content);
36 |
37 | ClearData();
38 |
39 | Grades = enumerable;
40 | viewModel.Grades = this.Grades;
41 | }
42 |
43 | internal IEnumerable? Grades { get; set; }
44 |
45 | private string WindowTitle
46 | {
47 | get => _windowTitle;
48 | set => this.RaiseAndSetIfChanged(ref _windowTitle, value);
49 | }
50 |
51 | private ObservableCollection ButtonColors { get; }
52 | = new() { new SolidColorBrush(_greenColor), new SolidColorBrush(_greenColor,0) };
53 |
54 | private ObservableCollection ButtonColorsHover { get; }
55 | = new()
56 | {
57 | new SolidColorBrush(_greenColor.DarkenColor(0.075f)),
58 | new SolidColorBrush(_darkerDefaultColor)
59 | };
60 |
61 | private ObservableCollection ButtonTextColors { get; }
62 | = new() { new SolidColorBrush(_whiteColor), new SolidColorBrush(_blackColor) };
63 |
64 | // ReSharper disable once CollectionNeverQueried.Local
65 | private ObservableCollection FontWeights { get; }
66 | = new() { FontWeight.Bold, FontWeight.Normal };
67 |
68 | private ViewModelBase? Content
69 | {
70 | get => _content;
71 | set => this.RaiseAndSetIfChanged(ref _content, value);
72 | }
73 |
74 | internal void ConfigureViewModels(IEnumerable grades)
75 | {
76 | this.Grades = grades;
77 | if(GetViewModel(typeof(TargetGradeViewModel)) is TargetGradeViewModel targetGrade)
78 | targetGrade.Grades = grades;
79 | if(GetViewModel(typeof(AverageGradeViewModel)) is AverageGradeViewModel averageGrade)
80 | averageGrade.Grades = grades;
81 | }
82 |
83 | internal void ClearData()
84 | {
85 | if(GetViewModel(typeof(TargetGradeViewModel)) is TargetGradeViewModel targetGrade)
86 | targetGrade.EraseData();
87 | if(GetViewModel(typeof(AverageGradeViewModel)) is AverageGradeViewModel averageGrade)
88 | averageGrade.EraseData();
89 | }
90 |
91 | private void SwitchCalculator(int selectedButton)
92 | {
93 | if (_currentButton == selectedButton)
94 | return;
95 | _currentButton = selectedButton;
96 |
97 | WindowTitle = _windowTitle.Equals(AverageTitle) ? TargetGradeTitle : AverageTitle;
98 |
99 | ChangeButtons(selectedButton);
100 | ToggleControl(selectedButton);
101 | }
102 |
103 | private void ChangeButtons(int selectedButton)
104 | {
105 | var otherButton = Math.Abs(selectedButton - 1);
106 |
107 | ButtonColors[selectedButton].Opacity = 1;
108 | ButtonColors[otherButton].Opacity = 0;
109 |
110 | ButtonColorsHover[selectedButton].Color = _darkerGreenColor;
111 | ButtonColorsHover[otherButton].Color = _darkerDefaultColor;
112 |
113 | ButtonTextColors[selectedButton].Color = _whiteColor;
114 | ButtonTextColors[otherButton].Color = _blackColor;
115 |
116 | FontWeights[selectedButton] = FontWeight.Bold;
117 | FontWeights[otherButton] = FontWeight.Normal;
118 | }
119 |
120 | private void ToggleControl(int selected)
121 | {
122 | var type = selected == 0 ? typeof(TargetGradeViewModel) : typeof(AverageGradeViewModel);
123 | ViewModelBase viewModel;
124 | if (_viewModels.Any(x => x.GetType() == type))
125 | viewModel = _viewModels.Find(x => x.GetType() == type)!;
126 | else
127 | {
128 | viewModel = (ViewModelBase)Activator.CreateInstance(type, this.Grades)!;
129 | _viewModels.Add(viewModel);
130 | }
131 |
132 | Content = viewModel;
133 | _content?.EraseData();
134 |
135 | if (viewModel is ITargetGrade viewModelInterface)
136 | viewModelInterface.Grades = this.Grades;
137 | }
138 |
139 | private ViewModelBase? GetViewModel(Type type)
140 | => _viewModels.FirstOrDefault(x => x.GetType() == type);
141 | }
142 | }
--------------------------------------------------------------------------------
/src/Views/AddPages/AddGradeWindow.axaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
29 |
30 |
33 |
34 |
35 |
36 |
39 |
42 |
45 |
46 |
47 |
55 |
56 |
59 |
62 |
64 |
66 |
69 |
71 |
74 |
75 |
77 |
79 |
82 |
83 |
86 |
90 |
91 |
92 |
95 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/src/Views/AddPages/AddGradeWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Markup.Xaml;
4 | using GradeManagement.ViewModels.AddPages;
5 |
6 | namespace GradeManagement.Views.AddPages
7 | {
8 | public class AddGradeWindow : Window
9 | {
10 | public AddGradeWindow()
11 | {
12 | InitializeComponent();
13 | #if DEBUG
14 | this.AttachDevTools();
15 | #endif
16 | }
17 |
18 | private void InitializeComponent()
19 | {
20 | AvaloniaXamlLoader.Load(this);
21 | }
22 |
23 | private void DisplayDateChanged(object? sender, SelectionChangedEventArgs args)
24 | {
25 | if(DataContext is AddGradeViewModel viewModel)
26 | viewModel.DateChanged(sender, args);
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/src/Views/AddPages/AddSubjectWindow.axaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
25 |
26 |
29 |
32 |
33 |
35 |
36 |
37 |
38 |
40 |
41 |
42 |
43 |
44 |
55 |
56 |
57 |
58 |
59 |
61 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/src/Views/AddPages/AddSubjectWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Markup.Xaml;
4 |
5 | namespace GradeManagement.Views.AddPages
6 | {
7 | public class AddSubjectWindow : Window
8 | {
9 | public AddSubjectWindow()
10 | {
11 | InitializeComponent();
12 | #if DEBUG
13 | this.AttachDevTools();
14 | #endif
15 | }
16 |
17 | private void InitializeComponent()
18 | {
19 | AvaloniaXamlLoader.Load(this);
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/src/Views/AddPages/AddYearWindow.axaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
25 |
26 |
29 |
30 |
32 |
33 |
34 |
35 |
37 |
38 |
39 |
40 |
41 |
52 |
53 |
54 |
55 |
56 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/Views/AddPages/AddYearWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Markup.Xaml;
4 |
5 | namespace GradeManagement.Views.AddPages
6 | {
7 | public class AddYearWindow : Window
8 | {
9 | public AddYearWindow()
10 | {
11 | InitializeComponent();
12 | #if DEBUG
13 | this.AttachDevTools();
14 | #endif
15 | }
16 |
17 | private void InitializeComponent()
18 | {
19 | AvaloniaXamlLoader.Load(this);
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/src/Views/Dialogs/ConfirmationDialogView.axaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
31 |
44 |
46 |
47 |
51 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/Views/Dialogs/ConfirmationDialogView.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Markup.Xaml;
3 |
4 | namespace GradeManagement.Views.Dialogs
5 | {
6 | public class ConfirmationDialogView : UserControl
7 | {
8 | public ConfirmationDialogView()
9 | {
10 | InitializeComponent();
11 | }
12 |
13 | private void InitializeComponent()
14 | {
15 | AvaloniaXamlLoader.Load(this);
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/src/Views/Lists/ElementButtonControls/ButtonStyleBase.cs:
--------------------------------------------------------------------------------
1 | using GradeManagement.Interfaces;
2 | using GradeManagement.Models.Elements;
3 |
4 | namespace GradeManagement.Views.Lists.ElementButtonControls
5 | {
6 | public abstract class ButtonStyleBase
7 | {
8 | protected ButtonStyleBase(IElement element)
9 | {
10 | switch (element)
11 | {
12 | case SchoolYear year:
13 | YearRef = year;
14 | break;
15 | case Subject subject:
16 | SubjectRef = subject;
17 | break;
18 | case Grade grade:
19 | GradeRef = grade;
20 | break;
21 | }
22 | }
23 |
24 | protected SchoolYear? YearRef { get; }
25 | protected Subject? SubjectRef { get; }
26 | protected Grade? GradeRef { get; }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/Views/Lists/ElementButtonControls/GridButton.cs:
--------------------------------------------------------------------------------
1 | using GradeManagement.Interfaces;
2 |
3 | namespace GradeManagement.Views.Lists.ElementButtonControls
4 | {
5 | public class GridButton : ButtonStyleBase
6 | {
7 | public GridButton(IElement element) : base(element) { }
8 | }
9 | }
--------------------------------------------------------------------------------
/src/Views/Lists/ElementButtonControls/ListButton.cs:
--------------------------------------------------------------------------------
1 | using GradeManagement.Interfaces;
2 |
3 | namespace GradeManagement.Views.Lists.ElementButtonControls
4 | {
5 | public class ListButton : ButtonStyleBase
6 | {
7 | public ListButton(IElement element) : base(element) { }
8 | }
9 | }
--------------------------------------------------------------------------------
/src/Views/Lists/GradeListView.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Markup.Xaml;
2 | using GradeManagement.Models;
3 |
4 | namespace GradeManagement.Views.Lists
5 | {
6 | public class GradeListView : DragControl
7 | {
8 | public GradeListView()
9 | {
10 | InitializeComponent();
11 | }
12 |
13 | private void InitializeComponent()
14 | {
15 | AvaloniaXamlLoader.Load(this);
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/src/Views/Lists/SubjectListView.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Markup.Xaml;
2 | using GradeManagement.Models;
3 |
4 | namespace GradeManagement.Views.Lists
5 | {
6 | public class SubjectListView : DragControl
7 | {
8 | public SubjectListView()
9 | {
10 | InitializeComponent();
11 | }
12 |
13 | private void InitializeComponent()
14 | {
15 | AvaloniaXamlLoader.Load(this);
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/src/Views/Lists/YearListView.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Markup.Xaml;
2 | using GradeManagement.Models;
3 |
4 | namespace GradeManagement.Views.Lists
5 | {
6 | public class YearListView : DragControl
7 | {
8 | public YearListView()
9 | {
10 | InitializeComponent();
11 | }
12 |
13 | private void InitializeComponent()
14 | {
15 | AvaloniaXamlLoader.Load(this);
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/src/Views/MainWindow.axaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
26 |
27 |
29 |
30 |
32 |
33 |
35 |
36 |
37 |
38 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
56 |
57 |
58 |
68 |
69 |
70 |
71 |
72 |
73 |
75 |
76 |
77 |
78 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
89 |
90 |
91 |
92 |
93 |
95 |
96 |
--------------------------------------------------------------------------------
/src/Views/MainWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Avalonia;
3 | using Avalonia.Controls;
4 | using Avalonia.Input;
5 | using Avalonia.Markup.Xaml;
6 | using GradeManagement.Interfaces;
7 | using GradeManagement.Models;
8 | using GradeManagement.Models.Elements;
9 | using GradeManagement.ViewModels;
10 | using GradeManagement.ViewModels.BaseClasses;
11 | using GradeManagement.ViewModels.Lists;
12 |
13 | namespace GradeManagement.Views
14 | {
15 | public class MainWindow : Window
16 | {
17 | private MainWindowViewModel? _mainWindowModel;
18 |
19 | public MainWindow()
20 | {
21 | InitializeComponent();
22 | #if DEBUG
23 | this.AttachDevTools();
24 | #endif
25 | Instance = this;
26 | }
27 |
28 | internal static MainWindow? Instance { get; private set; }
29 |
30 | private void InitializeComponent()
31 | {
32 | AvaloniaXamlLoader.Load(this);
33 | }
34 |
35 | private void OnYearPressed(object? sender, PointerPressedEventArgs e)
36 | {
37 | SwitchPage(DataManager.SchoolYears);
38 | MainWindowViewModel.CurrentYear = null;
39 | }
40 |
41 | private void OnSubjectPressed(object? sender, PointerPressedEventArgs e)
42 | {
43 | SwitchPage(MainWindowViewModel.CurrentYear!.Subjects);
44 | }
45 |
46 | private void OnGradePressed(object? sender, PointerPressedEventArgs e)
47 | {
48 | // ReSharper disable once MergeIntoPattern
49 | if (MainWindowViewModel.Instance?.Content is GradeListViewModel viewModel && viewModel.GradesContainer?.ParentContainer != null)
50 | SwitchPage(viewModel.GradesContainer.ParentContainer.Grades, viewModel.GradesContainer.ParentContainer);
51 | }
52 |
53 | private void SwitchPage(IEnumerable elements, IGradesContainer? gradesContainer = null)
54 | where TViewModel : ListViewModelBase, IListViewModel
55 | where TElement : class, IElement, IGradable
56 | {
57 | _mainWindowModel ??= this.DataContext as MainWindowViewModel;
58 | _mainWindowModel!.SwitchPage(elements, gradesContainer);
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/src/Views/TargetGrade/AverageGradeView.axaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
15 |
17 |
19 |
21 |
22 |
--------------------------------------------------------------------------------
/src/Views/TargetGrade/AverageGradeView.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Markup.Xaml;
3 |
4 | namespace GradeManagement.Views.TargetGrade
5 | {
6 | public class AverageGradeView : UserControl
7 | {
8 | public AverageGradeView()
9 | {
10 | InitializeComponent();
11 | }
12 |
13 | private void InitializeComponent()
14 | {
15 | AvaloniaXamlLoader.Load(this);
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/src/Views/TargetGrade/TargetGradeView.axaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
15 |
17 |
19 |
21 |
22 |
--------------------------------------------------------------------------------
/src/Views/TargetGrade/TargetGradeView.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Markup.Xaml;
3 |
4 | namespace GradeManagement.Views.TargetGrade
5 | {
6 | public class TargetGradeView : UserControl
7 | {
8 | public TargetGradeView()
9 | {
10 | InitializeComponent();
11 | }
12 |
13 | private void InitializeComponent()
14 | {
15 | AvaloniaXamlLoader.Load(this);
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/src/Views/TargetGrade/TargetGradeWindow.axaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
24 |
27 |
31 |
32 |
33 |
34 |
36 |
51 |
66 |
67 |
68 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/src/Views/TargetGrade/TargetGradeWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Avalonia.Markup.Xaml;
4 |
5 | namespace GradeManagement.Views.TargetGrade
6 | {
7 | public class TargetGradeWindow : Window
8 | {
9 | public TargetGradeWindow()
10 | {
11 | InitializeComponent();
12 | #if DEBUG
13 | this.AttachDevTools();
14 | #endif
15 | }
16 |
17 | private void InitializeComponent()
18 | {
19 | AvaloniaXamlLoader.Load(this);
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------