diff --git a/StudiApp/StudiApp.Test.Unit/ConverterTests/AverageMarkConverterTest.cs b/StudiApp/StudiApp.Test.Unit/ConverterTests/AverageMarkConverterTest.cs
index abbe3422044afa0c63c8edd8fc47cf59a9d1bdac..420fd8bf9e3d5e95bb711f3f25a2c7d61c76bf9c 100644
--- a/StudiApp/StudiApp.Test.Unit/ConverterTests/AverageMarkConverterTest.cs
+++ b/StudiApp/StudiApp.Test.Unit/ConverterTests/AverageMarkConverterTest.cs
@@ -11,6 +11,8 @@ namespace StudiApp.Test.Unit.ConverterTests
         [TestCase(1.0, 6.0, 0.7, 0.3, 2.5)]
         [TestCase(5.0, 6.0, 0.35, 0.65, 5.65)]
         [TestCase(5.0, 6.0, 0.01, 0.99, 5.99)]
+        [TestCase(4.0, 6.0, 1, 1, 5.0)]
+        [TestCase(4.5, 5, 0.2, 0.4, 4.83)]
         public void AverageMarkTest(double mark1, double mark2, double percentage1, double percentage2, double expected)
         {
             var m1 = new Mark()
diff --git a/StudiApp/StudiApp.Test.Unit/StudiApp.Test.Unit.csproj b/StudiApp/StudiApp.Test.Unit/StudiApp.Test.Unit.csproj
index 7d11f0debfc4bc89a1c1c791233648754999259b..056e01bbcedd613ca1577581ba006d28d863ce26 100644
--- a/StudiApp/StudiApp.Test.Unit/StudiApp.Test.Unit.csproj
+++ b/StudiApp/StudiApp.Test.Unit/StudiApp.Test.Unit.csproj
@@ -10,10 +10,12 @@
 	<ItemGroup>
 		<PackageReference Include="JunitXml.TestLogger" Version="3.0.124" />
 		<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
+		<PackageReference Include="Moq" Version="4.18.4" />
 		<PackageReference Include="NUnit" Version="3.13.3" />
 		<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
 		<PackageReference Include="NUnit.Analyzers" Version="3.5.0" />
 		<PackageReference Include="coverlet.collector" Version="3.1.2" />
+		<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
 	</ItemGroup>
 	<ItemGroup>
 	  <ProjectReference Include="..\StudiApp\StudiApp.csproj" />
diff --git a/StudiApp/StudiApp.Test.Unit/ViewModelsTests/MarkCreateViewModelTest.cs b/StudiApp/StudiApp.Test.Unit/ViewModelsTests/MarkCreateViewModelTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f00b98332d1a8b003c64a1d44cb82d945452a4c6
--- /dev/null
+++ b/StudiApp/StudiApp.Test.Unit/ViewModelsTests/MarkCreateViewModelTest.cs
@@ -0,0 +1,15 @@
+using System.Runtime.CompilerServices;
+using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Serialization;
+using NUnit.Framework.Internal;
+using StudiApp.Models;
+using StudiApp.ViewModels;
+
+namespace StudiApp.Test.Unit.ViewModelsTests;
+
+
+
+[TestFixture]
+public class MarkCreateViewModelTest
+{
+
+}
\ No newline at end of file
diff --git a/StudiApp/StudiApp.Test.Unit/ViewModelsTests/MarkEditViewModelTest.cs b/StudiApp/StudiApp.Test.Unit/ViewModelsTests/MarkEditViewModelTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ce27f174d76b5b453e51fb5fb2444824a426228b
--- /dev/null
+++ b/StudiApp/StudiApp.Test.Unit/ViewModelsTests/MarkEditViewModelTest.cs
@@ -0,0 +1,10 @@
+using StudiApp.Models;
+using StudiApp.ViewModels;
+
+namespace StudiApp.Test.Unit.ViewModelsTests;
+
+[TestFixture]
+public class MarkEditViewModelTest
+{
+    
+}
\ No newline at end of file
diff --git a/StudiApp/StudiApp.Test.Unit/ViewModelsTests/MarksOverviewViewModelTest.cs b/StudiApp/StudiApp.Test.Unit/ViewModelsTests/MarksOverviewViewModelTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c3ba7bc0b50ccfc18c2263cbe14c901b75f7eb08
--- /dev/null
+++ b/StudiApp/StudiApp.Test.Unit/ViewModelsTests/MarksOverviewViewModelTest.cs
@@ -0,0 +1,82 @@
+using StudiApp.Models;
+using StudiApp.Services.Repository;
+using StudiApp.ViewModels;
+using StudiApp.Interfaces;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+
+namespace StudiApp.Test.Unit.ViewModelsTests;
+
+[TestFixture]
+public class MarksOverviewViewModelTest
+{
+    private MarksOverviewViewModel vm;
+    private Semester semester1;
+    private Semester semester2;
+    private List<Semester> semesters;
+    private List<Course> semester1Courses;
+    private List<Course> semester2Courses;
+    private List<Mark> semester1Marks;
+    private List<Mark> semester2Marks;
+
+    [SetUp]
+    public void Setup()
+    {
+        semester1 = new Semester()
+        {
+            DateRange = new DateRange() { Start = new DateTime(2032, 1, 1), End = new DateTime(2023, 6, 1) },
+            Name = "Semester 1",
+            Id = 1
+        };
+        semester2 = new Semester()
+        {
+            DateRange = new DateRange() { Start = new DateTime(2032, 6, 2), End = new DateTime(2023, 12, 1) },
+            Name = "Semester 2",
+            Id = 2
+        };
+        semesters = new List<Semester>() { semester1, semester2 };
+
+
+        var mark1 = new Mark { Id = 1, Value = 4, Description = "Exam 1", Percentage = 0.5 };
+        var mark2 = new Mark { Id = 2, Value = 4, Description = "Exam 2", Percentage = 0.5 };
+        semester1Marks = new List<Mark>() { mark1 };
+        semester2Marks = new List<Mark>() { mark2 };
+        var allMarks = new List<Mark>() { mark1, mark2 };
+
+        var course1 = new Course() { Id = 1, Name = "Course 1", Ects = 4, IsPassed = true, SemesterId = 1, Marks = semester1Marks };
+        var course2 = new Course() { Id = 2, Name = "Course 2", Ects = 2, IsPassed = false, SemesterId = 2, Marks = semester2Marks };
+        semester1Courses = new List<Course>() { course1 };
+        semester2Courses = new List<Course>() { course2 };
+        var allCourses = new List<Course>() { course1, course2 };
+
+
+        var semesterRepository = new Mock<IRepository<Semester, int>>();
+        semesterRepository.Setup(r => r.GetAll())
+            .Returns(Task.FromResult(semesters));
+
+        var courseRepository = new Mock<IRepository<Course, int>>();
+        courseRepository.Setup(r => r.GetAll())
+            .Returns(Task.FromResult(allCourses));
+
+        var markRepository = new Mock<IRepository<Mark, int>>();
+        markRepository.Setup(r => r.GetAll())
+            .Returns(Task.FromResult(allMarks));
+
+        vm = new MarksOverviewViewModel(
+            new NullLogger<MarksOverviewViewModel>(),
+            semesterRepository.Object,
+            courseRepository.Object,
+            markRepository.Object);
+    }
+
+
+    [Test]
+    public void OnSelectedSemesterChangedTest()
+    {
+        vm.SelectedSemester = semester2;
+        
+        var actualId = vm.Marks[0].Id;
+        var expectedId= semester2Marks[0].Id;
+        Assert.That(actualId, Is.EqualTo(expectedId));
+    }
+}
diff --git a/StudiApp/StudiApp/AppShell.xaml.cs b/StudiApp/StudiApp/AppShell.xaml.cs
index c7f0a9c87cf69fe3fb0f95bb78a919154028eb5d..b74844ae3b5c9d443ea1a0a5d33d90c9af9d0384 100644
--- a/StudiApp/StudiApp/AppShell.xaml.cs
+++ b/StudiApp/StudiApp/AppShell.xaml.cs
@@ -1,4 +1,5 @@
-using StudiApp.Views;
+using System.Runtime.CompilerServices;
+using StudiApp.Views;
 
 namespace StudiApp;
 
@@ -16,5 +17,6 @@ public partial class AppShell : Shell
         Routing.RegisterRoute(nameof(CoursesEditView), typeof(CoursesEditView));
         Routing.RegisterRoute(nameof(TodoEditView), typeof(TodoEditView));
         Routing.RegisterRoute(nameof(MarksEditView), typeof(MarksEditView));
+        Routing.RegisterRoute(nameof(MarksCreateView), typeof(MarksCreateView));
     }
 }
