From 544b0c95913c123a95804b315130e3a851cf4bbe Mon Sep 17 00:00:00 2001 From: Daniel Pichler Date: Tue, 16 Dec 2025 15:27:09 +0100 Subject: [PATCH] Architecture Add DI, Interfaces, Repositories --- .../Interfaces/IHoursRepository.cs | 17 +++++ .../Interfaces/IHoursService.cs | 16 +++++ Jugenddienst Stunden/MauiProgram.cs | 7 ++ .../Repositories/HoursRepository.cs | 19 ++++++ Jugenddienst Stunden/Services/HoursService.cs | 38 +++++++++++ .../ViewModels/StundeViewModel.cs | 67 ++++++++++--------- .../ViewModels/StundenViewModel.cs | 31 ++++++--- 7 files changed, 152 insertions(+), 43 deletions(-) create mode 100644 Jugenddienst Stunden/Interfaces/IHoursRepository.cs create mode 100644 Jugenddienst Stunden/Interfaces/IHoursService.cs create mode 100644 Jugenddienst Stunden/Repositories/HoursRepository.cs create mode 100644 Jugenddienst Stunden/Services/HoursService.cs diff --git a/Jugenddienst Stunden/Interfaces/IHoursRepository.cs b/Jugenddienst Stunden/Interfaces/IHoursRepository.cs new file mode 100644 index 0000000..c1df1ed --- /dev/null +++ b/Jugenddienst Stunden/Interfaces/IHoursRepository.cs @@ -0,0 +1,17 @@ +using Jugenddienst_Stunden.Types; + +namespace Jugenddienst_Stunden.Interfaces; + +/// +/// Repository‑Schnittstelle für Datenzugriff (API/Storage) rund um Stunden. +/// +internal interface IHoursRepository { + Task LoadBase(string query); + Task LoadSettings(); + Task LoadData(); + Task LoadUser(string apiKey); + Task> LoadDay(DateTime date); + Task LoadEntry(int id); + Task SaveEntry(DayTime stunde); + Task DeleteEntry(DayTime stunde); +} diff --git a/Jugenddienst Stunden/Interfaces/IHoursService.cs b/Jugenddienst Stunden/Interfaces/IHoursService.cs new file mode 100644 index 0000000..a24f2bc --- /dev/null +++ b/Jugenddienst Stunden/Interfaces/IHoursService.cs @@ -0,0 +1,16 @@ +using Jugenddienst_Stunden.Types; + +namespace Jugenddienst_Stunden.Interfaces; + +/// +/// Fachlicher Service für Stunden – konsumiert Repository und stellt VM‑freundliche Methoden bereit. +/// +internal interface IHoursService { + Task<(Hours hours, Settings settings)> GetMonthSummaryAsync(DateTime monthDate); + Task<(List dayTimes, Settings settings)> GetDayWithSettingsAsync(DateTime date); + Task> GetDayRangeAsync(DateTime from, DateTime to); + Task GetSettingsAsync(); + Task GetEntryAsync(int id); + Task SaveEntryAsync(DayTime stunde); + Task DeleteEntryAsync(DayTime stunde); +} diff --git a/Jugenddienst Stunden/MauiProgram.cs b/Jugenddienst Stunden/MauiProgram.cs index b8f0b55..b432206 100644 --- a/Jugenddienst Stunden/MauiProgram.cs +++ b/Jugenddienst Stunden/MauiProgram.cs @@ -1,5 +1,8 @@ using CommunityToolkit.Maui; using Jugenddienst_Stunden.Models; +using Jugenddienst_Stunden.Interfaces; +using Jugenddienst_Stunden.Repositories; +using Jugenddienst_Stunden.Services; using Microsoft.Extensions.Logging; using ZXing.Net.Maui.Controls; @@ -36,6 +39,10 @@ public static class MauiProgram { builder.Logging.AddDebug(); #endif + // DI: Services & Repositories + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + return builder.Build(); } diff --git a/Jugenddienst Stunden/Repositories/HoursRepository.cs b/Jugenddienst Stunden/Repositories/HoursRepository.cs new file mode 100644 index 0000000..f3f2f11 --- /dev/null +++ b/Jugenddienst Stunden/Repositories/HoursRepository.cs @@ -0,0 +1,19 @@ +using Jugenddienst_Stunden.Interfaces; +using Jugenddienst_Stunden.Models; +using Jugenddienst_Stunden.Types; + +namespace Jugenddienst_Stunden.Repositories; + +/// +/// Standard-Repository, das die bestehende API-/Model-Logik kapselt. +/// +internal class HoursRepository : IHoursRepository { + public async Task LoadBase(string query) => await HoursBase.LoadBase(query); + public async Task LoadSettings() => await HoursBase.LoadSettings(); + public async Task LoadData() => await HoursBase.LoadData(); + public async Task LoadUser(string apiKey) => await HoursBase.LoadUser(apiKey); + public async Task> LoadDay(DateTime date) => await HoursBase.LoadDay(date); + public async Task LoadEntry(int id) => await HoursBase.LoadEntry(id); + public async Task SaveEntry(DayTime stunde) => await HoursBase.SaveEntry(stunde); + public async Task DeleteEntry(DayTime stunde) => await HoursBase.DeleteEntry(stunde); +} diff --git a/Jugenddienst Stunden/Services/HoursService.cs b/Jugenddienst Stunden/Services/HoursService.cs new file mode 100644 index 0000000..d516ee0 --- /dev/null +++ b/Jugenddienst Stunden/Services/HoursService.cs @@ -0,0 +1,38 @@ +using Jugenddienst_Stunden.Interfaces; +using Jugenddienst_Stunden.Types; + +namespace Jugenddienst_Stunden.Services; + +internal class HoursService : IHoursService { + private readonly IHoursRepository _repo; + + public HoursService(IHoursRepository repo) { + _repo = repo; + } + + public async Task<(Hours hours, Settings settings)> GetMonthSummaryAsync(DateTime monthDate) { + string q = $"hours&year={monthDate:yyyy}&month={monthDate:MM}"; + var baseRes = await _repo.LoadBase(q); + return (baseRes.hour, baseRes.settings); + } + + public async Task<(List dayTimes, Settings settings)> GetDayWithSettingsAsync(DateTime date) { + string q = $"date={date:yyyy-MM-dd}"; + var baseRes = await _repo.LoadBase(q); + return (baseRes.daytimes ?? new List(), baseRes.settings); + } + + public async Task> GetDayRangeAsync(DateTime from, DateTime to) { + string q = $"date={from:yyyy-MM-dd}&tilldate={to:yyyy-MM-dd}"; + var baseRes = await _repo.LoadBase(q); + return baseRes.daytimes ?? new List(); + } + + public async Task GetSettingsAsync() => await _repo.LoadSettings(); + + public async Task GetEntryAsync(int id) => await _repo.LoadEntry(id); + + public async Task SaveEntryAsync(DayTime stunde) => await _repo.SaveEntry(stunde); + + public async Task DeleteEntryAsync(DayTime stunde) => await _repo.DeleteEntry(stunde); +} diff --git a/Jugenddienst Stunden/ViewModels/StundeViewModel.cs b/Jugenddienst Stunden/ViewModels/StundeViewModel.cs index 82fd06b..d749372 100644 --- a/Jugenddienst Stunden/ViewModels/StundeViewModel.cs +++ b/Jugenddienst Stunden/ViewModels/StundeViewModel.cs @@ -5,12 +5,16 @@ using Jugenddienst_Stunden.Types; using System.ComponentModel; using System.Windows.Input; using static System.Runtime.InteropServices.JavaScript.JSType; +using Jugenddienst_Stunden.Interfaces; +using Jugenddienst_Stunden.Repositories; +using Jugenddienst_Stunden.Services; namespace Jugenddienst_Stunden.ViewModels; /// /// Viewmodel für die einzelnen Stundeneinträge / Bearbeitung /// public partial class StundeViewModel : ObservableObject, IQueryAttributable { + private readonly IHoursService _hoursService; public int Id { get; set; } public string Title { get; set; } = "Eintrag bearbeiten"; @@ -76,20 +80,20 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable { //public ICommand LoadDataCommand { get; private set; } - public StundeViewModel() { + public StundeViewModel() : this(GetServiceOrCreate()) { } + + private static IHoursService GetServiceOrCreate() => new HoursService(new HoursRepository()); + + internal StundeViewModel(IHoursService hoursService) { + _hoursService = hoursService; SaveCommand = new AsyncRelayCommand(Save); //DeleteCommand = new AsyncRelayCommand(Delete); DeleteConfirmCommand = new Command(async () => await DeleteConfirm()); } - public StundeViewModel(DayTime stunde) { - SaveCommand = new AsyncRelayCommand(Save); - DeleteConfirmCommand = new AsyncRelayCommand(DeleteConfirm); - } - private async void LoadSettingsAsync() { try { - Settings = await HoursBase.LoadSettings(); + Settings = await _hoursService.GetSettingsAsync(); GlobalVar.Settings = Settings; OptionsGemeinde = Settings.Gemeinden; @@ -114,7 +118,7 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable { if (proceed) { try { - await HoursBase.SaveEntry(DayTime); + await _hoursService.SaveEntryAsync(DayTime); } catch (Exception e) { AlertEvent?.Invoke(this, e.Message); exceptionOccurred = true; @@ -133,7 +137,7 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable { /// Löschen ohne Bestätigung /// private async Task Delete() { - await HoursBase.DeleteEntry(DayTime); + await _hoursService.DeleteEntryAsync(DayTime); await Shell.Current.GoToAsync($"..?date={DayTime.Day.ToString("yyyy-MM-dd")}"); } @@ -145,7 +149,7 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable { bool answer = await ConfirmEvent.Invoke("Achtung", "Löschen kann nicht ungeschehen gemacht werden. Fortfahren?"); if (answer) { //Löschen - await HoursBase.DeleteEntry(DayTime); + await _hoursService.DeleteEntryAsync(DayTime); await Shell.Current.GoToAsync($"..?date={DayTime.Day.ToString("yyyy-MM-dd")}"); } else { //nicht Löschen @@ -163,27 +167,27 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable { //DateTime heute = DateTime.Now; try { - //_dayTime = await HoursBase.LoadEntry(Convert.ToInt32(query["load"])); - BaseResponse dat = await HoursBase.LoadBase("id=" + Convert.ToInt32(query["load"])); - GlobalVar.Settings = dat.settings; - GemeindeAktivSet = dat.settings.GemeindeAktivSet; - ProjektAktivSet = dat.settings.ProjektAktivSet; + var entry = await _hoursService.GetEntryAsync(Convert.ToInt32(query["load"])); + var settings = await _hoursService.GetSettingsAsync(); + GlobalVar.Settings = settings; + GemeindeAktivSet = settings.GemeindeAktivSet; + ProjektAktivSet = settings.ProjektAktivSet; - DayTime = dat.daytime; - DayTime.TimeSpanVon = dat.daytime.Begin.ToTimeSpan(); - DayTime.TimeSpanBis = dat.daytime.End.ToTimeSpan(); + DayTime = entry; + DayTime.TimeSpanVon = entry.Begin.ToTimeSpan(); + DayTime.TimeSpanBis = entry.End.ToTimeSpan(); - OptionsGemeinde = dat.settings.Gemeinden ?? new List(); - OptionsProjekt = dat.settings.Projekte ?? new List(); - OptionsFreistellung = dat.settings.Freistellungen ?? new List(); + OptionsGemeinde = settings.Gemeinden ?? new List(); + OptionsProjekt = settings.Projekte ?? new List(); + OptionsFreistellung = settings.Freistellungen ?? new List(); DayTime.GemeindeAktiv = OptionsGemeinde.FirstOrDefault(Gemeinde => Gemeinde.Id == DayTime.Gemeinde) ?? new Gemeinde(); DayTime.ProjektAktiv = OptionsProjekt.FirstOrDefault(Projekt => Projekt.Id == DayTime.Projekt) ?? new Projekt(); DayTime.FreistellungAktiv = OptionsFreistellung.FirstOrDefault(Freistellung => Freistellung.Id == DayTime.Free) ?? new Freistellung(); //Evtl. noch die anderen Zeiten des gleichen Tages holen - BaseResponse dat1 = await HoursBase.LoadBase("date=" + DayTime.Day.ToString("yyyy-MM-dd")); - DayTimes = dat1.daytimes; + var day = await _hoursService.GetDayWithSettingsAsync(DayTime.Day); + DayTimes = day.dayTimes; OnPropertyChanged(nameof(DayTime)); OnPropertyChanged(nameof(DayTimes)); @@ -209,17 +213,16 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable { DateTime _date = DateTime.ParseExact((string)query["date"], "yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture); //Bei neuem Eintrag die vorhandenen des gleichen Tages anzeigen try { - //DayTimes = await HoursBase.LoadDay(_date); - BaseResponse dat = await HoursBase.LoadBase("date=" + _date.ToString("yyyy-MM-dd")); - GlobalVar.Settings = dat.settings; - DayTimes = dat.daytimes; + var (list, settings) = await _hoursService.GetDayWithSettingsAsync(_date); + GlobalVar.Settings = settings; + DayTimes = list; - OptionsGemeinde = dat.settings.Gemeinden; - OptionsProjekt = dat.settings.Projekte; - OptionsFreistellung = dat.settings.Freistellungen; + OptionsGemeinde = settings.Gemeinden; + OptionsProjekt = settings.Projekte; + OptionsFreistellung = settings.Freistellungen; - GemeindeAktivSet = dat.settings.GemeindeAktivSet; - ProjektAktivSet = dat.settings.ProjektAktivSet; + GemeindeAktivSet = settings.GemeindeAktivSet; + ProjektAktivSet = settings.ProjektAktivSet; } catch (Exception) { //Ein Tag ohne Einträge gibt eine Fehlermeldung, diff --git a/Jugenddienst Stunden/ViewModels/StundenViewModel.cs b/Jugenddienst Stunden/ViewModels/StundenViewModel.cs index 315ce1e..415c4ea 100644 --- a/Jugenddienst Stunden/ViewModels/StundenViewModel.cs +++ b/Jugenddienst Stunden/ViewModels/StundenViewModel.cs @@ -8,6 +8,9 @@ using System.Windows.Input; using CommunityToolkit.Maui.Alerts; using CommunityToolkit.Maui.Core; using static System.Runtime.InteropServices.JavaScript.JSType; +using Jugenddienst_Stunden.Interfaces; +using Jugenddienst_Stunden.Repositories; +using Jugenddienst_Stunden.Services; namespace Jugenddienst_Stunden.ViewModels; @@ -15,6 +18,7 @@ namespace Jugenddienst_Stunden.ViewModels; /// ViewModel für die Stundenliste /// internal partial class StundenViewModel : ObservableObject, IQueryAttributable, INotifyPropertyChanged { + private readonly IHoursService _hoursService; public ICommand NewEntryCommand { get; } public ICommand SelectEntryCommand { get; } @@ -156,7 +160,13 @@ internal partial class StundenViewModel : ObservableObject, IQueryAttributable, /// /// CTOR /// - public StundenViewModel() { + public StundenViewModel() : this(GetServiceOrCreate()) { + } + + private static IHoursService GetServiceOrCreate() => new HoursService(new HoursRepository()); + + internal StundenViewModel(IHoursService hoursService) { + _hoursService = hoursService; Hours = new Hours(); LoadOverview = "Lade Summen für " + DateToday.ToString("MMMM"); @@ -200,9 +210,9 @@ internal partial class StundenViewModel : ObservableObject, IQueryAttributable, /// private async Task LoadData() { try { - BaseResponse dat = await HoursBase.LoadBase("hours&year=" + DateToday.ToString("yyyy") + "&month=" + DateToday.ToString("MM")); - Hours = dat.hour; - Settings = dat.settings; + var (hours, settings) = await _hoursService.GetMonthSummaryAsync(DateToday); + Hours = hours; + Settings = settings; if (Settings.Version != AppInfo.Current.VersionString.Substring(0, 5)) { InfoEvent?.Invoke(this, "Version: " + Settings.Version + " verfügbar (" + AppInfo.Current.VersionString.Substring(0, 5) + " installiert)"); @@ -222,11 +232,10 @@ internal partial class StundenViewModel : ObservableObject, IQueryAttributable, DayTotal = new TimeOnly(0); Sollstunden = new TimeOnly(0); try { - //_dayTimes = await HoursBase.LoadDay(date); - BaseResponse dat = await HoursBase.LoadBase("date=" + date.ToString("yyyy-MM-dd")); + var (dayTimes, settings) = await _hoursService.GetDayWithSettingsAsync(date); - DayTimes = dat.daytimes; - Settings = dat.settings; + DayTimes = dayTimes; + Settings = settings; GemeindeAktivSet = Settings.GemeindeAktivSet; ProjektAktivSet = Settings.ProjektAktivSet; @@ -251,9 +260,9 @@ internal partial class StundenViewModel : ObservableObject, IQueryAttributable, //Nach der Tagessumme die anderen Tage anhängen if (DayTimes != null) { - BaseResponse dat1 = await HoursBase.LoadBase("date=" + date.ToString("yyyy-MM-dd") + "&tilldate=" + date.AddDays(3).ToString("yyyy-MM-dd")); - if (dat1.daytimes != null) - DayTimes = dat.daytimes.Concat(dat1.daytimes).ToList(); + var more = await _hoursService.GetDayRangeAsync(date, date.AddDays(3)); + if (more != null && more.Count > 0) + DayTimes = DayTimes.Concat(more).ToList(); } } catch (Exception e) {