Refactor: Remove GlobalVar and replace with IAppSettings; restructure affected infrastructure, services, and view models for dependency injection.
This commit is contained in:
@@ -47,9 +47,11 @@ internal sealed class ApiClient : IApiClient {
|
|||||||
|
|
||||||
public async Task<T> SendAsync<T>(HttpMethod method, string path, object? body = null,
|
public async Task<T> SendAsync<T>(HttpMethod method, string path, object? body = null,
|
||||||
IDictionary<string, string?>? query = null, CancellationToken ct = default) {
|
IDictionary<string, string?>? query = null, CancellationToken ct = default) {
|
||||||
|
|
||||||
// Absolute URI aus aktuellem Settings‑BaseUrl bauen, ohne HttpClient.BaseAddress zu nutzen.
|
// Absolute URI aus aktuellem Settings‑BaseUrl bauen, ohne HttpClient.BaseAddress zu nutzen.
|
||||||
var uri = BuildAbsoluteUri(_settings.ApiUrl, path, query);
|
var uri = BuildAbsoluteUri(_settings.ApiUrl, path, query);
|
||||||
using var req = new HttpRequestMessage(method, uri);
|
using var req = new HttpRequestMessage(method, uri);
|
||||||
|
|
||||||
// Authorization PRO REQUEST setzen (immer, wenn Token vorhanden ist)
|
// Authorization PRO REQUEST setzen (immer, wenn Token vorhanden ist)
|
||||||
// Hinweis: Das QR-Token kann RFC-unzulässige Zeichen (z. B. '|') enthalten.
|
// Hinweis: Das QR-Token kann RFC-unzulässige Zeichen (z. B. '|') enthalten.
|
||||||
// AuthenticationHeaderValue würde solche Werte ablehnen. Daher ohne Validierung setzen.
|
// AuthenticationHeaderValue würde solche Werte ablehnen. Daher ohne Validierung setzen.
|
||||||
@@ -134,43 +136,44 @@ internal sealed class ApiClient : IApiClient {
|
|||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
"ApiUrl ist leer. Bitte zuerst eine gültige Server-URL setzen (Preferences key 'apiUrl').");
|
"ApiUrl ist leer. Bitte zuerst eine gültige Server-URL setzen (Preferences key 'apiUrl').");
|
||||||
|
|
||||||
// Basis muss absolut sein (z. B. https://host/appapi/)
|
|
||||||
var baseUri = new Uri(baseUrl, UriKind.Absolute);
|
|
||||||
|
|
||||||
|
var normalizedBase = baseUrl.Trim();
|
||||||
// Pfad relativ zur Basis aufbauen
|
// Pfad relativ zur Basis aufbauen
|
||||||
string relativePath = path ?? string.Empty;
|
string relativePath = path ?? string.Empty;
|
||||||
|
|
||||||
|
if (relativePath.StartsWith('/'))
|
||||||
|
relativePath = relativePath.TrimStart('/');
|
||||||
|
|
||||||
if (query is not null && query.Count > 0) {
|
if (query is not null && query.Count > 0) {
|
||||||
|
if (normalizedBase.EndsWith('/'))
|
||||||
|
normalizedBase = normalizedBase.TrimEnd('/');
|
||||||
|
|
||||||
var sb = new StringBuilder(relativePath);
|
var sb = new StringBuilder(relativePath);
|
||||||
sb.Append(relativePath.Contains('?') ? '&' : '?');
|
sb.Append(relativePath.Contains('?') ? '&' : '?');
|
||||||
sb.Append(string.Join('&', query
|
sb.Append(string.Join('&', query
|
||||||
.Where(kv => kv.Value is not null)
|
.Where(kv => kv.Value is not null)
|
||||||
.Select(kv => $"{Uri.EscapeDataString(kv.Key)}={Uri.EscapeDataString(kv.Value!)}")));
|
.Select(kv => $"{Uri.EscapeDataString(kv.Key)}={Uri.EscapeDataString(kv.Value!)}")));
|
||||||
relativePath = sb.ToString();
|
relativePath = sb.ToString();
|
||||||
|
} else {
|
||||||
|
if (!normalizedBase.EndsWith('/'))
|
||||||
|
normalizedBase += "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var baseUri = new Uri(normalizedBase, UriKind.Absolute);
|
||||||
|
|
||||||
// Wenn path bereits absolut ist, direkt verwenden
|
// Wenn path bereits absolut ist, direkt verwenden
|
||||||
//if (Uri.TryCreate(relativePath, UriKind.Absolute, out var absoluteFromPath))
|
if (Uri.TryCreate(relativePath, UriKind.Absolute, out var absoluteFromPath)) {
|
||||||
// return absoluteFromPath;
|
var uriString = absoluteFromPath.ToString();
|
||||||
|
if (uriString.EndsWith('/') && absoluteFromPath.AbsolutePath.Length > 1)
|
||||||
// Sonderfall: Wenn path ein absoluter file:// URI ist, diesen relativ zur Basis behandeln
|
return new Uri(uriString.TrimEnd('/'));
|
||||||
// Weiß nicht wie file:// zustande kommt, vermutlich wäre das zu verhindern
|
return absoluteFromPath;
|
||||||
if (Uri.TryCreate(relativePath, UriKind.Absolute, out var uri)) {
|
|
||||||
if (uri.Scheme == Uri.UriSchemeFile) {
|
|
||||||
|
|
||||||
var normalizedBase = baseUrl.Trim();
|
|
||||||
if (!normalizedBase.EndsWith('/'))
|
|
||||||
normalizedBase += "/";
|
|
||||||
|
|
||||||
if (relativePath.StartsWith('/'))
|
|
||||||
relativePath = relativePath.TrimStart('/');
|
|
||||||
|
|
||||||
var baseUriNormalized = new Uri(normalizedBase, UriKind.Absolute);
|
|
||||||
return new Uri(baseUriNormalized, relativePath);
|
|
||||||
}
|
|
||||||
return uri;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var result = new Uri(baseUri, relativePath);
|
||||||
|
var finalUriString = result.ToString();
|
||||||
|
if (finalUriString.EndsWith('/') && result.AbsolutePath.Length > 1)
|
||||||
|
return new Uri(finalUriString.TrimEnd('/'));
|
||||||
|
|
||||||
return new Uri(baseUri, relativePath);
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,18 @@
|
|||||||
using Jugenddienst_Stunden.Interfaces;
|
using Jugenddienst_Stunden.Interfaces;
|
||||||
|
using Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.Infrastructure;
|
namespace Jugenddienst_Stunden.Infrastructure;
|
||||||
|
|
||||||
internal sealed class PreferencesAppSettings : IAppSettings {
|
/// <summary>
|
||||||
|
/// Represents the application settings and provides access to user preferences
|
||||||
|
/// such as API URL, API key, employee ID, and personal details.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The <c>AppSettings</c> class implements the <c>IAppSettings</c> interface and manages
|
||||||
|
/// persistent configuration settings needed for the application.
|
||||||
|
/// These settings include preferences like API configuration and user identification details.
|
||||||
|
/// </remarks>
|
||||||
|
internal sealed class AppSettings : IAppSettings {
|
||||||
public string ApiUrl {
|
public string ApiUrl {
|
||||||
get => Preferences.Default.Get("apiUrl", "");
|
get => Preferences.Default.Get("apiUrl", "");
|
||||||
set => Preferences.Default.Set("apiUrl", value);
|
set => Preferences.Default.Set("apiUrl", value);
|
||||||
@@ -27,4 +37,5 @@ internal sealed class PreferencesAppSettings : IAppSettings {
|
|||||||
get => Preferences.Default.Get("surname", "Eingeloggt");
|
get => Preferences.Default.Get("surname", "Eingeloggt");
|
||||||
set => Preferences.Default.Set("surname", value);
|
set => Preferences.Default.Set("surname", value);
|
||||||
}
|
}
|
||||||
|
public Settings? Settings { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
using Jugenddienst_Stunden.Interfaces;
|
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.Infrastructure;
|
|
||||||
|
|
||||||
internal sealed class GlobalVarTokenProvider : ITokenProvider {
|
|
||||||
public string? GetToken() => Models.GlobalVar.ApiKey;
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
namespace Jugenddienst_Stunden.Interfaces;
|
namespace Jugenddienst_Stunden.Interfaces;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines methods for making HTTP requests to a specified API.
|
||||||
|
/// </summary>
|
||||||
internal interface IApiClient {
|
internal interface IApiClient {
|
||||||
Task<T> GetAsync<T>(string path, IDictionary<string, string?>? query = null, CancellationToken ct = default);
|
Task<T> GetAsync<T>(string path, IDictionary<string, string?>? query = null, CancellationToken ct = default);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
|
using Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.Interfaces;
|
namespace Jugenddienst_Stunden.Interfaces;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the application settings required for configuration and operation of the application.
|
||||||
|
/// </summary>
|
||||||
public interface IAppSettings {
|
public interface IAppSettings {
|
||||||
string ApiUrl { get; set; }
|
string ApiUrl { get; set; }
|
||||||
string ApiKey { get; set; }
|
string ApiKey { get; set; }
|
||||||
@@ -7,4 +12,6 @@ public interface IAppSettings {
|
|||||||
int EmployeeId { get; set; }
|
int EmployeeId { get; set; }
|
||||||
string Name { get; set; }
|
string Name { get; set; }
|
||||||
string Surname { get; set; }
|
string Surname { get; set; }
|
||||||
|
|
||||||
|
Settings? Settings { get; set; }
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ public static class MauiProgram {
|
|||||||
builder.Services.AddSingleton<IAlertService, AlertService>();
|
builder.Services.AddSingleton<IAlertService, AlertService>();
|
||||||
|
|
||||||
// DI: Settings aus Preferences (Single Source of Truth bleibt Preferences)
|
// DI: Settings aus Preferences (Single Source of Truth bleibt Preferences)
|
||||||
builder.Services.AddSingleton<IAppSettings, PreferencesAppSettings>();
|
builder.Services.AddSingleton<IAppSettings, AppSettings>();
|
||||||
|
|
||||||
// DI: ApiOptions IMMER aus aktuellen Settings erzeugen (nicht beim Start einfrieren)
|
// DI: ApiOptions IMMER aus aktuellen Settings erzeugen (nicht beim Start einfrieren)
|
||||||
builder.Services.AddTransient(sp => new ApiOptions {
|
builder.Services.AddTransient(sp => new ApiOptions {
|
||||||
|
|||||||
@@ -100,82 +100,7 @@ internal static class BaseFunc {
|
|||||||
new() { Date = File.GetLastWriteTime(filename) };
|
new() { Date = File.GetLastWriteTime(filename) };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stundeneintrag speichern
|
|
||||||
/// </summary>
|
|
||||||
internal static async Task SaveItemAsync(string url, string token, DayTime item, bool isNewItem = false) {
|
|
||||||
//Uhrzeiten sollten sinnvolle Werte haben - außer bei Freistellungen, da wäre eigentlich null
|
|
||||||
if (item.TimeSpanVon == item.TimeSpanBis && item.FreistellungAktiv == null) {
|
|
||||||
throw new Exception("Beginn und Ende sind gleich");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.TimeSpanBis < item.TimeSpanVon) {
|
|
||||||
throw new Exception("Ende ist vor Beginn");
|
|
||||||
}
|
|
||||||
|
|
||||||
TimeSpan span = TimeSpan.Zero;
|
|
||||||
span += item.TimeSpanBis - item.TimeSpanVon;
|
|
||||||
if (span.Hours > 10) {
|
|
||||||
//Hier vielleicht eine Abfrage, ob mehr als 10 Stunden gesund sind?
|
|
||||||
//Das müsste aber das ViewModel machen
|
|
||||||
}
|
|
||||||
|
|
||||||
//Gemeinde ist ein Pflichtfeld
|
|
||||||
if (item.GemeindeAktiv == null && GlobalVar.Settings.GemeindeAktivSet) {
|
|
||||||
throw new Exception("Gemeinde nicht gewählt");
|
|
||||||
}
|
|
||||||
|
|
||||||
//Projekt ist ein Pflichtfeld
|
|
||||||
if (item.ProjektAktiv == null && GlobalVar.Settings.ProjektAktivSet) {
|
|
||||||
throw new Exception("Projekt nicht gewählt");
|
|
||||||
}
|
|
||||||
|
|
||||||
//Keine Beschreibung
|
|
||||||
if (string.IsNullOrEmpty(item.Description) && item.FreistellungAktiv == null) {
|
|
||||||
throw new Exception("Keine Beschreibung");
|
|
||||||
}
|
|
||||||
|
|
||||||
//Keine Beschreibung
|
|
||||||
if (string.IsNullOrEmpty(item.Description)) {
|
|
||||||
item.Description = item.FreistellungAktiv.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
using (HttpClient client = new HttpClient() { Timeout = TimeSpan.FromSeconds(15) }) {
|
|
||||||
//HttpClient client = new HttpClient();
|
|
||||||
client.DefaultRequestHeaders.Add("Accept", "application/json");
|
|
||||||
client.DefaultRequestHeaders.Authorization =
|
|
||||||
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
|
||||||
|
|
||||||
//string json = JsonSerializer.Serialize<DayTime>(item);
|
|
||||||
string json = JsonConvert.SerializeObject(item);
|
|
||||||
StringContent content = new StringContent(json, Encoding.UTF8, "application/json");
|
|
||||||
|
|
||||||
HttpResponseMessage? response = null;
|
|
||||||
if (isNewItem)
|
|
||||||
response = await client.PostAsync(url, content);
|
|
||||||
else
|
|
||||||
response = await client.PutAsync(url, content);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode) {
|
|
||||||
throw new Exception("Fehler beim Speichern " + response.Content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stundeneintrag löschen
|
|
||||||
/// </summary>
|
|
||||||
internal static async Task DeleteItemAsync(string url, string token) {
|
|
||||||
using (HttpClient client = new HttpClient() { Timeout = TimeSpan.FromSeconds(15) }) {
|
|
||||||
//HttpClient client = new HttpClient();
|
|
||||||
client.DefaultRequestHeaders.Add("Accept", "application/json");
|
|
||||||
client.DefaultRequestHeaders.Authorization =
|
|
||||||
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
|
||||||
|
|
||||||
HttpResponseMessage response = await client.DeleteAsync(url);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
throw new Exception("Fehler beim Löschen " + response.Content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using Jugenddienst_Stunden.Types;
|
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.Models;
|
|
||||||
|
|
||||||
internal static class GlobalVar {
|
|
||||||
public static string ApiKey {
|
|
||||||
get => Preferences.Default.Get("apiKey", "");
|
|
||||||
set => Preferences.Default.Set("apiKey", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int EmployeeId {
|
|
||||||
get => Preferences.Default.Get("EmployeeId", 0);
|
|
||||||
set => Preferences.Default.Set("EmployeeId", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string Name {
|
|
||||||
get => Preferences.Default.Get("name", "Nicht");
|
|
||||||
set => Preferences.Default.Set("name", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string Surname {
|
|
||||||
get => Preferences.Default.Get("surname", "Eingeloggt");
|
|
||||||
set => Preferences.Default.Set("surname", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string ApiUrl {
|
|
||||||
get => Preferences.Default.Get("apiUrl", "");
|
|
||||||
set => Preferences.Default.Set("apiUrl", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Settings Settings { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
using Jugenddienst_Stunden.Types;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.Models;
|
|
||||||
|
|
||||||
internal static class HoursBase {
|
|
||||||
/// <summary>
|
|
||||||
/// Laden ... what can be: "settings", "hours", date="YYYY-MM-DD", id=<int/>
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Entire response</returns>
|
|
||||||
internal static async Task<BaseResponse> LoadBase(string what) {
|
|
||||||
string data = await BaseFunc.GetApiDataWithAuthAsync(GlobalVar.ApiUrl + "?" + what, GlobalVar.ApiKey);
|
|
||||||
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(data) ??
|
|
||||||
throw new Exception("Fehler beim Deserialisieren der Daten");
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Einstellungen laden
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Settings only</returns>
|
|
||||||
internal static async Task<Settings> LoadSettings() {
|
|
||||||
string data = await BaseFunc.GetApiDataWithAuthAsync(GlobalVar.ApiUrl + "?settings", GlobalVar.ApiKey);
|
|
||||||
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(data) ??
|
|
||||||
throw new Exception("Fehler beim Deserialisieren der Daten");
|
|
||||||
return res.settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Daten laden
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Hours only</returns>
|
|
||||||
internal static async Task<Hours> LoadData() {
|
|
||||||
string data = await BaseFunc.GetApiDataWithAuthAsync(GlobalVar.ApiUrl + "?hours", GlobalVar.ApiKey);
|
|
||||||
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(data) ??
|
|
||||||
throw new Exception("Fehler beim Deserialisieren der Daten");
|
|
||||||
return res.hour;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Benutzerdaten laden
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>User-Object</returns>
|
|
||||||
public static async Task<User> LoadUser(string apiKey) {
|
|
||||||
string data = await BaseFunc.GetApiDataWithAuthAsync(GlobalVar.ApiUrl, apiKey);
|
|
||||||
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(data) ??
|
|
||||||
throw new Exception("Fehler beim Deserialisieren der Daten");
|
|
||||||
return res.user;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Zeiten eines Tages holen
|
|
||||||
/// </summary>
|
|
||||||
internal static async Task<List<DayTime>> LoadDay(DateTime date) {
|
|
||||||
string data = await BaseFunc.GetApiDataWithAuthAsync(GlobalVar.ApiUrl + "?date=" + date.ToString("yyyy-MM-dd"),
|
|
||||||
GlobalVar.ApiKey);
|
|
||||||
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(data) ??
|
|
||||||
throw new Exception("Fehler beim Deserialisieren der Daten");
|
|
||||||
return res.daytimes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Einzelnen Stundeneintrag holen
|
|
||||||
/// </summary>
|
|
||||||
internal static async Task<DayTime> LoadEntry(int id) {
|
|
||||||
string data = await BaseFunc.GetApiDataWithAuthAsync(GlobalVar.ApiUrl + "?id=" + id, GlobalVar.ApiKey);
|
|
||||||
BaseResponse res = JsonConvert.DeserializeObject<BaseResponse>(data) ??
|
|
||||||
throw new Exception("Fehler beim Deserialisieren der Daten");
|
|
||||||
res.daytime.TimeSpanVon = res.daytime.Begin.ToTimeSpan();
|
|
||||||
res.daytime.TimeSpanBis = res.daytime.End.ToTimeSpan();
|
|
||||||
return res.daytime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Eintrag speichern
|
|
||||||
/// </summary>
|
|
||||||
internal static async Task<DayTime> SaveEntry(DayTime stunde) {
|
|
||||||
//, string begin, string end, string freistellung, string bemerkung) {
|
|
||||||
bool isNew = false;
|
|
||||||
if (stunde.Id == null)
|
|
||||||
isNew = true;
|
|
||||||
await BaseFunc.SaveItemAsync(GlobalVar.ApiUrl, GlobalVar.ApiKey, stunde, isNew);
|
|
||||||
|
|
||||||
return stunde;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Eintrag löschen
|
|
||||||
/// </summary>
|
|
||||||
internal static async Task DeleteEntry(DayTime stunde) {
|
|
||||||
await BaseFunc.DeleteItemAsync(GlobalVar.ApiUrl + "/entry/" + stunde.Id, GlobalVar.ApiKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -62,7 +62,7 @@ internal class HoursRepository : IHoursRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Task DeleteEntry(DayTime stunde)
|
public Task DeleteEntry(DayTime stunde)
|
||||||
=> _api.DeleteAsync($"/entry/{stunde.Id}");
|
=> _api.DeleteAsync($"entry/{stunde.Id}");
|
||||||
|
|
||||||
private static Dictionary<string, string?> QueryToDictionary(string query) {
|
private static Dictionary<string, string?> QueryToDictionary(string query) {
|
||||||
var dict = new Dictionary<string, string?>();
|
var dict = new Dictionary<string, string?>();
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ namespace Jugenddienst_Stunden.ViewModels;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class LoginViewModel : ObservableObject {
|
public partial class LoginViewModel : ObservableObject {
|
||||||
private readonly IAuthService _auth;
|
private readonly IAuthService _auth;
|
||||||
private readonly IAppSettings _settings;
|
|
||||||
private readonly IAlertService? _alerts;
|
private readonly IAlertService? _alerts;
|
||||||
private DateTime _lastDetectionTime = DateTime.MinValue;
|
private DateTime _lastDetectionTime = DateTime.MinValue;
|
||||||
private readonly TimeSpan _detectionInterval = TimeSpan.FromSeconds(5);
|
private readonly TimeSpan _detectionInterval = TimeSpan.FromSeconds(5);
|
||||||
@@ -59,9 +58,8 @@ public partial class LoginViewModel : ObservableObject {
|
|||||||
// Explizite Command-Property für den QR-Scanner-Event, damit das Binding in XAML zuverlässig greift
|
// Explizite Command-Property für den QR-Scanner-Event, damit das Binding in XAML zuverlässig greift
|
||||||
public IAsyncRelayCommand<object?> QrDetectedCommand { get; }
|
public IAsyncRelayCommand<object?> QrDetectedCommand { get; }
|
||||||
|
|
||||||
public LoginViewModel(IAuthService auth, IAppSettings settings) {
|
public LoginViewModel(IAuthService auth) {
|
||||||
_auth = auth;
|
_auth = auth;
|
||||||
_settings = settings;
|
|
||||||
|
|
||||||
// gespeicherte Präferenz für Logintyp laden
|
// gespeicherte Präferenz für Logintyp laden
|
||||||
var lt = Preferences.Default.Get("logintype", "qr");
|
var lt = Preferences.Default.Get("logintype", "qr");
|
||||||
@@ -81,7 +79,7 @@ public partial class LoginViewModel : ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DI-Konstruktor: AlertService anbinden und Alerts an VM-Event weiterreichen (analog StundeViewModel)
|
// DI-Konstruktor: AlertService anbinden und Alerts an VM-Event weiterreichen (analog StundeViewModel)
|
||||||
internal LoginViewModel(IAuthService auth, IAppSettings settings, IAlertService alertService) : this(auth, settings) {
|
internal LoginViewModel(IAuthService auth, IAlertService alertService) : this(auth) {
|
||||||
_alerts = alertService;
|
_alerts = alertService;
|
||||||
if (alertService is not null) {
|
if (alertService is not null) {
|
||||||
alertService.AlertRaised += (s, msg) => AlertEvent?.Invoke(this, msg);
|
alertService.AlertRaised += (s, msg) => AlertEvent?.Invoke(this, msg);
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ namespace Jugenddienst_Stunden.ViewModels;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class StundeViewModel : ObservableObject, IQueryAttributable {
|
public partial class StundeViewModel : ObservableObject, IQueryAttributable {
|
||||||
private readonly IHoursService _hoursService;
|
private readonly IHoursService _hoursService;
|
||||||
|
private readonly IAppSettings _settings;
|
||||||
|
private readonly IAlertService _alertService;
|
||||||
|
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Title { get; set; } = "Eintrag bearbeiten";
|
public string Title { get; set; } = "Eintrag bearbeiten";
|
||||||
public string SubTitle { get; set; } = DateTime.Today.ToString("dddd, d. MMMM yyyy");
|
public string SubTitle { get; set; } = DateTime.Today.ToString("dddd, d. MMMM yyyy");
|
||||||
|
|
||||||
//private HoursBase HoursBase = new HoursBase();
|
|
||||||
internal Settings Settings = new Settings();
|
|
||||||
|
|
||||||
public event EventHandler<string> AlertEvent;
|
public event EventHandler<string> AlertEvent;
|
||||||
public event EventHandler<string> InfoEvent;
|
public event EventHandler<string> InfoEvent;
|
||||||
@@ -77,36 +77,19 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
|
|||||||
//public ICommand LoadDataCommand { get; private set; }
|
//public ICommand LoadDataCommand { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
public StundeViewModel(IHoursService hoursService, IAlertService alertService) {
|
public StundeViewModel(IHoursService hoursService, IAlertService alertService, IAppSettings settings) {
|
||||||
_hoursService = hoursService;
|
_hoursService = hoursService;
|
||||||
|
_settings = settings;
|
||||||
|
_alertService = alertService;
|
||||||
SaveCommand = new AsyncRelayCommand(Save);
|
SaveCommand = new AsyncRelayCommand(Save);
|
||||||
DeleteConfirmCommand = new Command(async () => await DeleteConfirm());
|
DeleteConfirmCommand = new Command(async () => await DeleteConfirm());
|
||||||
|
|
||||||
if (alertService is not null) {
|
_alertService.AlertRaised += (s, msg) => AlertEvent?.Invoke(this, msg);
|
||||||
alertService.AlertRaised += (s, msg) => AlertEvent?.Invoke(this, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
//LoadSettingsAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void LoadSettingsAsync() {
|
|
||||||
try {
|
|
||||||
Settings = await _hoursService.GetSettingsAsync();
|
|
||||||
GlobalVar.Settings = Settings;
|
|
||||||
|
|
||||||
OptionsGemeinde = Settings.Gemeinden;
|
private void UpdateSettings(Settings settings) {
|
||||||
OptionsProjekt = Settings.Projekte;
|
_settings.Settings = settings;
|
||||||
OptionsFreistellung = Settings.Freistellungen;
|
|
||||||
|
|
||||||
GemeindeAktivSet = Settings.GemeindeAktivSet;
|
|
||||||
ProjektAktivSet = Settings.ProjektAktivSet;
|
|
||||||
} catch (Exception e) {
|
|
||||||
AlertEvent?.Invoke(this, e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void UpdateSettingsAsync(Settings settings) {
|
|
||||||
GlobalVar.Settings = settings;
|
|
||||||
OptionsGemeinde = settings.Gemeinden;
|
OptionsGemeinde = settings.Gemeinden;
|
||||||
OptionsProjekt = settings.Projekte;
|
OptionsProjekt = settings.Projekte;
|
||||||
OptionsFreistellung = settings.Freistellungen;
|
OptionsFreistellung = settings.Freistellungen;
|
||||||
@@ -126,7 +109,7 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Projekt ist ein Pflichtfeld
|
//Projekt ist ein Pflichtfeld
|
||||||
if (Settings.ProjektAktivSet) {
|
if (_settings.Settings.ProjektAktivSet) {
|
||||||
var projektId = DayTime.ProjektAktiv?.Id ?? 0;
|
var projektId = DayTime.ProjektAktiv?.Id ?? 0;
|
||||||
if (projektId == 0) {
|
if (projektId == 0) {
|
||||||
proceed = false;
|
proceed = false;
|
||||||
@@ -135,7 +118,7 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Gemeinde ist ein Pflichtfeld
|
//Gemeinde ist ein Pflichtfeld
|
||||||
if (Settings.GemeindeAktivSet) {
|
if (_settings.Settings.GemeindeAktivSet) {
|
||||||
var gemeindeId = DayTime.GemeindeAktiv?.Id ?? 0;
|
var gemeindeId = DayTime.GemeindeAktiv?.Id ?? 0;
|
||||||
if (gemeindeId == 0) {
|
if (gemeindeId == 0) {
|
||||||
proceed = false;
|
proceed = false;
|
||||||
@@ -200,17 +183,18 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
|
|||||||
//DateTime heute = DateTime.Now;
|
//DateTime heute = DateTime.Now;
|
||||||
try {
|
try {
|
||||||
//var entry = await _hoursService.GetEntryAsync(Convert.ToInt32(query["load"]));
|
//var entry = await _hoursService.GetEntryAsync(Convert.ToInt32(query["load"]));
|
||||||
var (entry, settings, daytimes) = await _hoursService.GetEntryWithSettingsAsync(Convert.ToInt32(query["load"]));
|
var (entry, settings, daytimes) =
|
||||||
UpdateSettingsAsync(settings);
|
await _hoursService.GetEntryWithSettingsAsync(Convert.ToInt32(query["load"]));
|
||||||
|
UpdateSettings(settings);
|
||||||
|
|
||||||
DayTime = entry;
|
DayTime = entry;
|
||||||
DayTime.TimeSpanVon = entry.Begin.ToTimeSpan();
|
DayTime.TimeSpanVon = entry.Begin.ToTimeSpan();
|
||||||
DayTime.TimeSpanBis = entry.End.ToTimeSpan();
|
DayTime.TimeSpanBis = entry.End.ToTimeSpan();
|
||||||
|
|
||||||
DayTime.GemeindeAktiv = OptionsGemeinde.FirstOrDefault(Gemeinde => Gemeinde.Id == DayTime.Gemeinde) ??
|
DayTime.GemeindeAktiv = OptionsGemeinde.FirstOrDefault(Gemeinde => Gemeinde.Id == DayTime.Gemeinde) ??
|
||||||
new Gemeinde();
|
new Gemeinde();
|
||||||
DayTime.ProjektAktiv = OptionsProjekt.FirstOrDefault(Projekt => Projekt.Id == DayTime.Projekt) ??
|
DayTime.ProjektAktiv = OptionsProjekt.FirstOrDefault(Projekt => Projekt.Id == DayTime.Projekt) ??
|
||||||
new Projekt();
|
new Projekt();
|
||||||
DayTime.FreistellungAktiv =
|
DayTime.FreistellungAktiv =
|
||||||
OptionsFreistellung.FirstOrDefault(Freistellung => Freistellung.Id == DayTime.Free) ??
|
OptionsFreistellung.FirstOrDefault(Freistellung => Freistellung.Id == DayTime.Free) ??
|
||||||
new Freistellung();
|
new Freistellung();
|
||||||
@@ -229,11 +213,6 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
|
|||||||
OnPropertyChanged(nameof(DayTimes));
|
OnPropertyChanged(nameof(DayTimes));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
AlertEvent?.Invoke(this, e.Message);
|
AlertEvent?.Invoke(this, e.Message);
|
||||||
} finally {
|
|
||||||
//Evtl. noch die anderen Zeiten des gleichen Tages holen
|
|
||||||
//var day = await _hoursService.GetDayWithSettingsAsync(DayTime.Day);
|
|
||||||
//DayTimes = day.dayTimes;
|
|
||||||
//OnPropertyChanged(nameof(DayTimes));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//OnPropertyChanged(nameof(DayTime));
|
//OnPropertyChanged(nameof(DayTime));
|
||||||
@@ -246,7 +225,7 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
|
|||||||
//Bei neuem Eintrag die vorhandenen des gleichen Tages anzeigen
|
//Bei neuem Eintrag die vorhandenen des gleichen Tages anzeigen
|
||||||
try {
|
try {
|
||||||
var (list, settings) = await _hoursService.GetDayWithSettingsAsync(_date);
|
var (list, settings) = await _hoursService.GetDayWithSettingsAsync(_date);
|
||||||
UpdateSettingsAsync(settings);
|
UpdateSettings(settings);
|
||||||
DayTimes = list;
|
DayTimes = list;
|
||||||
OnPropertyChanged(nameof(DayTimes));
|
OnPropertyChanged(nameof(DayTimes));
|
||||||
} catch (Exception) {
|
} catch (Exception) {
|
||||||
@@ -257,7 +236,7 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
|
|||||||
} finally {
|
} finally {
|
||||||
DayTime = new DayTime();
|
DayTime = new DayTime();
|
||||||
DayTime.Day = _date;
|
DayTime.Day = _date;
|
||||||
DayTime.EmployeeId = GlobalVar.EmployeeId;
|
DayTime.EmployeeId = _settings.EmployeeId;
|
||||||
DayTime.GemeindeAktiv = new Gemeinde();
|
DayTime.GemeindeAktiv = new Gemeinde();
|
||||||
DayTime.ProjektAktiv = new Projekt();
|
DayTime.ProjektAktiv = new Projekt();
|
||||||
DayTime.FreistellungAktiv = new Freistellung();
|
DayTime.FreistellungAktiv = new Freistellung();
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ namespace Jugenddienst_Stunden.ViewModels;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class StundenViewModel : ObservableObject, IQueryAttributable, INotifyPropertyChanged {
|
public partial class StundenViewModel : ObservableObject, IQueryAttributable, INotifyPropertyChanged {
|
||||||
private readonly IHoursService _hoursService;
|
private readonly IHoursService _hoursService;
|
||||||
|
private readonly IAppSettings _settings;
|
||||||
|
|
||||||
public ICommand NewEntryCommand { get; }
|
public ICommand NewEntryCommand { get; }
|
||||||
public ICommand SelectEntryCommand { get; }
|
public ICommand SelectEntryCommand { get; }
|
||||||
@@ -50,7 +51,13 @@ public partial class StundenViewModel : ObservableObject, IQueryAttributable, IN
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[ObservableProperty] private List<DayTime> dayTimes = new List<DayTime>();
|
[ObservableProperty] private List<DayTime> dayTimes = new List<DayTime>();
|
||||||
|
|
||||||
public string Title { get; set; } = GlobalVar.Name + " " + GlobalVar.Surname;
|
/// <summary>
|
||||||
|
/// Der Titel der Stundenübersicht ist der aktuelle Benutzername
|
||||||
|
/// </summary>
|
||||||
|
public string Title {
|
||||||
|
get => _settings.Name + " " + _settings.Surname;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
[ObservableProperty] private Hours hours;
|
[ObservableProperty] private Hours hours;
|
||||||
|
|
||||||
@@ -82,14 +89,10 @@ public partial class StundenViewModel : ObservableObject, IQueryAttributable, IN
|
|||||||
LoadOverview = "Lade Summen für " + dateToday.ToString("MMMM yy");
|
LoadOverview = "Lade Summen für " + dateToday.ToString("MMMM yy");
|
||||||
// Task.Run(() => LoadDay(value));
|
// Task.Run(() => LoadDay(value));
|
||||||
// NICHT Task.Run: LoadDay aktualisiert UI-gebundene Properties
|
// NICHT Task.Run: LoadDay aktualisiert UI-gebundene Properties
|
||||||
MainThread.BeginInvokeOnMainThread(async () =>
|
MainThread.BeginInvokeOnMainThread(async () => {
|
||||||
{
|
try {
|
||||||
try
|
|
||||||
{
|
|
||||||
await LoadDay(dateToday);
|
await LoadDay(dateToday);
|
||||||
}
|
} catch (Exception ex) {
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
AlertEvent?.Invoke(this, ex.Message);
|
AlertEvent?.Invoke(this, ex.Message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -162,8 +165,9 @@ public partial class StundenViewModel : ObservableObject, IQueryAttributable, IN
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// CTOR (DI)
|
/// CTOR (DI)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StundenViewModel(IHoursService hoursService) {
|
public StundenViewModel(IHoursService hoursService, IAppSettings appSettings) {
|
||||||
_hoursService = hoursService;
|
_hoursService = hoursService;
|
||||||
|
_settings = appSettings;
|
||||||
Hours = new Hours();
|
Hours = new Hours();
|
||||||
|
|
||||||
LoadOverview = "Lade Summen für " + DateToday.ToString("MMMM");
|
LoadOverview = "Lade Summen für " + DateToday.ToString("MMMM");
|
||||||
@@ -177,14 +181,10 @@ public partial class StundenViewModel : ObservableObject, IQueryAttributable, IN
|
|||||||
// Task task = LoadDay(DateTime.Today);
|
// Task task = LoadDay(DateTime.Today);
|
||||||
// Beim Startup NICHT direkt im CTOR laden (kann Startup/Navigation blockieren)
|
// Beim Startup NICHT direkt im CTOR laden (kann Startup/Navigation blockieren)
|
||||||
// Stattdessen via Dispatcher "nach" dem Aufbau starten:
|
// Stattdessen via Dispatcher "nach" dem Aufbau starten:
|
||||||
MainThread.BeginInvokeOnMainThread(async () =>
|
MainThread.BeginInvokeOnMainThread(async () => {
|
||||||
{
|
try {
|
||||||
try
|
|
||||||
{
|
|
||||||
await LoadDay(DateTime.Today);
|
await LoadDay(DateTime.Today);
|
||||||
}
|
} catch (Exception ex) {
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
AlertEvent?.Invoke(this, ex.Message);
|
AlertEvent?.Invoke(this, ex.Message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -244,16 +244,14 @@ public partial class StundenViewModel : ObservableObject, IQueryAttributable, IN
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task LoadDay(DateTime date) {
|
public async Task LoadDay(DateTime date) {
|
||||||
// kleine Initialwerte sind ok, aber UI-Thread sicher setzen:
|
// kleine Initialwerte sind ok, aber UI-Thread sicher setzen:
|
||||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
await MainThread.InvokeOnMainThreadAsync(() => {
|
||||||
{
|
|
||||||
DayTotal = new TimeOnly(0);
|
DayTotal = new TimeOnly(0);
|
||||||
Sollstunden = new TimeOnly(0);
|
Sollstunden = new TimeOnly(0);
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
var (dayTimes, settings) = await _hoursService.GetDayWithSettingsAsync(date);
|
var (dayTimes, settings) = await _hoursService.GetDayWithSettingsAsync(date);
|
||||||
|
|
||||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
await MainThread.InvokeOnMainThreadAsync(() => {
|
||||||
{
|
|
||||||
DayTimes = dayTimes;
|
DayTimes = dayTimes;
|
||||||
Settings = settings;
|
Settings = settings;
|
||||||
GemeindeAktivSet = Settings.GemeindeAktivSet;
|
GemeindeAktivSet = Settings.GemeindeAktivSet;
|
||||||
@@ -275,8 +273,7 @@ public partial class StundenViewModel : ObservableObject, IQueryAttributable, IN
|
|||||||
}
|
}
|
||||||
|
|
||||||
_soll = Settings.Nominal.Where(w => w.Timetable == dt.TimeTable && w.Wochentag == dt.Wday).ToList();
|
_soll = Settings.Nominal.Where(w => w.Timetable == dt.TimeTable && w.Wochentag == dt.Wday).ToList();
|
||||||
if (_soll.Count > 0)
|
if (_soll.Count > 0) {
|
||||||
{
|
|
||||||
var soll = TimeOnly.FromTimeSpan(TimeSpan.FromHours(_soll[0].Zeit));
|
var soll = TimeOnly.FromTimeSpan(TimeSpan.FromHours(_soll[0].Zeit));
|
||||||
await MainThread.InvokeOnMainThreadAsync(() => Sollstunden = soll);
|
await MainThread.InvokeOnMainThreadAsync(() => Sollstunden = soll);
|
||||||
}
|
}
|
||||||
@@ -288,8 +285,7 @@ public partial class StundenViewModel : ObservableObject, IQueryAttributable, IN
|
|||||||
//Nach der Tagessumme die anderen Tage anhängen
|
//Nach der Tagessumme die anderen Tage anhängen
|
||||||
if (DayTimes != null) {
|
if (DayTimes != null) {
|
||||||
var more = await _hoursService.GetDayRangeAsync(date.AddDays(1), date.AddDays(3));
|
var more = await _hoursService.GetDayRangeAsync(date.AddDays(1), date.AddDays(3));
|
||||||
if (more != null && more.Count > 0)
|
if (more != null && more.Count > 0) {
|
||||||
{
|
|
||||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||||
DayTimes = DayTimes.Concat(more).ToList()
|
DayTimes = DayTimes.Concat(more).ToList()
|
||||||
);
|
);
|
||||||
@@ -297,8 +293,7 @@ public partial class StundenViewModel : ObservableObject, IQueryAttributable, IN
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
||||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
await MainThread.InvokeOnMainThreadAsync(() => {
|
||||||
{
|
|
||||||
DayTimes = new List<DayTime>();
|
DayTimes = new List<DayTime>();
|
||||||
//TODO: hier könnte auch ein Fehler kommen, dann wäre InfoEvent falsch.
|
//TODO: hier könnte auch ein Fehler kommen, dann wäre InfoEvent falsch.
|
||||||
|
|
||||||
@@ -314,8 +309,7 @@ public partial class StundenViewModel : ObservableObject, IQueryAttributable, IN
|
|||||||
|
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
await MainThread.InvokeOnMainThreadAsync(() => {
|
||||||
{
|
|
||||||
OnPropertyChanged(nameof(DayTotal));
|
OnPropertyChanged(nameof(DayTotal));
|
||||||
OnPropertyChanged(nameof(Sollstunden));
|
OnPropertyChanged(nameof(Sollstunden));
|
||||||
OnPropertyChanged(nameof(DateToday));
|
OnPropertyChanged(nameof(DateToday));
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Jugenddienst_Stunden.Interfaces;
|
||||||
using Jugenddienst_Stunden.Models;
|
using Jugenddienst_Stunden.Models;
|
||||||
using Jugenddienst_Stunden.Types;
|
using Jugenddienst_Stunden.Types;
|
||||||
using Jugenddienst_Stunden.ViewModels;
|
using Jugenddienst_Stunden.ViewModels;
|
||||||
@@ -10,9 +11,6 @@ namespace Jugenddienst_Stunden.Views;
|
|||||||
/// Die Loginseite mit dem Barcodescanner
|
/// Die Loginseite mit dem Barcodescanner
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class LoginPage : ContentPage {
|
public partial class LoginPage : ContentPage {
|
||||||
private DateTime _lastDetectionTime;
|
|
||||||
private readonly TimeSpan _detectionInterval = TimeSpan.FromSeconds(5);
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CTOR
|
/// CTOR
|
||||||
@@ -24,7 +22,7 @@ public partial class LoginPage : ContentPage {
|
|||||||
try {
|
try {
|
||||||
if (BindingContext is null) {
|
if (BindingContext is null) {
|
||||||
var sp = Application.Current?.Handler?.MauiContext?.Services
|
var sp = Application.Current?.Handler?.MauiContext?.Services
|
||||||
?? throw new InvalidOperationException("DI container ist nicht verfügbar.");
|
?? throw new InvalidOperationException("DI container ist nicht verfügbar.");
|
||||||
BindingContext = sp.GetRequiredService<LoginViewModel>();
|
BindingContext = sp.GetRequiredService<LoginViewModel>();
|
||||||
}
|
}
|
||||||
} catch (Exception) {
|
} catch (Exception) {
|
||||||
@@ -59,69 +57,9 @@ public partial class LoginPage : ContentPage {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//if (BindingContext is LoginViewModel vm) {
|
|
||||||
// vm.AlertEvent += Vm_AlertEvent;
|
|
||||||
// vm.InfoEvent += Vm_InfoEvent;
|
|
||||||
// vm.MsgEvent += Vm_MsgEvent;
|
|
||||||
//}
|
|
||||||
|
|
||||||
//Zwischen manuellem und automatischem Login (mit QR-Code) umschalten und die Schalterstellung merken
|
|
||||||
// MVVM übernimmt Umschalten über IsManualMode im ViewModel; keine Code-Behind-Umschaltung mehr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Nach der Erkennung des Barcodes wird der Benutzer eingeloggt
|
|
||||||
/// ZXing.Net.Maui.Controls 0.4.4
|
|
||||||
/// </summary>
|
|
||||||
private void BarcodesDetected(object sender, BarcodeDetectionEventArgs e) {
|
|
||||||
var currentTime = DateTime.Now;
|
|
||||||
if ((currentTime - _lastDetectionTime) > _detectionInterval) {
|
|
||||||
_lastDetectionTime = currentTime;
|
|
||||||
foreach (var barcode in e.Results) {
|
|
||||||
if (GlobalVar.ApiKey != barcode.Value) {
|
|
||||||
_ = MainThread.InvokeOnMainThreadAsync(async () => {
|
|
||||||
//await DisplayAlert("Barcode erkannt", $"Barcode: {barcode.Format} - {barcode.Value}", "OK");
|
|
||||||
|
|
||||||
try {
|
|
||||||
var tokendata = new TokenData(barcode.Value);
|
|
||||||
GlobalVar.ApiUrl = tokendata.Url;
|
|
||||||
User user = await HoursBase.LoadUser(barcode.Value);
|
|
||||||
|
|
||||||
GlobalVar.ApiKey = barcode.Value;
|
|
||||||
GlobalVar.Name = user.Name;
|
|
||||||
GlobalVar.Surname = user.Surname;
|
|
||||||
GlobalVar.EmployeeId = user.Id;
|
|
||||||
|
|
||||||
Title = user.Name + " " + user.Surname;
|
|
||||||
//Auf der Loginseite wird der Server als Info ohne Protokoll und ohne /appapi angezeigt
|
|
||||||
ServerLabel.Text = "Server: " + tokendata.Url.Replace("/appapi", "").Replace("https://", "")
|
|
||||||
.Replace("http://", "");
|
|
||||||
|
|
||||||
|
|
||||||
await DisplayAlert("Login erfolgreich", user.Name + " " + user.Surname, "OK");
|
|
||||||
if (Navigation.NavigationStack.Count > 1) {
|
|
||||||
//Beim ersten Start ohne Login, wird man automatisch auf die Loginseite geleitet. Danach in der History zur<75>ck
|
|
||||||
await Navigation.PopAsync();
|
|
||||||
} else {
|
|
||||||
//Beim manuellen Wechsel auf die Loginseite leiten wir nach erfolgreichem Login auf die Stunden<65>bersicht
|
|
||||||
await Shell.Current.GoToAsync($"//StundenPage");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
await DisplayAlert("Fehler", e.Message, "OK");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
MainThread.InvokeOnMainThreadAsync(() => {
|
|
||||||
DisplayAlert("Bereits eingeloggt",
|
|
||||||
Preferences.Default.Get("name", "") + " " + Preferences.Default.Get("surname", ""),
|
|
||||||
"OK");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnDisappearing() {
|
protected override void OnDisappearing() {
|
||||||
base.OnDisappearing();
|
base.OnDisappearing();
|
||||||
|
|
||||||
@@ -136,78 +74,6 @@ public partial class LoginPage : ContentPage {
|
|||||||
barcodeScannerView.CameraLocation = CameraLocation.Rear;
|
barcodeScannerView.CameraLocation = CameraLocation.Rear;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsCameraAvailable() {
|
|
||||||
var status = Permissions.CheckStatusAsync<Permissions.Camera>().Result;
|
|
||||||
if (status != PermissionStatus.Granted) {
|
|
||||||
status = Permissions.RequestAsync<Permissions.Camera>().Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return status != PermissionStatus.Granted;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnLoginButtonClicked(object sender, EventArgs e) {
|
|
||||||
var username = UsernameEntry.Text;
|
|
||||||
var password = PasswordEntry.Text;
|
|
||||||
var server = ServerEntry.Text;
|
|
||||||
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(server)) {
|
|
||||||
await DisplayAlert("Fehler", "Bitte alle Felder ausf<73>llen", "OK");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Uri uri = new Uri(InputUrlWithSchema(server));
|
|
||||||
|
|
||||||
Types.User response =
|
|
||||||
await BaseFunc.AuthUserPass(username, password, uri.Scheme + "://" + uri.Authority + "/appapi");
|
|
||||||
|
|
||||||
GlobalVar.ApiKey = response.Token;
|
|
||||||
GlobalVar.Name = response.Name;
|
|
||||||
GlobalVar.Surname = response.Surname;
|
|
||||||
GlobalVar.EmployeeId = response.Id;
|
|
||||||
GlobalVar.ApiUrl = uri.Scheme + "://" + uri.Authority + "/appapi";
|
|
||||||
|
|
||||||
Title = response.Name + " " + response.Surname;
|
|
||||||
//ServerLabel.Text = "Server: " + server.Replace("/appapi", "").Replace("https://", "").Replace("http://", "");
|
|
||||||
ServerLabel.Text = "Server: " + uri.Authority;
|
|
||||||
|
|
||||||
await DisplayAlert("Login erfolgreich", response.Name + " " + response.Surname, "OK");
|
|
||||||
if (Navigation.NavigationStack.Count > 1)
|
|
||||||
await Navigation.PopAsync();
|
|
||||||
else {
|
|
||||||
await Shell.Current.GoToAsync($"//StundenPage");
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
await DisplayAlert("Fehler", ex.Message, "OK");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Aus einer URL ohne Schema eine URL mit Schema machen
|
|
||||||
/// </summary>
|
|
||||||
private static string InputUrlWithSchema(string url) {
|
|
||||||
if (!url.StartsWith("http://") && !url.StartsWith("https://")) {
|
|
||||||
url = "https://" + url;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url.StartsWith("http://")) {
|
|
||||||
url = url.Replace("http://", "https://");
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Zwischen manuellem und automatischem Login (mit QR-Code) umschalten und die Schalterstellung merken
|
|
||||||
// Umschalt-Logik erfolgt über Binding an IsManualMode im ViewModel
|
|
||||||
|
|
||||||
//private void Vm_AlertEvent(object? sender, string e) {
|
|
||||||
// DisplayAlert("Fehler:", e, "OK");
|
|
||||||
//}
|
|
||||||
//private void Vm_InfoEvent(object? sender, string e) {
|
|
||||||
// DisplayAlert("Information:", e, "OK");
|
|
||||||
//}
|
|
||||||
//private async Task Vm_MsgEvent(string title, string message) {
|
|
||||||
// await DisplayAlert(title, message, "OK");
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
@@ -28,24 +28,13 @@ public partial class StundenPage : ContentPage {
|
|||||||
vm.AlertEvent += Vm_AlertEvent;
|
vm.AlertEvent += Vm_AlertEvent;
|
||||||
vm.InfoEvent += Vm_InfoEvent;
|
vm.InfoEvent += Vm_InfoEvent;
|
||||||
|
|
||||||
// Navigation NICHT im CTOR ausführen (Shell/Navigation-Stack ist hier oft noch nicht ?ready?)
|
|
||||||
// if (!CheckLogin()) {
|
|
||||||
// NavigateToTargetPage();
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Vm_AlertEvent(object? sender, string e) {
|
private void Vm_AlertEvent(object? sender, string e) {
|
||||||
MainThread.BeginInvokeOnMainThread(async () => { await DisplayAlert("Fehler:", e, "OK"); });
|
MainThread.BeginInvokeOnMainThread(async () => { await DisplayAlert("Fehler:", e, "OK"); });
|
||||||
}
|
}
|
||||||
|
|
||||||
//private void Vm_InfoEvent(object? sender, string e) {
|
|
||||||
// DisplayAlert("Information:", e, "OK");
|
|
||||||
//}
|
|
||||||
//private void Vm_InfoEvent(object? sender, string e) {
|
|
||||||
// MainThread.BeginInvokeOnMainThread(async () => {
|
|
||||||
// await DisplayAlert("Information:", e, "OK");
|
|
||||||
// });
|
|
||||||
//}
|
|
||||||
private void Vm_InfoEvent(object? sender, string e) {
|
private void Vm_InfoEvent(object? sender, string e) {
|
||||||
MainThread.BeginInvokeOnMainThread(async () => {
|
MainThread.BeginInvokeOnMainThread(async () => {
|
||||||
CancellationTokenSource cts = new CancellationTokenSource();
|
CancellationTokenSource cts = new CancellationTokenSource();
|
||||||
@@ -76,9 +65,6 @@ public partial class StundenPage : ContentPage {
|
|||||||
return Preferences.Default.Get("apiKey", "") != "";
|
return Preferences.Default.Get("apiKey", "") != "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// private async void NavigateToTargetPage() {
|
|
||||||
// await Navigation.PushAsync(new LoginPage());
|
|
||||||
// }
|
|
||||||
|
|
||||||
private Task NavigateToTargetPage() {
|
private Task NavigateToTargetPage() {
|
||||||
// Shell-Navigation statt Navigation.PushAsync
|
// Shell-Navigation statt Navigation.PushAsync
|
||||||
|
|||||||
Reference in New Issue
Block a user