diff --git a/StudiApp/StudiApp/MauiProgram.cs b/StudiApp/StudiApp/MauiProgram.cs
index 2e37fcc87489bfa0d113cfcfea15b7daf10516e7..16dc5c2b64dd339b5dcfe339aeb31dde39303a37 100644
--- a/StudiApp/StudiApp/MauiProgram.cs
+++ b/StudiApp/StudiApp/MauiProgram.cs
@@ -8,6 +8,7 @@ using StudiApp.Services.Repository;
 using StudiApp.ViewModels;
 using StudiApp.Views;
 using Syncfusion.Maui.Core.Hosting;
+using CommunityToolkit.Maui;
 
 namespace StudiApp;
 
@@ -68,11 +69,11 @@ public static class MauiProgram
         mauiAppBuilder.Services.AddSingleton<TimetableViewModel>();
         mauiAppBuilder.Services.AddSingleton<TodoOverviewViewModel>();
         mauiAppBuilder.Services.AddSingleton<SettingsViewModel>();
-        mauiAppBuilder.Services.AddSingleton<IRepository<Mark, int>, SqliteRepository<Mark>>();
 
         mauiAppBuilder.Services.AddTransient<CoursesEditViewModel>();
         mauiAppBuilder.Services.AddTransient<TodoEditViewModel>();
         mauiAppBuilder.Services.AddTransient<MarksEditViewModel>();
+        mauiAppBuilder.Services.AddTransient<MarksCreateViewModel>();
 
         return mauiAppBuilder;
     }
@@ -88,6 +89,7 @@ public static class MauiProgram
         mauiAppBuilder.Services.AddTransient<CoursesEditView>();
         mauiAppBuilder.Services.AddTransient<TodoEditView>();
         mauiAppBuilder.Services.AddTransient<MarksEditView>();
+        mauiAppBuilder.Services.AddTransient<MarksCreateView>();
 
         return mauiAppBuilder;
     }
diff --git a/StudiApp/StudiApp/Services/Converters/AverageMarkConverter.cs b/StudiApp/StudiApp/Services/Converters/AverageMarkConverter.cs
index f84f27e6e5f579f2d5c18cca8fd61cc14ae530c4..901a3019db03db5e9dcddfe7eee878cbd0c73976 100644
--- a/StudiApp/StudiApp/Services/Converters/AverageMarkConverter.cs
+++ b/StudiApp/StudiApp/Services/Converters/AverageMarkConverter.cs
@@ -16,7 +16,9 @@ namespace StudiApp.Services.Converters
                 throw new ArgumentException("A Percentage in the marks list is over 100% (1.0) or under 1% (0.01)");
             }
 
-            var average =  marks.Sum(mark => mark.Value * mark.Percentage);
+            var marksSum =  marks.Sum(mark => mark.Value * mark.Percentage);
+            var percentageSum = marks.Sum(mark => mark.Percentage);
+            var average = marksSum / percentageSum;
             return Math.Round(average, 2);
         } 
     }
