Compare commits
8 Commits
98d6d61f16
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ee0fc61f6 | |||
| c6fd58a290 | |||
| 656d39f43e | |||
| 15856d0dd0 | |||
| 8da8734065 | |||
| cd4eae34c3 | |||
| 52815d7e21 | |||
| 8d512963b5 |
@@ -73,10 +73,17 @@ internal sealed class ApiClient : IApiClient {
|
|||||||
using var res = await _http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false);
|
using var res = await _http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false);
|
||||||
var text = await res.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
|
var text = await res.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (res.StatusCode == System.Net.HttpStatusCode.NotFound) {
|
||||||
|
var message = req.Method + ": " + req.RequestUri + " nicht gefunden";
|
||||||
|
throw ApiException.From(res.StatusCode, message);
|
||||||
|
}
|
||||||
|
|
||||||
if (!res.IsSuccessStatusCode)
|
if (!res.IsSuccessStatusCode)
|
||||||
throw ApiException.From(res.StatusCode, TryGetMessage(text), text);
|
throw ApiException.From(res.StatusCode, TryGetMessage(text), text);
|
||||||
|
|
||||||
|
|
||||||
if (res.StatusCode != System.Net.HttpStatusCode.OK) {
|
if (res.StatusCode != System.Net.HttpStatusCode.OK) {
|
||||||
|
|
||||||
// Verhalten wie in BaseFunc: bei Fehlerstatus -> "message" aus Body lesen und mit dessen Inhalt eine Exception werfen.
|
// Verhalten wie in BaseFunc: bei Fehlerstatus -> "message" aus Body lesen und mit dessen Inhalt eine Exception werfen.
|
||||||
try {
|
try {
|
||||||
var options = new JsonDocumentOptions { AllowTrailingCommas = true };
|
var options = new JsonDocumentOptions { AllowTrailingCommas = true };
|
||||||
@@ -142,8 +149,27 @@ internal sealed class ApiClient : IApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
// return absoluteFromPath;
|
||||||
|
|
||||||
|
// Sonderfall: Wenn path ein absoluter file:// URI ist, diesen relativ zur Basis behandeln
|
||||||
|
// Weiß nicht wie file:// zustande kommt, vermutlich wäre das zu verhindern
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return new Uri(baseUri, relativePath);
|
return new Uri(baseUri, relativePath);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Jugenddienst_Stunden.Interfaces;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Infrastructure;
|
||||||
|
|
||||||
|
internal static class HttpClientRegistration {
|
||||||
|
/// <summary>
|
||||||
|
/// Registriert den ApiClient mit einem SocketsHttpHandler als prim<69>ren MessageHandler.
|
||||||
|
/// Vermeidet den Android-spezifischen Cast-Fehler in Xamarin.Android.Net.AndroidMessageHandler.
|
||||||
|
///</summary>
|
||||||
|
public static IServiceCollection AddApiHttpClient(this IServiceCollection services, ApiOptions options) {
|
||||||
|
if (services is null)
|
||||||
|
throw new ArgumentNullException(nameof(services));
|
||||||
|
if (options is null)
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
|
||||||
|
// ApiOptions als Singleton bereitstellen (kann nach Bedarf angepasst werden)
|
||||||
|
services.AddSingleton(options);
|
||||||
|
|
||||||
|
// HttpClient f<>r ApiClient registrieren und einen SocketsHttpHandler verwenden.
|
||||||
|
// SocketsHttpHandler vermeidet das problematische Casting, das bei AndroidMessageHandler
|
||||||
|
// zur InvalidCastException (URLConnectionInvoker -> HttpURLConnection) f<>hrt.
|
||||||
|
services.AddHttpClient<IApiClient, ApiClient>()
|
||||||
|
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler());
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.Interfaces;
|
namespace Jugenddienst_Stunden.Interfaces;
|
||||||
internal interface IAlertService {
|
public interface IAlertService {
|
||||||
event EventHandler<string> AlertRaised;
|
event EventHandler<string> AlertRaised;
|
||||||
void Raise(string message);
|
void Raise(string message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,4 +13,5 @@ public interface IHoursService {
|
|||||||
Task<DayTime> GetEntryAsync(int id);
|
Task<DayTime> GetEntryAsync(int id);
|
||||||
Task<DayTime> SaveEntryAsync(DayTime stunde);
|
Task<DayTime> SaveEntryAsync(DayTime stunde);
|
||||||
Task DeleteEntryAsync(DayTime stunde);
|
Task DeleteEntryAsync(DayTime stunde);
|
||||||
|
Task<(DayTime dayTime, Settings settings, List<DayTime> existingDayTimes)> GetEntryWithSettingsAsync(int id);
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- <TargetFrameworks>net8.0-maccatalyst;net9.0-android35.0</TargetFrameworks> -->
|
<!-- <TargetFrameworks>net8.0-maccatalyst;net9.0-android35.0</TargetFrameworks> -->
|
||||||
<TargetFrameworks>net9.0-android35.0</TargetFrameworks>
|
<TargetFrameworks>net10.0-android36.0</TargetFrameworks>
|
||||||
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
|
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
|
||||||
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
|
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
|
||||||
|
|
||||||
@@ -176,76 +176,76 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net9.0-windows10.0.26100.0</TargetFrameworks>
|
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net10.0-windows10.0.26100.0</TargetFrameworks>
|
||||||
<WindowsPackageType>None</WindowsPackageType>
|
<WindowsPackageType>MSIX</WindowsPackageType>
|
||||||
<!-- <TargetFrameworks>;net9.0-android35.0</TargetFrameworks> -->
|
<!-- <TargetFrameworks>;net9.0-android35.0</TargetFrameworks> -->
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- App Icon -->
|
<!-- App Icon -->
|
||||||
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4"/>
|
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
|
||||||
|
|
||||||
<!-- Splash Screen -->
|
<!-- Splash Screen -->
|
||||||
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#F7931D" BaseSize="128,128"/>
|
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#F7931D" BaseSize="128,128" />
|
||||||
|
|
||||||
<!-- Splash Screen (Windows fix) -->
|
<!-- Splash Screen (Windows fix) -->
|
||||||
<!--<MauiImage Include="Resources\Images\logo_splash_win.svg" Color="#F7931D" BaseSize="208,208" />-->
|
<!--<MauiImage Include="Resources\Images\logo_splash_win.svg" Color="#F7931D" BaseSize="208,208" />-->
|
||||||
|
|
||||||
<!-- Images -->
|
<!-- Images -->
|
||||||
<MauiImage Include="Resources\Images\*"/>
|
<MauiImage Include="Resources\Images\*" />
|
||||||
<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185"/>
|
<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185" />
|
||||||
|
|
||||||
<!-- Custom Fonts -->
|
<!-- Custom Fonts -->
|
||||||
<MauiFont Include="Resources\Fonts\*"/>
|
<MauiFont Include="Resources\Fonts\*" />
|
||||||
|
|
||||||
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
|
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
|
||||||
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)"/>
|
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Resources\Windows\%24placeholder%24.scale-100.png"/>
|
<None Remove="Resources\Windows\%24placeholder%24.scale-100.png" />
|
||||||
<None Remove="Resources\Windows\%24placeholder%24.scale-125.png"/>
|
<None Remove="Resources\Windows\%24placeholder%24.scale-125.png" />
|
||||||
<None Remove="Resources\Windows\%24placeholder%24.scale-150.png"/>
|
<None Remove="Resources\Windows\%24placeholder%24.scale-150.png" />
|
||||||
<None Remove="Resources\Windows\%24placeholder%24.scale-200.png"/>
|
<None Remove="Resources\Windows\%24placeholder%24.scale-200.png" />
|
||||||
<None Remove="Resources\Windows\%24placeholder%24.scale-400.png"/>
|
<None Remove="Resources\Windows\%24placeholder%24.scale-400.png" />
|
||||||
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-100.png"/>
|
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-100.png" />
|
||||||
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-125.png"/>
|
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-125.png" />
|
||||||
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-150.png"/>
|
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-150.png" />
|
||||||
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-200.png"/>
|
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-200.png" />
|
||||||
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-400.png"/>
|
<None Remove="Resources\Windows\Small\%24placeholder%24.scale-400.png" />
|
||||||
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-100.png"/>
|
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-100.png" />
|
||||||
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-125.png"/>
|
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-125.png" />
|
||||||
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-150.png"/>
|
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-150.png" />
|
||||||
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-200.png"/>
|
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-200.png" />
|
||||||
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-400.png"/>
|
<None Remove="Resources\Windows\Splash\%24placeholder%24.scale-400.png" />
|
||||||
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-100.png"/>
|
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-100.png" />
|
||||||
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-125.png"/>
|
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-125.png" />
|
||||||
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-150.png"/>
|
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-150.png" />
|
||||||
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-200.png"/>
|
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-200.png" />
|
||||||
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-400.png"/>
|
<None Remove="Resources\Windows\Wide\%24placeholder%24.scale-400.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="Resources\Windows\$placeholder$.scale-100.png"/>
|
<Content Include="Resources\Windows\$placeholder$.scale-100.png" />
|
||||||
<Content Include="Resources\Windows\$placeholder$.scale-125.png"/>
|
<Content Include="Resources\Windows\$placeholder$.scale-125.png" />
|
||||||
<Content Include="Resources\Windows\$placeholder$.scale-150.png"/>
|
<Content Include="Resources\Windows\$placeholder$.scale-150.png" />
|
||||||
<Content Include="Resources\Windows\$placeholder$.scale-200.png"/>
|
<Content Include="Resources\Windows\$placeholder$.scale-200.png" />
|
||||||
<Content Include="Resources\Windows\$placeholder$.scale-400.png"/>
|
<Content Include="Resources\Windows\$placeholder$.scale-400.png" />
|
||||||
<Content Include="Resources\Windows\Small\$placeholder$.scale-100.png"/>
|
<Content Include="Resources\Windows\Small\$placeholder$.scale-100.png" />
|
||||||
<Content Include="Resources\Windows\Small\$placeholder$.scale-125.png"/>
|
<Content Include="Resources\Windows\Small\$placeholder$.scale-125.png" />
|
||||||
<Content Include="Resources\Windows\Small\$placeholder$.scale-150.png"/>
|
<Content Include="Resources\Windows\Small\$placeholder$.scale-150.png" />
|
||||||
<Content Include="Resources\Windows\Small\$placeholder$.scale-200.png"/>
|
<Content Include="Resources\Windows\Small\$placeholder$.scale-200.png" />
|
||||||
<Content Include="Resources\Windows\Small\$placeholder$.scale-400.png"/>
|
<Content Include="Resources\Windows\Small\$placeholder$.scale-400.png" />
|
||||||
<Content Include="Resources\Windows\Splash\$placeholder$.scale-100.png"/>
|
<Content Include="Resources\Windows\Splash\$placeholder$.scale-100.png" />
|
||||||
<Content Include="Resources\Windows\Splash\$placeholder$.scale-125.png"/>
|
<Content Include="Resources\Windows\Splash\$placeholder$.scale-125.png" />
|
||||||
<Content Include="Resources\Windows\Splash\$placeholder$.scale-150.png"/>
|
<Content Include="Resources\Windows\Splash\$placeholder$.scale-150.png" />
|
||||||
<Content Include="Resources\Windows\Splash\$placeholder$.scale-200.png"/>
|
<Content Include="Resources\Windows\Splash\$placeholder$.scale-200.png" />
|
||||||
<Content Include="Resources\Windows\Splash\$placeholder$.scale-400.png"/>
|
<Content Include="Resources\Windows\Splash\$placeholder$.scale-400.png" />
|
||||||
<Content Include="Resources\Windows\Wide\$placeholder$.scale-100.png"/>
|
<Content Include="Resources\Windows\Wide\$placeholder$.scale-100.png" />
|
||||||
<Content Include="Resources\Windows\Wide\$placeholder$.scale-125.png"/>
|
<Content Include="Resources\Windows\Wide\$placeholder$.scale-125.png" />
|
||||||
<Content Include="Resources\Windows\Wide\$placeholder$.scale-150.png"/>
|
<Content Include="Resources\Windows\Wide\$placeholder$.scale-150.png" />
|
||||||
<Content Include="Resources\Windows\Wide\$placeholder$.scale-200.png"/>
|
<Content Include="Resources\Windows\Wide\$placeholder$.scale-200.png" />
|
||||||
<Content Include="Resources\Windows\Wide\$placeholder$.scale-400.png"/>
|
<Content Include="Resources\Windows\Wide\$placeholder$.scale-400.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -256,18 +256,19 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommunityToolkit.Maui" Version="12.2.0"/>
|
<PackageReference Include="CommunityToolkit.Maui" Version="12.2.0" />
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0"/>
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.110">
|
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.110">
|
||||||
<TreatAsUsed>true</TreatAsUsed>
|
<TreatAsUsed>true</TreatAsUsed>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="9.0.110"/>
|
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="9.0.110" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.9"/>
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.9" />
|
||||||
<PackageReference Include="Microsoft.Maui.Graphics" Version="9.0.110"/>
|
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Runtime.MonoAOTCompiler.Task" Version="9.0.9"/>
|
<PackageReference Include="Microsoft.Maui.Graphics" Version="9.0.110" />
|
||||||
<PackageReference Include="Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk" Version="9.0.9"/>
|
<PackageReference Include="Microsoft.NET.Runtime.MonoAOTCompiler.Task" Version="9.0.9" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
<PackageReference Include="Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk" Version="9.0.9" />
|
||||||
<PackageReference Include="ZXing.Net.Maui.Controls" Version="0.5.0"/>
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="ZXing.Net.Maui.Controls" Version="0.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging;
|
|||||||
using ZXing.Net.Maui.Controls;
|
using ZXing.Net.Maui.Controls;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Jugenddienst_Stunden.ViewModels;
|
using Jugenddienst_Stunden.ViewModels;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden;
|
namespace Jugenddienst_Stunden;
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ public static class MauiProgram {
|
|||||||
//#if DEBUG
|
//#if DEBUG
|
||||||
// if (string.IsNullOrWhiteSpace(GlobalVar.ApiKey)) {
|
// if (string.IsNullOrWhiteSpace(GlobalVar.ApiKey)) {
|
||||||
// GlobalVar.ApiKey = Preferences.Default.Get("apiKey",
|
// GlobalVar.ApiKey = Preferences.Default.Get("apiKey",
|
||||||
// "MTQxfHNkdFptQkNZTXlPT3ZyMHNBZDl0UnVxNExMRXxodHRwOi8vaG91cnMuZGF1bmkubWluZS5udTo4MS9hcHBhcGk=");
|
// "MTQxfHNkdFptQkNZTXlPT3ZyMHxodHRwOi8vaG91cnMuZGF1bmkubWluZS5udTo4MS9hcHBhcGk=");
|
||||||
// GlobalVar.Name = Preferences.Default.Get("name", "Testserver: Isabell");
|
// GlobalVar.Name = Preferences.Default.Get("name", "Testserver: Isabell");
|
||||||
// GlobalVar.Surname = Preferences.Default.Get("surname", "Biasi");
|
// GlobalVar.Surname = Preferences.Default.Get("surname", "Biasi");
|
||||||
// GlobalVar.EmployeeId = Preferences.Default.Get("EmployeeId", 141);
|
// GlobalVar.EmployeeId = Preferences.Default.Get("EmployeeId", 141);
|
||||||
@@ -42,6 +43,10 @@ public static class MauiProgram {
|
|||||||
// builder.Logging.AddDebug();
|
// builder.Logging.AddDebug();
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
|
// ApiClient registrieren: SocketsHttpHandler als Primary Handler (vermeidet AndroidMessageHandler-Castfehler)
|
||||||
|
//var apiOptions = new Infrastructure.ApiOptions { BaseUrl = GlobalVar.ApiUrl, Timeout = TimeSpan.FromSeconds(15) };
|
||||||
|
//builder.Services.AddApiHttpClient(apiOptions);
|
||||||
|
|
||||||
// DI: AlertService für globale Alerts (z. B. leere ApiUrl)
|
// DI: AlertService für globale Alerts (z. B. leere ApiUrl)
|
||||||
builder.Services.AddSingleton<IAlertService, AlertService>();
|
builder.Services.AddSingleton<IAlertService, AlertService>();
|
||||||
|
|
||||||
@@ -50,17 +55,23 @@ public static class MauiProgram {
|
|||||||
|
|
||||||
// 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 {
|
||||||
BaseUrl = sp.GetRequiredService<IAppSettings>().ApiUrl, Timeout = TimeSpan.FromSeconds(15)
|
BaseUrl = sp.GetRequiredService<IAppSettings>().ApiUrl,
|
||||||
|
Timeout = TimeSpan.FromSeconds(15)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Token Provider soll ebenfalls aus Settings/Preferences lesen
|
// Token Provider soll ebenfalls aus Settings/Preferences lesen
|
||||||
builder.Services.AddSingleton<ITokenProvider, SettingsTokenProvider>();
|
builder.Services.AddSingleton<ITokenProvider, SettingsTokenProvider>();
|
||||||
|
|
||||||
// HttpClient + ApiClient
|
// HttpClient + ApiClient
|
||||||
// Configure HttpClient with RequestLoggingHandler and disable automatic redirects for diagnosis
|
// Configure HttpClient with SocketsHttpHandler (managed) and RequestLoggingHandler
|
||||||
builder.Services.AddTransient<RequestLoggingHandler>();
|
builder.Services.AddTransient<RequestLoggingHandler>();
|
||||||
builder.Services.AddSingleton<HttpClient>(sp => {
|
builder.Services.AddSingleton<HttpClient>(sp => {
|
||||||
var nativeHandler = new HttpClientHandler { AllowAutoRedirect = false };
|
var nativeHandler = new SocketsHttpHandler {
|
||||||
|
AllowAutoRedirect = false,
|
||||||
|
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
|
||||||
|
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
|
||||||
|
ConnectTimeout = TimeSpan.FromSeconds(10)
|
||||||
|
};
|
||||||
var logging = sp.GetRequiredService<RequestLoggingHandler>();
|
var logging = sp.GetRequiredService<RequestLoggingHandler>();
|
||||||
logging.InnerHandler = nativeHandler;
|
logging.InnerHandler = nativeHandler;
|
||||||
// HttpClient.Timeout will be adjusted by ApiClient if needed
|
// HttpClient.Timeout will be adjusted by ApiClient if needed
|
||||||
@@ -95,16 +106,18 @@ public static class MauiProgram {
|
|||||||
// DI: Validatoren
|
// DI: Validatoren
|
||||||
builder.Services.AddSingleton<IHoursValidator, HoursValidator>();
|
builder.Services.AddSingleton<IHoursValidator, HoursValidator>();
|
||||||
|
|
||||||
// DI: Services & Repositories
|
// DI: Services & Repositories
|
||||||
builder.Services.AddSingleton<IHoursRepository, HoursRepository>();
|
builder.Services.AddSingleton<IHoursRepository, HoursRepository>();
|
||||||
builder.Services.AddSingleton<IHoursService, HoursService>();
|
builder.Services.AddSingleton<IHoursService, HoursService>();
|
||||||
builder.Services.AddSingleton<IAuthService, AuthService>();
|
builder.Services.AddSingleton<IAuthService, AuthService>();
|
||||||
|
|
||||||
// DI: Views/ViewModels
|
// DI: Views/ViewModels
|
||||||
builder.Services.AddTransient<ViewModels.StundenViewModel>();
|
builder.Services.AddTransient<ViewModels.StundenViewModel>();
|
||||||
builder.Services.AddTransient<Views.StundenPage>();
|
builder.Services.AddTransient<Views.StundenPage>();
|
||||||
builder.Services.AddTransient<ViewModels.LoginViewModel>();
|
builder.Services.AddTransient<ViewModels.StundeViewModel>();
|
||||||
builder.Services.AddTransient<Views.LoginPage>();
|
builder.Services.AddTransient<Views.StundePage>();
|
||||||
|
builder.Services.AddTransient<ViewModels.LoginViewModel>();
|
||||||
|
builder.Services.AddTransient<Views.LoginPage>();
|
||||||
|
|
||||||
return builder.Build();
|
return builder.Build();
|
||||||
}
|
}
|
||||||
|
|||||||
21
Jugenddienst Stunden/Models/ConfirmationEventArgs.cs
Normal file
21
Jugenddienst Stunden/Models/ConfirmationEventArgs.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Jugenddienst_Stunden.Models;
|
||||||
|
public sealed class ConfirmationEventArgs : System.EventArgs {
|
||||||
|
public string Title { get; }
|
||||||
|
public string Message { get; }
|
||||||
|
public string ConfirmText { get; }
|
||||||
|
|
||||||
|
private readonly TaskCompletionSource<bool> _tcs = new();
|
||||||
|
|
||||||
|
public ConfirmationEventArgs(string title, string message, string confirmText = "OK") {
|
||||||
|
Title = title;
|
||||||
|
Message = message;
|
||||||
|
ConfirmText = confirmText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> Task => _tcs.Task;
|
||||||
|
|
||||||
|
public void SetResult(bool result) => _tcs.TrySetResult(result);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"Windows Machine": {
|
"Windows Machine": {
|
||||||
"commandName": "Project",
|
"commandName": "MsixPackage",
|
||||||
"nativeDebugging": false
|
"nativeDebugging": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,15 @@ internal class HoursService : IHoursService {
|
|||||||
|
|
||||||
public async Task<DayTime> GetEntryAsync(int id) => await _repo.LoadEntry(id);
|
public async Task<DayTime> GetEntryAsync(int id) => await _repo.LoadEntry(id);
|
||||||
|
|
||||||
|
public async Task<(DayTime dayTime, Settings settings, List<DayTime> existingDayTimes)> GetEntryWithSettingsAsync(int id) {
|
||||||
|
//var stunde = await _repo.LoadEntry(id);
|
||||||
|
//var (existingDayTimes, settings) = await GetDayWithSettingsAsync(stunde.Day);
|
||||||
|
//return (stunde, settings, existingDayTimes);
|
||||||
|
string q = $"id={id}";
|
||||||
|
var baseRes = await _repo.LoadBase(q);
|
||||||
|
return (baseRes.daytime ?? new DayTime(), baseRes.settings, baseRes.daytimes ?? new List<DayTime>());
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<DayTime> SaveEntryAsync(DayTime stunde) {
|
public async Task<DayTime> SaveEntryAsync(DayTime stunde) {
|
||||||
var settings = await _repo.LoadSettings();
|
var settings = await _repo.LoadSettings();
|
||||||
_validator.Validate(stunde, settings);
|
_validator.Validate(stunde, settings);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Jugenddienst_Stunden.Models;
|
using Jugenddienst_Stunden.Models;
|
||||||
|
using Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.Types;
|
namespace Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
@@ -8,12 +9,12 @@ internal class BaseResponse {
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Monatsübersicht
|
/// Monatsübersicht
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Hours hour { get; set; }
|
public Types.Hours hour { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stundenliste ... für die Katz?
|
/// Stundenliste ... für die Katz?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<Hours> hours { get; set; }
|
public List<Types.Hours> hours { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Liste der Stundeneinträge
|
/// Liste der Stundeneinträge
|
||||||
|
|||||||
@@ -4,24 +4,45 @@ using System.Collections.ObjectModel;
|
|||||||
namespace Jugenddienst_Stunden.Types;
|
namespace Jugenddienst_Stunden.Types;
|
||||||
|
|
||||||
public partial class Hours : ObservableObject {
|
public partial class Hours : ObservableObject {
|
||||||
public double? Zeit;
|
/// <summary>
|
||||||
|
/// Total time in seconds for the current context.
|
||||||
|
/// "zeit" is used by the API to represent the current recorded time value.
|
||||||
|
/// </summary>
|
||||||
|
public int? zeit { get; set; }
|
||||||
|
|
||||||
public double? Nominal;
|
/// <summary>
|
||||||
|
/// Nominal working time expectation (e.g. seconds per day or month depending on API semantics).
|
||||||
|
/// Represents the expected amount of time to be worked.
|
||||||
|
/// </summary>
|
||||||
|
public int? nominal { get; set; }
|
||||||
|
|
||||||
//public Dictionary<DateOnly,NominalDay> nominal_day_api;
|
/// <summary>
|
||||||
|
/// List of nominal day records returned by the API.
|
||||||
|
/// May be null when the API does not provide per-day nominal data.
|
||||||
|
/// </summary>
|
||||||
public List<NominalDay>? Nominal_day_api;
|
public List<NominalDay>? Nominal_day_api;
|
||||||
|
|
||||||
//public Dictionary<int,NominalWeek> nominal_week_api;
|
/// <summary>
|
||||||
|
/// List of nominal week records returned by the API.
|
||||||
|
/// May be null when the API does not provide per-week nominal data.
|
||||||
|
/// </summary>
|
||||||
public List<NominalWeek>? Nominal_week_api;
|
public List<NominalWeek>? Nominal_week_api;
|
||||||
|
|
||||||
//public List<string> time_line;
|
/// <summary>
|
||||||
public double? Zeit_total;
|
/// Total time in seconds reported by the API for the current period. Nullable if not provided.
|
||||||
|
/// </summary>
|
||||||
//https://stackoverflow.com/questions/29449641/deserialize-json-when-a-value-can-be-an-object-or-an-empty-array/29450279#29450279
|
public double? zeit_total { get; set; }
|
||||||
//[JsonConverter(typeof(JsonSingleOrEmptyArrayConverter<Hours>))]
|
|
||||||
//public Dictionary<int,decimal> zeit_total_daily;
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Daily total time values returned by the API.
|
||||||
|
/// Each entry represents a day with its associated time value.
|
||||||
|
/// </summary>
|
||||||
public List<TimeDay> zeit_total_daily_api;
|
public List<TimeDay> zeit_total_daily_api;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Collection of daytime entries representing individual recorded time slots or events.
|
||||||
|
/// Nullable when the API returns no detailed daytime information.
|
||||||
|
/// </summary>
|
||||||
public List<DayTime>? daytime;
|
public List<DayTime>? daytime;
|
||||||
//public List<string> wochensumme;
|
//public List<string> wochensumme;
|
||||||
|
|
||||||
@@ -33,7 +54,7 @@ public partial class Hours : ObservableObject {
|
|||||||
[ObservableProperty] public double zeitausgleich;
|
[ObservableProperty] public double zeitausgleich;
|
||||||
|
|
||||||
public double zeitausgleich_month;
|
public double zeitausgleich_month;
|
||||||
public double holiday;
|
public double? holiday { get; set; }
|
||||||
public double krankheit;
|
public double krankheit;
|
||||||
public double weiterbildung;
|
public double weiterbildung;
|
||||||
public double bereitschaft;
|
public double bereitschaft;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Jugenddienst_Stunden.Interfaces;
|
using Jugenddienst_Stunden.Interfaces;
|
||||||
|
using Jugenddienst_Stunden.Models;
|
||||||
|
|
||||||
namespace Jugenddienst_Stunden.ViewModels;
|
namespace Jugenddienst_Stunden.ViewModels;
|
||||||
|
|
||||||
@@ -15,7 +16,8 @@ public partial class LoginViewModel : ObservableObject {
|
|||||||
private readonly TimeSpan _detectionInterval = TimeSpan.FromSeconds(5);
|
private readonly TimeSpan _detectionInterval = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
public event EventHandler<string>? AlertEvent;
|
public event EventHandler<string>? AlertEvent;
|
||||||
public event EventHandler<string>? InfoEvent;
|
//public event EventHandler<string>? InfoEvent;
|
||||||
|
public event EventHandler<ConfirmationEventArgs>? InfoEvent;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Name der Anwendung
|
/// Name der Anwendung
|
||||||
@@ -102,9 +104,13 @@ public partial class LoginViewModel : ObservableObject {
|
|||||||
(Server ?? string.Empty).Trim());
|
(Server ?? string.Empty).Trim());
|
||||||
|
|
||||||
Title = $"{user.Name} {user.Surname}";
|
Title = $"{user.Name} {user.Surname}";
|
||||||
InfoEvent?.Invoke(this, "Login erfolgreich");
|
// Info zeigen und auf Bestätigung warten
|
||||||
|
var args = new ConfirmationEventArgs("Information:", "Login erfolgreich");
|
||||||
await Shell.Current.GoToAsync("//StundenPage");
|
InfoEvent?.Invoke(this, args);
|
||||||
|
bool confirmed = await args.Task;
|
||||||
|
if (confirmed) {
|
||||||
|
await Shell.Current.GoToAsync("//StundenPage");
|
||||||
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
if (_alerts is not null) {
|
if (_alerts is not null) {
|
||||||
_alerts.Raise(ex.Message);
|
_alerts.Raise(ex.Message);
|
||||||
@@ -128,7 +134,13 @@ public partial class LoginViewModel : ObservableObject {
|
|||||||
var user = await _auth.LoginWithToken(token);
|
var user = await _auth.LoginWithToken(token);
|
||||||
Title = $"{user.Name} {user.Surname}";
|
Title = $"{user.Name} {user.Surname}";
|
||||||
|
|
||||||
await Shell.Current.GoToAsync("//StundenPage");
|
// Info zeigen und auf Bestätigung warten
|
||||||
|
var infoArgs = new ConfirmationEventArgs("Information:", "Login erfolgreich");
|
||||||
|
InfoEvent?.Invoke(this, infoArgs);
|
||||||
|
bool confirmed = await infoArgs.Task;
|
||||||
|
if (confirmed) {
|
||||||
|
await Shell.Current.GoToAsync("//StundenPage");
|
||||||
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
if (_alerts is not null) {
|
if (_alerts is not null) {
|
||||||
_alerts.Raise(ex.Message);
|
_alerts.Raise(ex.Message);
|
||||||
|
|||||||
@@ -77,33 +77,16 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
|
|||||||
//public ICommand LoadDataCommand { get; private set; }
|
//public ICommand LoadDataCommand { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
public StundeViewModel() : this(GetServiceOrCreate()) {
|
public StundeViewModel(IHoursService hoursService, IAlertService alertService) {
|
||||||
LoadSettingsAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IHoursService GetServiceOrCreate() {
|
|
||||||
// Fallback-Konstruktion, falls DI nicht injiziert wurde (z. B. im Designer)
|
|
||||||
var http = new HttpClient();
|
|
||||||
var options = new Infrastructure.ApiOptions { BaseUrl = GlobalVar.ApiUrl, Timeout = TimeSpan.FromSeconds(15) };
|
|
||||||
var tokenProvider = new GlobalVarTokenProvider();
|
|
||||||
var api = new ApiClient(http, options, tokenProvider, new PreferencesAppSettings());
|
|
||||||
var repo = new HoursRepository(api);
|
|
||||||
var validator = new HoursValidator();
|
|
||||||
return new HoursService(repo, validator);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal StundeViewModel(IHoursService hoursService) {
|
|
||||||
_hoursService = hoursService;
|
_hoursService = hoursService;
|
||||||
SaveCommand = new AsyncRelayCommand(Save);
|
SaveCommand = new AsyncRelayCommand(Save);
|
||||||
//DeleteCommand = new AsyncRelayCommand(Delete);
|
|
||||||
DeleteConfirmCommand = new Command(async () => await DeleteConfirm());
|
DeleteConfirmCommand = new Command(async () => await DeleteConfirm());
|
||||||
}
|
|
||||||
|
|
||||||
// DI-Konstruktor, der den globalen Alert-Service abonniert und Alerts an das ViewModel weiterreicht.
|
|
||||||
internal StundeViewModel(IHoursService hoursService, IAlertService alertService) : this(hoursService) {
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//LoadSettingsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void LoadSettingsAsync() {
|
private async void LoadSettingsAsync() {
|
||||||
@@ -122,26 +105,42 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void UpdateSettingsAsync(Settings settings) {
|
||||||
|
GlobalVar.Settings = settings;
|
||||||
|
OptionsGemeinde = settings.Gemeinden;
|
||||||
|
OptionsProjekt = settings.Projekte;
|
||||||
|
OptionsFreistellung = settings.Freistellungen;
|
||||||
|
|
||||||
|
GemeindeAktivSet = settings.GemeindeAktivSet;
|
||||||
|
ProjektAktivSet = settings.ProjektAktivSet;
|
||||||
|
}
|
||||||
|
|
||||||
async Task Save() {
|
async Task Save() {
|
||||||
bool exceptionOccurred = false;
|
bool exceptionOccurred = false;
|
||||||
bool proceed = true;
|
bool proceed = true;
|
||||||
|
|
||||||
//Arbeitszeit sollte nicht null sein
|
//Arbeitszeit sollte nicht null sein
|
||||||
if (DayTime.TimeSpanVon == DayTime.TimeSpanBis && DayTime.FreistellungAktiv.Name == null) {
|
if (DayTime.TimeSpanVon == DayTime.TimeSpanBis && DayTime.FreistellungAktiv.Name == null) {
|
||||||
proceed = false;
|
proceed = false;
|
||||||
AlertEvent?.Invoke(this, "Uhrzeiten sollten unterschiedlich sein");
|
AlertEvent?.Invoke(this, "Uhrzeiten sollten unterschiedlich sein");
|
||||||
}
|
}
|
||||||
|
|
||||||
//Projekt ist ein Pflichtfeld
|
//Projekt ist ein Pflichtfeld
|
||||||
if (Settings.ProjektAktivSet && DayTime.ProjektAktiv.Id == 0) {
|
if (Settings.ProjektAktivSet) {
|
||||||
proceed = false;
|
var projektId = DayTime.ProjektAktiv?.Id ?? 0;
|
||||||
AlertEvent?.Invoke(this, "Projekt darf nicht leer sein");
|
if (projektId == 0) {
|
||||||
|
proceed = false;
|
||||||
|
AlertEvent?.Invoke(this, "Projekt darf nicht leer sein");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Gemeinde ist ein Pflichtfeld
|
//Gemeinde ist ein Pflichtfeld
|
||||||
if (Settings.GemeindeAktivSet && DayTime.GemeindeAktiv.Id == 0) {
|
if (Settings.GemeindeAktivSet) {
|
||||||
proceed = false;
|
var gemeindeId = DayTime.GemeindeAktiv?.Id ?? 0;
|
||||||
AlertEvent?.Invoke(this, "Gemeinde darf nicht leer sein");
|
if (gemeindeId == 0) {
|
||||||
|
proceed = false;
|
||||||
|
AlertEvent?.Invoke(this, "Gemeinde darf nicht leer sein");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (proceed) {
|
if (proceed) {
|
||||||
@@ -179,8 +178,12 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
|
|||||||
await ConfirmEvent.Invoke("Achtung", "Löschen kann nicht ungeschehen gemacht werden. Fortfahren?");
|
await ConfirmEvent.Invoke("Achtung", "Löschen kann nicht ungeschehen gemacht werden. Fortfahren?");
|
||||||
if (answer) {
|
if (answer) {
|
||||||
//Löschen
|
//Löschen
|
||||||
await _hoursService.DeleteEntryAsync(DayTime);
|
try {
|
||||||
await Shell.Current.GoToAsync($"..?date={DayTime.Day.ToString("yyyy-MM-dd")}");
|
await _hoursService.DeleteEntryAsync(DayTime);
|
||||||
|
await Shell.Current.GoToAsync($"..?date={DayTime.Day.ToString("yyyy-MM-dd")}");
|
||||||
|
} catch (Exception e) {
|
||||||
|
AlertEvent?.Invoke(this, e.Message);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
//nicht Löschen
|
//nicht Löschen
|
||||||
}
|
}
|
||||||
@@ -196,46 +199,43 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
|
|||||||
if (query.ContainsKey("load")) {
|
if (query.ContainsKey("load")) {
|
||||||
//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 settings = await _hoursService.GetSettingsAsync();
|
var (entry, settings, daytimes) = await _hoursService.GetEntryWithSettingsAsync(Convert.ToInt32(query["load"]));
|
||||||
// GlobalVar.Settings = settings;
|
UpdateSettingsAsync(settings);
|
||||||
// GemeindeAktivSet = settings.GemeindeAktivSet;
|
|
||||||
// ProjektAktivSet = settings.ProjektAktivSet;
|
|
||||||
|
|
||||||
DayTime = entry;
|
DayTime = entry;
|
||||||
DayTime.TimeSpanVon = entry.Begin.ToTimeSpan();
|
DayTime.TimeSpanVon = entry.Begin.ToTimeSpan();
|
||||||
DayTime.TimeSpanBis = entry.End.ToTimeSpan();
|
DayTime.TimeSpanBis = entry.End.ToTimeSpan();
|
||||||
|
|
||||||
// OptionsGemeinde = settings.Gemeinden ?? new List<Gemeinde>();
|
|
||||||
// OptionsProjekt = settings.Projekte ?? new List<Projekt>();
|
|
||||||
// OptionsFreistellung = settings.Freistellungen ?? new List<Freistellung>();
|
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
//Evtl. noch die anderen Zeiten des gleichen Tages holen
|
|
||||||
var day = await _hoursService.GetDayWithSettingsAsync(DayTime.Day);
|
|
||||||
DayTimes = day.dayTimes;
|
|
||||||
OnPropertyChanged(nameof(DayTime));
|
OnPropertyChanged(nameof(DayTime));
|
||||||
|
if (System.String.IsNullOrEmpty(DayTime.Description)) {
|
||||||
|
InfoEvent?.Invoke(this, "Eintrag hat keinen Beschreibungstext");
|
||||||
|
}
|
||||||
|
|
||||||
|
SubTitle = DayTime.Day.ToString("dddd, d. MMMM yyyy");
|
||||||
|
OnPropertyChanged(nameof(SubTitle));
|
||||||
|
|
||||||
|
FreistellungEnabled = !DayTime.Approved;
|
||||||
|
|
||||||
|
DayTimes = daytimes;
|
||||||
OnPropertyChanged(nameof(DayTimes));
|
OnPropertyChanged(nameof(DayTimes));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
AlertEvent?.Invoke(this, e.Message);
|
AlertEvent?.Invoke(this, e.Message);
|
||||||
} finally {
|
} finally {
|
||||||
|
//Evtl. noch die anderen Zeiten des gleichen Tages holen
|
||||||
|
//var day = await _hoursService.GetDayWithSettingsAsync(DayTime.Day);
|
||||||
|
//DayTimes = day.dayTimes;
|
||||||
|
//OnPropertyChanged(nameof(DayTimes));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (System.String.IsNullOrEmpty(DayTime.Description)) {
|
|
||||||
InfoEvent?.Invoke(this, "Eintrag hat keinen Beschreibungstext");
|
|
||||||
}
|
|
||||||
|
|
||||||
SubTitle = DayTime.Day.ToString("dddd, d. MMMM yyyy");
|
|
||||||
OnPropertyChanged(nameof(SubTitle));
|
|
||||||
|
|
||||||
FreistellungEnabled = !DayTime.Approved;
|
|
||||||
//OnPropertyChanged(nameof(DayTime));
|
//OnPropertyChanged(nameof(DayTime));
|
||||||
} else if (query.ContainsKey("date")) {
|
} else if (query.ContainsKey("date")) {
|
||||||
Title = "Neuer Eintrag";
|
Title = "Neuer Eintrag";
|
||||||
@@ -246,15 +246,9 @@ 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);
|
||||||
GlobalVar.Settings = settings;
|
UpdateSettingsAsync(settings);
|
||||||
DayTimes = list;
|
DayTimes = list;
|
||||||
|
OnPropertyChanged(nameof(DayTimes));
|
||||||
OptionsGemeinde = settings.Gemeinden;
|
|
||||||
OptionsProjekt = settings.Projekte;
|
|
||||||
OptionsFreistellung = settings.Freistellungen;
|
|
||||||
|
|
||||||
GemeindeAktivSet = settings.GemeindeAktivSet;
|
|
||||||
ProjektAktivSet = settings.ProjektAktivSet;
|
|
||||||
} catch (Exception) {
|
} catch (Exception) {
|
||||||
//Ein Tag ohne Einträge gibt eine Fehlermeldung,
|
//Ein Tag ohne Einträge gibt eine Fehlermeldung,
|
||||||
//die soll aber ignoriert werden, weil beim Neueintrag ist das ja Wurscht
|
//die soll aber ignoriert werden, weil beim Neueintrag ist das ja Wurscht
|
||||||
|
|||||||
@@ -101,14 +101,14 @@ public partial class StundenViewModel : ObservableObject, IQueryAttributable, IN
|
|||||||
/// Monatsübersicht: Geleistete Stunden
|
/// Monatsübersicht: Geleistete Stunden
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double? ZeitCalculated {
|
public double? ZeitCalculated {
|
||||||
get => Hours.Zeit_total;
|
get => Hours.zeit_total;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Monatsübersicht: Sollstunden
|
/// Monatsübersicht: Sollstunden
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double? Nominal {
|
public double? Nominal {
|
||||||
get => Hours.Nominal;
|
get => Hours.nominal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -136,7 +136,7 @@ public partial class StundenViewModel : ObservableObject, IQueryAttributable, IN
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Monatsübersicht: Resturlaub
|
/// Monatsübersicht: Resturlaub
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double Holiday {
|
public double? Holiday {
|
||||||
get => Hours.holiday;
|
get => Hours.holiday;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +287,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, 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(() =>
|
||||||
|
|||||||
@@ -33,7 +33,14 @@ public partial class LoginPage : ContentPage {
|
|||||||
|
|
||||||
if (BindingContext is LoginViewModel vm) {
|
if (BindingContext is LoginViewModel vm) {
|
||||||
vm.AlertEvent += async (_, msg) => await DisplayAlert("Fehler:", msg, "OK");
|
vm.AlertEvent += async (_, msg) => await DisplayAlert("Fehler:", msg, "OK");
|
||||||
vm.InfoEvent += async (_, msg) => await DisplayAlert("Information:", msg, "OK");
|
//vm.InfoEvent += async (_, msg) => await DisplayAlert("Information:", msg, "OK");
|
||||||
|
// Neues InfoEvent: Dialog anzeigen und nach Bestätigung das Result setzen
|
||||||
|
vm.InfoEvent += async (_, infoArgs) => {
|
||||||
|
await MainThread.InvokeOnMainThreadAsync(async () => {
|
||||||
|
await DisplayAlert(infoArgs.Title, infoArgs.Message, infoArgs.ConfirmText);
|
||||||
|
infoArgs.SetResult(true);
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
barcodeScannerView.Options =
|
barcodeScannerView.Options =
|
||||||
|
|||||||
@@ -8,10 +8,6 @@
|
|||||||
x:Class="Jugenddienst_Stunden.Views.StundePage"
|
x:Class="Jugenddienst_Stunden.Views.StundePage"
|
||||||
Title="{Binding Title}">
|
Title="{Binding Title}">
|
||||||
|
|
||||||
<ContentPage.BindingContext>
|
|
||||||
<models:StundeViewModel />
|
|
||||||
</ContentPage.BindingContext>
|
|
||||||
|
|
||||||
<ContentPage.Resources>
|
<ContentPage.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<conv:IntBoolConverter x:Key="IntBoolConverter" />
|
<conv:IntBoolConverter x:Key="IntBoolConverter" />
|
||||||
|
|||||||
@@ -13,14 +13,13 @@ public partial class StundePage : ContentPage {
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// CTOR
|
/// CTOR
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StundePage() {
|
public StundePage(StundeViewModel vm) {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
if (BindingContext is StundeViewModel vm) {
|
BindingContext = vm;
|
||||||
vm.AlertEvent += Vm_AlertEvent;
|
vm.AlertEvent += Vm_AlertEvent;
|
||||||
vm.InfoEvent += Vm_InfoEvent;
|
vm.InfoEvent += Vm_InfoEvent;
|
||||||
vm.ConfirmEvent += ShowConfirm;
|
vm.ConfirmEvent += ShowConfirm;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Vm_AlertEvent(object? sender, string e) {
|
private void Vm_AlertEvent(object? sender, string e) {
|
||||||
|
|||||||
Reference in New Issue
Block a user