diff --git a/StudiApp/StudiApp/StudiApp.csproj b/StudiApp/StudiApp/StudiApp.csproj
index ea15876a25ef0fb3addd5a6d3a799cd4de4d3152..0b7f3e9201eb8df33ce33251e82e00e9657be8a1 100644
--- a/StudiApp/StudiApp/StudiApp.csproj
+++ b/StudiApp/StudiApp/StudiApp.csproj
@@ -49,6 +49,7 @@
 		<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0" />
 		<PackageReference Include="MetroLog.Maui" Version="2.1.0" />
 		<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
+		<PackageReference Include="Syncfusion.Maui.Inputs" Version="21.1.39" />
 		<PackageReference Include="Syncfusion.Maui.Scheduler" Version="21.1.37" />
 		<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
 		<PackageReference Include="SQLiteNetExtensions" Version="2.1.0" />
@@ -69,6 +70,9 @@
 		<MauiXaml Update="Views\DashboardView.xaml">
 			<SubType>Designer</SubType>
 		</MauiXaml>
+		<MauiXaml Update="Views\MarksCreateView.xaml">
+		  <Generator>MSBuild:Compile</Generator>
+		</MauiXaml>
 		<MauiXaml Update="Views\SettingsView.xaml">
 			<Generator>MSBuild:Compile</Generator>
 		</MauiXaml>
@@ -84,6 +88,9 @@
 			<DependentUpon>DashboardView.xaml</DependentUpon>
 			<SubType>Code</SubType>
 		</Compile>
+		<Compile Update="Views\MarksCreateView.xaml.cs">
+		  <DependentUpon>MarksCreateView.xaml</DependentUpon>
+		</Compile>
 		<Compile Update="Views\MarksEditView.xaml.cs">
 			<DependentUpon>MarksEditView.xaml</DependentUpon>
 		</Compile>
diff --git a/StudiApp/StudiApp/ViewModels/MarksCreateViewModel.cs b/StudiApp/StudiApp/ViewModels/MarksCreateViewModel.cs
new file mode 100644
index 0000000000000000000000000000000000000000..10b754c11bf0958bd4802eebdeca4e15a5beeb28
--- /dev/null
+++ b/StudiApp/StudiApp/ViewModels/MarksCreateViewModel.cs
@@ -0,0 +1,167 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Microsoft.Extensions.Logging;
+using StudiApp.Interfaces;
+using StudiApp.Models;
+using StudiApp.Services.Converters;
+using System.Collections.ObjectModel;
+using System.Runtime.CompilerServices;
+
+namespace StudiApp.ViewModels
+{
+    [QueryProperty(nameof(Mark), "Mark")]
+    public partial class MarksCreateViewModel : ObservableObject, IQueryAttributable
+    {
+        private readonly ILogger<MarksCreateViewModel> _logger;
+        private readonly IRepository<Mark, int> _markRepository;
+        private readonly IRepository<Course, int> _courseRepository;
+
+        [ObservableProperty] private Mark _mark;
+        [ObservableProperty] private string _percentage;
+        [ObservableProperty] private ObservableCollection<Course> _courses;
+
+        [ObservableProperty] private bool _courseHasError;
+        [ObservableProperty] private bool _descriptionHasError;
+        [ObservableProperty] private bool _markHasError;
+        [ObservableProperty] private bool _percentageHasError;
+
+        private string _errorMessage;
+
+        public MarksCreateViewModel(
+            ILogger<MarksCreateViewModel> logger,
+            IRepository<Mark, int> markMaRepository,
+            IRepository<Course, int> coursesRepository)
+        {
+            _logger = logger;
+            _markRepository = markMaRepository;
+            _courseRepository = coursesRepository;
+            _mark = new Mark();
+            _courses = new ObservableCollection<Course>();
+
+            _logger.LogInformation($"Initialized {nameof(MarksCreateViewModel)}");
+
+            LoadCourses();
+        }
+
+        [RelayCommandAttribute]
+        public async void Save()
+        {
+            Mark.Percentage = ConvertPercentageToDouble(Percentage);
+
+            var isInputValid = ValidateInput();
+
+            if (isInputValid)
+            {
+                await _markRepository.Create(Mark);
+
+                _logger.LogInformation("New Mark created");
+
+                await Shell.Current.GoToAsync("..");
+            }
+            else
+            {
+                // TODO: Implement dialog service
+            }
+
+            
+        }
+        
+        public void ApplyQueryAttributes(IDictionary<string, object> query)
+        {
+        }
+
+        private async void LoadCourses()
+        {
+
+            List<Course> courses = await _courseRepository.GetAll();
+
+            foreach (var course in courses)
+            {
+                Courses.Add(course);
+            }
+
+        }
+
+        private double ConvertPercentageToDouble(string percentage)
+        {
+            return PercentageConverter.ConvertToDouble(percentage);
+        }
+
+        private bool ValidateInput()
+        {
+            _errorMessage = string.Empty;
+
+            return ValidateCourse() &&
+                   ValidateDescription() &&
+                   ValidateMarkValue() &&
+                   ValidatePercentage();
+        }
+
+        private bool ValidateCourse()
+        {
+            if (Mark.Course != null)
+            {
+                CourseHasError = false;
+                return true;
+            }
+
+            CourseHasError = true;
+            _errorMessage += "A course must be chosen\n";
+            return false;
+        }
+
+        private bool ValidateDescription()
+        {
+            if (Mark.Description is { Length: <= 250 and > 1 })
+            {
+                DescriptionHasError = false;
+                return true;
+            }
+
+            DescriptionHasError = true;
+            _errorMessage += "Description between 1 - 250 chars\n";
+            return false;
+
+        }
+
+        private bool ValidateMarkValue()
+        {
+            if (Mark.Value is >= 1.0 and <= 6.0)
+            {
+                MarkHasError = false;
+                return true;
+            }
+
+            MarkHasError = true;
+            _errorMessage += "Mark value out of range, must be between 1.0 and 6.0\n";
+            return false;
+        }
+
+        private bool ValidatePercentage()
+        {
+            if (Mark.Percentage is > 0 and <= 1 && ValidatePercentageOverAllMarks())
+            {
+                PercentageHasError = false;
+                return true;
+            } 
+            
+            if (!ValidatePercentageOverAllMarks())
+            {
+                MarkHasError = true;
+                _errorMessage += "Percentage is above 100% over all marks of this course";
+                return false;
+            }
+
+            MarkHasError = true;
+            _errorMessage += "Percentage out of range, must be in range 0.1 up to 1.0";
+            return false;
+        }
+
+        private bool ValidatePercentageOverAllMarks()
+        {
+            var overallCourseMarkPercentage = Mark.Course.Marks.Sum(mark => mark.Percentage);
+
+            return (overallCourseMarkPercentage + Mark.Percentage) <= 1.0;
+        }
+    }
+}
diff --git a/StudiApp/StudiApp/ViewModels/MarksEditViewModel.cs b/StudiApp/StudiApp/ViewModels/MarksEditViewModel.cs
index 497cc721ade679551c773f5abdadca45f279b377..d94f9c87910394ee9eb9a59ffb1b0ba284b8e090 100644
--- a/StudiApp/StudiApp/ViewModels/MarksEditViewModel.cs
+++ b/StudiApp/StudiApp/ViewModels/MarksEditViewModel.cs
@@ -1,8 +1,169 @@
-using CommunityToolkit.Mvvm.ComponentModel;
+using System.Collections.ObjectModel;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Microsoft.Extensions.Logging;
+using StudiApp.Interfaces;
+using StudiApp.Models;
+using StudiApp.Services.Converters;
 
-namespace StudiApp.ViewModels
+namespace StudiApp.ViewModels;
+
+[QueryProperty(nameof(Mark), "Mark")]
+public partial class MarksEditViewModel : ObservableObject, IQueryAttributable
 {
-    public partial class MarksEditViewModel : ObservableObject
+    private readonly ILogger<MarksEditViewModel> _logger;
+    private readonly IRepository<Mark, int> _markRepository;
+    private readonly IRepository<Course, int> _courseRepository;
+
+    [ObservableProperty] private Mark _mark;
+    [ObservableProperty] private string _percentage;
+    [ObservableProperty] private ObservableCollection<Course> _courses;
+
+    [ObservableProperty] private bool _courseHasError;
+    [ObservableProperty] private bool _descriptionHasError;
+    [ObservableProperty] private bool _markHasError;
+    [ObservableProperty] private bool _percentageHasError;
+
+    private string _errorMessage;
+
+    public MarksEditViewModel(
+        ILogger<MarksEditViewModel> logger,
+        IRepository<Mark, int> markRepository,
+        IRepository<Course, int> courseRepository)
+    {
+        _logger = logger;
+        _markRepository = markRepository;
+        _courseRepository = courseRepository;
+        _courses = new ObservableCollection<Course>();
+
+        LoadCourses();
+
+        _logger.LogInformation($"Initialized {nameof(MarksEditViewModel)}");
+    }
+
+    public void ApplyQueryAttributes(IDictionary<string, object> query)
+    {
+        Mark = (query[nameof(Mark)] as Mark)!;
+        Percentage = PercentageConverter.ConvertToString(Mark.Percentage);
+        _logger.LogInformation($"Edit Page for Mark {Mark.Id} loaded");
+    }
+
+    [RelayCommandAttribute]
+    public async void Save()
+    {
+        Mark.Percentage = ConvertPercentageToDouble(Percentage);
+
+        var isInputValid = ValidateInput();
+
+        if (isInputValid)
+        {
+            _logger.LogInformation("Mark name: " + Mark.Description);
+            
+            await _markRepository.Update(Mark);
+
+            _logger.LogInformation("Altered Mark Saved");
+
+            await Shell.Current.GoToAsync("..");
+        }
+        else
+        {
+            // TODO: Implement dialog service
+        }
+    }
+
+    private async void LoadCourses()
+    {
+    
+        List<Course> courses = await _courseRepository.GetAll();
+
+        foreach (var course in courses)
+        {
+            Courses.Add(course);
+        }
+        
+    }
+
+    private double ConvertPercentageToDouble(string percentage)
+    {
+        return PercentageConverter.ConvertToDouble(percentage);
+    }
+
+    private bool ValidateInput()
+    {
+        _errorMessage = string.Empty;
+
+        return ValidateCourse() &&
+               ValidateDescription() &&
+               ValidateMarkValue() &&
+               ValidatePercentage();
+    }
+
+    private bool ValidateCourse()
+    {
+        if (Mark.Course != null)
+        {
+            CourseHasError = false;
+            return true;
+        }
+
+        CourseHasError = true;
+        _errorMessage += "A course must be chosen\n";
+        return false;
+    }
+
+    private bool ValidateDescription()
+    {
+        if (Mark.Description is { Length: <= 250 and > 1 })
+        {
+            DescriptionHasError = false;
+            return true;
+        }
+
+        DescriptionHasError = true;
+        _errorMessage += "Description between 1 - 250 chars\n";
+        return false;
+
+    }
+
+    private bool ValidateMarkValue()
     {
+        if (Mark.Value is >= 1.0 and <= 6.0)
+        {
+            MarkHasError = false;
+            return true;
+        }
+
+        MarkHasError = true;
+        _errorMessage += "Mark value out of range, must be between 1.0 and 6.0\n";
+        return false;
+    }
+
+    private bool ValidatePercentage()
+    {
+        if (Mark.Percentage is > 0 and <= 1 && ValidatePercentageOverAllMarks())
+        {
+            PercentageHasError = false;
+            return true;
+        }
+
+        if (!ValidatePercentageOverAllMarks())
+        {
+            MarkHasError = true;
+            _errorMessage += "Percentage is above 100% over all marks of this course";
+            return false;
+        }
+
+        MarkHasError = true;
+        _errorMessage += "Percentage out of range, must be in range 0.1 up to 1.0";
+        return false;
     }
+
+    private bool ValidatePercentageOverAllMarks()
+    {
+        var overallCourseMarkPercentage = Mark.Course.Marks.Sum(mark => mark.Percentage);
+
+        return (overallCourseMarkPercentage) <= 1.0;
+    }
+
 }
+
diff --git a/StudiApp/StudiApp/ViewModels/MarksOverviewViewModel.cs b/StudiApp/StudiApp/ViewModels/MarksOverviewViewModel.cs
index 5ff7dde722b7f31a27ddd3296fa15870809e8cb7..2bb04a2855ae2c0e6f87e7bcfbe15182eaa738ae 100644
--- a/StudiApp/StudiApp/ViewModels/MarksOverviewViewModel.cs
+++ b/StudiApp/StudiApp/ViewModels/MarksOverviewViewModel.cs
@@ -1,8 +1,150 @@
-using CommunityToolkit.Mvvm.ComponentModel;
+using System.Collections.ObjectModel;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Microsoft.Extensions.Logging;
+using StudiApp.Interfaces;
+using StudiApp.Models;
+using StudiApp.Services.Converters;
+using StudiApp.Views;
 
-namespace StudiApp.ViewModels
+namespace StudiApp.ViewModels;
+public partial class MarksOverviewViewModel : ObservableObject
 {
-    public class MarksOverviewViewModel : ObservableObject
+    private readonly ILogger<MarksOverviewViewModel> _logger;
+
+    private readonly IRepository<Semester, int> _semesterRepository;
+    private readonly IRepository<Course, int> _courseRepository;
+    private readonly IRepository<Mark, int> _markRepository;
+
+    [ObservableProperty]
+    private double _totalAverageMark;
+
+    [ObservableProperty]
+    private int _totalEcts;
+
+    [ObservableProperty]
+    private double _semesterAverageMark;
+
+    [ObservableProperty]
+    private int _semesterEcts;
+
+    [ObservableProperty] private ObservableCollection<Mark> _marks;
+
+    [ObservableProperty] 
+    private ObservableCollection<Semester> _semesters;
+
+    [ObservableProperty] 
+    private Semester _selectedSemester;
+
+    public MarksOverviewViewModel(ILogger<MarksOverviewViewModel> logger,
+        IRepository<Semester, int> semesterRepository,
+        IRepository<Course, int> courseRepository,
+        IRepository<Mark, int> markRepository)
+    {
+        _semesterRepository = semesterRepository;
+        _courseRepository = courseRepository;
+        _markRepository = markRepository;
+        _logger = logger;
+
+        InitializeSemesterData();
+        LoadTotalOverview();
+    }
+
+    [RelayCommand]
+    public void OnAppearing()
+    {
+        LoadSemesterOverview();
+    }
+
+    private async void InitializeSemesterData()
+    {
+        //CreateMockData(); // TODO: remove this when not needed anymore
+        var result = await _semesterRepository.GetAll();
+        Semesters = new ObservableCollection<Semester>(result);
+        SelectedSemester = Semesters[0]; // TODO: get actual current 
+    }
+
+    private async void LoadSemesterOverview()
+    {
+        // TODO: if SelectedSemester is null, show text 'Select a semester'
+        var result = await _courseRepository.GetAll();
+        var semesterCourses = result.Where(c => c.SemesterId == SelectedSemester.Id).ToList();
+        var semesterMarks = GetMarksFromCourses(semesterCourses);
+
+        Marks = new ObservableCollection<Mark>(semesterMarks);
+        SemesterEcts = GetObtainedEcts(semesterCourses);
+        SemesterAverageMark = AverageMarkConverter.MarksToAverage(semesterMarks);
+    }
+
+    private void LoadTotalOverview()
     {
+        _courseRepository.GetAll().ContinueWith(t => TotalEcts = GetObtainedEcts(t.Result));
+        _markRepository.GetAll().ContinueWith(t => TotalAverageMark = AverageMarkConverter.MarksToAverage(t.Result));
+    }
+
+    private List<Mark> GetMarksFromCourses(List<Course> courses)
+    {
+        return courses.SelectMany(c => c.Marks).ToList();
+    }
+
+    partial void OnSelectedSemesterChanged(Semester value)
+    {
+        LoadSemesterOverview();
+    }
+
+    private int GetObtainedEcts(IEnumerable<Course> courses)
+    {
+        var passedCourses = courses.Where(course => course.IsPassed);
+        return passedCourses.Sum(course => course.Ects);
+    }
+
+    [RelayCommand]
+    public async Task EditMark(Mark mark)
+    {
+        await Shell.Current.GoToAsync(nameof(MarksEditView),
+            new Dictionary<string, object> { {"Mark", mark} });
+        _logger.LogInformation($"Navigating to {nameof(MarksEditView)} (edit mark)");
+    }
+
+    [RelayCommand]
+    public async Task NewMark()
+    {
+        await Shell.Current.GoToAsync(nameof(MarksCreateView));
+        _logger.LogInformation($"Navigating to {nameof(MarksCreateView)} (create new mark)");
+    }
+
+
+    // TODO: remove this function when not needed anymore
+    private async void CreateMockData()
+    {
+        var semester1 = new Semester()
+        {
+            DateRange = new DateRange() { Start = new DateTime(2032, 1, 1), End = new DateTime(2023, 6, 1) },
+            Name = "Semester 1"
+        };
+        var semester2 = new Semester()
+        {
+            DateRange = new DateRange() { Start = new DateTime(2032, 6, 2), End = new DateTime(2023, 12, 1) },
+            Name = "Semester 2"
+        };
+
+        semester1 = await _semesterRepository.Create(semester1);
+        semester2 = await _semesterRepository.Create(semester2);
+
+        var course1 = new Course() { Name = "Course 1", Ects = 4, IsPassed = true, Semester = semester1, SemesterId = semester1.Id };
+        var course2 = new Course() { Name = "Course 2", Ects = 2, IsPassed = true, Semester = semester1, SemesterId = semester1.Id };
+        var course3 = new Course() { Name = "Course 3", Ects = 4, IsPassed = true, Semester = semester2, SemesterId = semester2.Id };
+
+        course1 = await _courseRepository.Create(course1);
+        course2 = await _courseRepository.Create(course2);
+        course3 = await _courseRepository.Create(course3);
+
+        await _markRepository.Create(new Mark { Value = 4, Description = "Exam 1", Percentage = 0.5, Course = course1, CourseId = course1.Id });
+        await _markRepository.Create(new Mark { Value = 4, Description = "Exam 2", Percentage = 0.5, Course = course1, CourseId = course1.Id });
+        await _markRepository.Create(new Mark { Value = 4.5, Description = "Exam 1", Percentage = 0.5, Course = course2, CourseId = course2.Id });
+        await _markRepository.Create(new Mark { Value = 5.5, Description = "Exam 2", Percentage = 0.5, Course = course2, CourseId = course2.Id });
+        await _markRepository.Create(new Mark { Value = 1, Description = "Testat 1", Percentage = 0.5, Course = course3, CourseId = course3.Id });
+        await _markRepository.Create(new Mark { Value = 4.5, Description = "Testat 2", Percentage = 0.3, Course = course3, CourseId = course3.Id });
+        await _markRepository.Create(new Mark { Value = 4, Description = "Final Exam", Percentage = 0.2, Course = course3, CourseId = course3.Id });
     }
 }
diff --git a/StudiApp/StudiApp/Views/MarksCreateView.xaml b/StudiApp/StudiApp/Views/MarksCreateView.xaml
new file mode 100644
index 0000000000000000000000000000000000000000..469401880c0bc846ccfaa11c6cfd8411ff1b8811
--- /dev/null
+++ b/StudiApp/StudiApp/Views/MarksCreateView.xaml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+             x:Class="StudiApp.Views.MarksCreateView"
+             x:DataType="viewmodel:MarksCreateViewModel"
+             xmlns:viewmodel="clr-namespace:StudiApp.ViewModels"
+             xmlns:inputLayout="clr-namespace:Syncfusion.Maui.Core;assembly=Syncfusion.Maui.Core"
+             xmlns:control="clr-namespace:Syncfusion.Maui.Inputs;assembly=Syncfusion.Maui.Inputs"
+             Title="Create Mark">
+
+    <ContentPage.Content>
+        <Grid Padding="20" RowSpacing="10">
+
+            <Grid.RowDefinitions>
+                <RowDefinition Height="auto"/>
+                <RowDefinition Height="auto"/>
+                <RowDefinition Height="auto"/>
+                <RowDefinition Height="auto"/>
+            </Grid.RowDefinitions>
+
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition />
+                <ColumnDefinition />
+            </Grid.ColumnDefinitions>
+
+            <inputLayout:SfTextInputLayout Grid.Column="0" Grid.Row="0"
+                                           Grid.ColumnSpan="2"
+                                           WidthRequest="350"
+                                           HeightRequest="75"
+                                           Margin="0, -10, 0, -10"
+                                           ContainerBackground="White"
+                                           IsHintAlwaysFloated="true"
+                                           ContainerType="Outlined"
+                                           Hint="Course"
+                                           HasError="{Binding CourseHasError}">
+                <control:SfComboBox ItemsSource="{Binding Courses}"
+                                    DisplayMemberPath="Name"
+                                    TextMemberPath="Name"
+                                    SelectedItem="{Binding Mark.Course}">
+                </control:SfComboBox>
+            </inputLayout:SfTextInputLayout>
+
+            <inputLayout:SfTextInputLayout Grid.Row="1" Grid.Column="0"
+                                           Grid.ColumnSpan="2"
+                                           WidthRequest="350"
+                                           HeightRequest="75"
+                                           Margin="0,-10,0,-10"
+                                           ContainerBackground="White"
+                                           IsHintAlwaysFloated="true"
+                                           ContainerType="Outlined"
+                                           Hint="Description"
+                                           HasError="{Binding DescriptionHasError}">
+                <Entry Text="{Binding Mark.Description}"/>
+            </inputLayout:SfTextInputLayout>
+
+            <HorizontalStackLayout Grid.Row="2" Grid.Column="0"
+                                   Grid.ColumnSpan="2"
+                                   WidthRequest="350">
+
+                <inputLayout:SfTextInputLayout HeightRequest="75"
+                                               WidthRequest="175"
+                                               Margin="0,-10,0,-10"
+                                               ContainerBackground="White"
+                                               IsHintAlwaysFloated="true"
+                                               ContainerType="Outlined"
+                                               Hint="Mark">
+                    <control:SfMaskedEntry MaskType="RegEx"
+                                           Mask="[1-6]\.[0-9]{0,2}"
+                                           Value="{Binding Mark.Value, Mode=TwoWay}"/>
+
+                </inputLayout:SfTextInputLayout>
+
+                <inputLayout:SfTextInputLayout HeightRequest="75"
+                                               WidthRequest="175"
+                                               Margin="0,-10,0,-10"
+                                               ContainerBackground="White"
+                                               IsHintAlwaysFloated="true"
+                                               ContainerType="Outlined"
+                                               Hint="Percentage">
+                    <control:SfMaskedEntry MaskType="RegEx"
+                                           Mask="[0-9]{1,3}\%"
+                                           Value="{Binding Percentage, Mode=TwoWay}"/>
+                </inputLayout:SfTextInputLayout>
+
+            </HorizontalStackLayout>
+
+            <Button Grid.Row="3"
+                    Grid.Column="1"
+                    HeightRequest="40"
+                    WidthRequest="125"
+                    HorizontalOptions="End"
+                    CornerRadius="20"
+                    Text="Save"
+                    Command="{Binding SaveCommand}"/>
+
+        </Grid>
+    </ContentPage.Content>
+</ContentPage>
\ No newline at end of file
diff --git a/StudiApp/StudiApp/Views/MarksCreateView.xaml.cs b/StudiApp/StudiApp/Views/MarksCreateView.xaml.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4d570acaa60c48a84dd168a66a498e2e525697b6
--- /dev/null
+++ b/StudiApp/StudiApp/Views/MarksCreateView.xaml.cs
@@ -0,0 +1,12 @@
+using StudiApp.ViewModels;
+
+namespace StudiApp.Views;
+
+public partial class MarksCreateView : ContentPage
+{
+    public MarksCreateView(MarksCreateViewModel vm)
+    {
+        BindingContext = vm;
+        InitializeComponent();
+    }
+}
\ No newline at end of file
diff --git a/StudiApp/StudiApp/Views/MarksEditView.xaml b/StudiApp/StudiApp/Views/MarksEditView.xaml
index b95c09c8937e210e884c672af619f2c561450756..73106af3d253159c5bc96bd3de9da679833cbeb7 100644
--- a/StudiApp/StudiApp/Views/MarksEditView.xaml
+++ b/StudiApp/StudiApp/Views/MarksEditView.xaml
@@ -3,9 +3,88 @@
 <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
              x:Class="StudiApp.Views.MarksEditView"
+             x:DataType="viewmodel:MarksEditViewModel"
              xmlns:viewmodel="clr-namespace:StudiApp.ViewModels"
-             x:DataType="viewmodel:MarksEditViewModel">
+             xmlns:inputLayout="clr-namespace:Syncfusion.Maui.Core;assembly=Syncfusion.Maui.Core"
+             xmlns:control="clr-namespace:Syncfusion.Maui.Inputs;assembly=Syncfusion.Maui.Inputs"
+             Title="Edit Mark">
     <ContentPage.Content>
-        
+        <Grid Padding="20" RowSpacing="10">
+
+            <Grid.RowDefinitions>
+                <RowDefinition Height="auto"/>
+                <RowDefinition Height="auto"/>
+                <RowDefinition Height="auto"/>
+                <RowDefinition Height="auto"/>
+            </Grid.RowDefinitions>
+
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition />
+                <ColumnDefinition />
+            </Grid.ColumnDefinitions>
+
+            <inputLayout:SfTextInputLayout Grid.Column="0" Grid.Row="0"
+                                           Grid.ColumnSpan="2"
+                                           WidthRequest="350"
+                                           HeightRequest="75"
+                                           Margin="0, -10, 0, -10"
+                                           ContainerBackground="White"
+                                           IsHintAlwaysFloated="true"
+                                           ContainerType="Outlined"
+                                           Hint="Course">
+                <control:SfComboBox ItemsSource="{Binding Courses}"
+                                    DisplayMemberPath="Name"
+                                    SelectedItem="{Binding Mark.Course, Mode=TwoWay}">
+                </control:SfComboBox>
+            </inputLayout:SfTextInputLayout>
+
+            <inputLayout:SfTextInputLayout Grid.Row="1" Grid.Column="0"
+                                           Grid.ColumnSpan="2"
+                                           WidthRequest="350"
+                                           HeightRequest="75"
+                                           Margin="0,-10,0,-10"
+                                           ContainerBackground="White"
+                                           IsHintAlwaysFloated="true"
+                                           ContainerType="Outlined"
+                                           Hint="Description">
+                <Entry Text="{Binding Mark.Description}"/>
+            </inputLayout:SfTextInputLayout>
+
+            <HorizontalStackLayout Grid.Row="2" Grid.Column="0"
+                                   Grid.ColumnSpan="2"
+                                   WidthRequest="350">
+
+                <inputLayout:SfTextInputLayout HeightRequest="75"
+                                               WidthRequest="175"
+                                               Margin="0,-10,0,-10"
+                                               ContainerBackground="White"
+                                               IsHintAlwaysFloated="true"
+                                               ContainerType="Outlined"
+                                               Hint="Mark">
+                    <Entry Text="{Binding Mark.Value}"/>
+                </inputLayout:SfTextInputLayout>
+
+                <inputLayout:SfTextInputLayout HeightRequest="75"
+                                               WidthRequest="175"
+                                               Margin="0,-10,0,-10"
+                                               ContainerBackground="White"
+                                               IsHintAlwaysFloated="true"
+                                               ContainerType="Outlined"
+                                               Hint="Percentage">
+                    <Entry Text="{Binding Percentage}"/>
+                </inputLayout:SfTextInputLayout>
+
+            </HorizontalStackLayout>
+
+            <Button Grid.Row="3"
+                    Grid.Column="1"
+                    HeightRequest="40"
+                    WidthRequest="125"
+                    HorizontalOptions="End"
+                    CornerRadius="20"
+                    Text="Save"
+                    Command="{Binding SaveCommand}"/>
+
+        </Grid>
     </ContentPage.Content>
 </ContentPage>
\ No newline at end of file
diff --git a/StudiApp/StudiApp/Views/MarksOverviewView.xaml b/StudiApp/StudiApp/Views/MarksOverviewView.xaml
index 333cd93db73e0b2c7066b0e256b1625dc7d9bfe4..f4a4dee58642335f2a7f2814f3b06e01dd0a4a15 100644
--- a/StudiApp/StudiApp/Views/MarksOverviewView.xaml
+++ b/StudiApp/StudiApp/Views/MarksOverviewView.xaml
@@ -4,8 +4,161 @@
              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
              x:Class="StudiApp.Views.MarksOverviewView"
              xmlns:viewmodel="clr-namespace:StudiApp.ViewModels"
+             xmlns:model="clr-namespace:StudiApp.Models"
+             xmlns:mct="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
              x:DataType="viewmodel:MarksOverviewViewModel">
-    <ContentPage.Content>
-        
-    </ContentPage.Content>
-</ContentPage>
\ No newline at end of file
+    <ContentPage.Behaviors>
+        <mct:EventToCommandBehavior EventName="Appearing" Command="{Binding AppearingCommand}"/>
+    </ContentPage.Behaviors>
+    <ScrollView>
+        <VerticalStackLayout Spacing="10" Margin="10">
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="*" />
+                    <ColumnDefinition Width="Auto" />
+                </Grid.ColumnDefinitions>
+                <Label Text="Total Overview"
+                       FontSize="20"
+                       VerticalOptions="Center"/>
+                <Picker x:Name="SemesterPicker"
+                        Title="Select semester"
+                        Grid.Column="1"
+                        ItemsSource="{ Binding Semesters }"
+                        ItemDisplayBinding="{ Binding Name }"
+                        SelectedItem="{ Binding SelectedSemester }">
+                </Picker>
+            </Grid>
+
+            <Frame Padding="10">
+                <HorizontalStackLayout>
+                    <Grid>
+                        <Ellipse Fill="Black"
+                                 WidthRequest="40"
+                                 HeightRequest="40"
+                                 HorizontalOptions="Start" />
+                        <Label Text="{ Binding TotalEcts }"
+                               TextColor="White"
+                               FontSize="14"
+                               HorizontalOptions="Center"
+                               VerticalOptions="CenterAndExpand" />
+                    </Grid>
+                    <Label Text="Total ECTS" 
+                           VerticalOptions="Center"
+                           Margin="10,0"/>
+                </HorizontalStackLayout>
+            </Frame>
+            <Frame Padding="10">
+                <HorizontalStackLayout>
+                    <Grid>
+                        <Ellipse Fill="Black"
+                                 WidthRequest="40"
+                                 HeightRequest="40"
+                                 HorizontalOptions="Start"/>
+                        <Label Text="{ Binding TotalAverageMark }"
+                               TextColor="White"
+                               FontSize="14"
+                               HorizontalOptions="Center"
+                               VerticalOptions="CenterAndExpand" />
+                    </Grid>
+                    <Label Text="Average Mark" 
+                           VerticalOptions="Center"
+                           Margin="10,0"/>
+                </HorizontalStackLayout>
+            </Frame>
+
+            <Label Text="Semester Overview"
+                   FontSize="20"
+                   VerticalOptions="Center"/>
+
+            <Frame Padding="10">
+                <HorizontalStackLayout>
+                    <Grid>
+                        <Ellipse Fill="Black"
+                                 WidthRequest="40"
+                                 HeightRequest="40"
+                                 HorizontalOptions="Start" />
+                        <Label Text="{ Binding SemesterEcts }"
+                               TextColor="White"
+                               FontSize="14"
+                               HorizontalOptions="Center"
+                               VerticalOptions="CenterAndExpand" />
+                    </Grid>
+                    <Label Text="Obtained ECTS" 
+                           VerticalOptions="Center"
+                           Margin="10,0"/>
+                </HorizontalStackLayout>
+            </Frame>
+            <Frame Padding="10">
+                <HorizontalStackLayout>
+                    <Grid>
+                        <Ellipse Fill="Black"
+                                 WidthRequest="40"
+                                 HeightRequest="40"
+                                 HorizontalOptions="Start"/>
+                        <Label Text="{ Binding SemesterAverageMark }"
+                               TextColor="White"
+                               FontSize="14"
+                               HorizontalOptions="Center"
+                               VerticalOptions="CenterAndExpand" />
+                    </Grid>
+                    <Label Text="Average Mark" 
+                           VerticalOptions="Center"
+                           Margin="10,0"/>
+                </HorizontalStackLayout>
+            </Frame>
+
+
+
+            <Label Text="Marks List"
+                   FontSize="20"
+                   VerticalOptions="Center"/>
+
+            <CollectionView ItemsSource="{Binding Marks}" SelectionMode="None">
+                <CollectionView.ItemsLayout>
+                    <LinearItemsLayout Orientation="Vertical" ItemSpacing="10" />
+                </CollectionView.ItemsLayout>
+                <CollectionView.ItemTemplate>
+                    <DataTemplate x:DataType="{x:Type model:Mark}">
+                        <Frame Padding="10">
+                            <Frame.GestureRecognizers>
+                                <TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:MarksOverviewViewModel}}, Path=EditMarkCommand}"
+                                                      CommandParameter="{Binding}"/>
+                            </Frame.GestureRecognizers>
+
+                            <HorizontalStackLayout>
+                                <Grid>
+                                    <Ellipse Fill="Black"
+                                             WidthRequest="40"
+                                             HeightRequest="40"
+                                             HorizontalOptions="Start" />
+                                    <Label Text="{ Binding Value }"
+                                           TextColor="White"
+                                           FontSize="14"
+                                           HorizontalOptions="Center"
+                                           VerticalOptions="CenterAndExpand" />
+                                </Grid>
+
+                                <VerticalStackLayout Margin="10,0">
+                                    <Label>
+                                        <Label.Text>
+                                            <MultiBinding StringFormat="{}{0} - {1}">
+                                                <Binding Path="Course.Name"></Binding>
+                                                <Binding Path="Description"></Binding>
+                                            </MultiBinding>
+                                        </Label.Text>
+                                    </Label>
+                                    <Label Text="{ Binding Course.Semester.Name }"></Label>
+                                </VerticalStackLayout>
+                            </HorizontalStackLayout>
+                        </Frame>
+                    </DataTemplate>
+                </CollectionView.ItemTemplate>
+            </CollectionView>
+
+            <Button Text="New Mark"
+                    HorizontalOptions="End"
+                    Command="{Binding NewMarkCommand}"/>
+
+        </VerticalStackLayout>
+    </ScrollView>
+</ContentPage>