Wetterdienst
This commit is contained in:
@@ -222,6 +222,7 @@ public static class AppBootstrapper
|
||||
var syncSettings = new SyncSettingsService(appData);
|
||||
services.AddSingleton(syncSettings);
|
||||
services.AddSingleton(_ => new SyncAuthService(new HttpClient()));
|
||||
services.AddSingleton(sp => new SchoolWeatherService(new HttpClient(), syncSettings));
|
||||
// War dieses Gerät schon eingeloggt, aber sync.key fehlt(e), wurde gerade eben (unten)
|
||||
// stillschweigend ein neuer, unabhängiger Schlüssel erzeugt - bisher unter dem ALTEN
|
||||
// Schlüssel synchronisierte Server-Daten sind für dieses Gerät dann nicht mehr lesbar.
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using LehrerApp.Core.Models;
|
||||
|
||||
namespace LehrerApp.Desktop.Services;
|
||||
|
||||
public sealed class SchoolWeatherException(string message) : Exception(message);
|
||||
|
||||
/// <summary>Client für das serverseitige Schulstandort-/Wetterprofil. Liest URL und Token je
|
||||
/// Aufruf, damit ein Login oder Serverwechsel in den Einstellungen sofort berücksichtigt wird.</summary>
|
||||
public sealed class SchoolWeatherService(HttpClient http, SyncSettingsService syncSettings)
|
||||
{
|
||||
public bool IsAvailable => !string.IsNullOrWhiteSpace(syncSettings.ServerUrl) && syncSettings.IsLoggedIn;
|
||||
|
||||
public async Task<SchoolLocationProfile?> GetLocationAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var response = await SendAsync(HttpMethod.Get, "/api/school/location", null, cancellationToken);
|
||||
if (response.StatusCode == HttpStatusCode.NotFound) return null;
|
||||
await EnsureSuccessAsync(response);
|
||||
return await response.Content.ReadFromJsonAsync<SchoolLocationProfile>(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<SchoolLocationProfile> SaveLocationAsync(SchoolLocationRequest location,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var response = await SendAsync(HttpMethod.Put, "/api/school/location", location, cancellationToken);
|
||||
await EnsureSuccessAsync(response);
|
||||
return await response.Content.ReadFromJsonAsync<SchoolLocationProfile>(cancellationToken)
|
||||
?? throw new SchoolWeatherException("Der Server hat keine Standortdaten zurückgegeben.");
|
||||
}
|
||||
|
||||
public async Task<WeatherSnapshot?> GetWeatherAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var response = await SendAsync(HttpMethod.Get, "/api/school/weather", null, cancellationToken);
|
||||
if (response.StatusCode == HttpStatusCode.NotFound) return null;
|
||||
await EnsureSuccessAsync(response);
|
||||
return await response.Content.ReadFromJsonAsync<WeatherSnapshot>(cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> SendAsync(HttpMethod method, string path, object? body,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(syncSettings.ServerUrl) || !syncSettings.IsLoggedIn)
|
||||
throw new SchoolWeatherException("Bitte zuerst im Reiter „Synchronisation“ am Server anmelden.");
|
||||
using var request = new HttpRequestMessage(method, new Uri(new Uri(syncSettings.ServerUrl), path));
|
||||
var token = syncSettings.GetToken();
|
||||
if (token is not null)
|
||||
request.Headers.Authorization = new("Bearer", token);
|
||||
if (body is not null) request.Content = JsonContent.Create(body);
|
||||
try { return await http.SendAsync(request, cancellationToken); }
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
throw new SchoolWeatherException("Der LehrerApp-Server ist nicht erreichbar.");
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task EnsureSuccessAsync(HttpResponseMessage response)
|
||||
{
|
||||
if (response.IsSuccessStatusCode) return;
|
||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
throw new SchoolWeatherException("Die Server-Anmeldung ist abgelaufen. Bitte erneut anmelden.");
|
||||
try
|
||||
{
|
||||
using var document = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
|
||||
if (document.RootElement.TryGetProperty("detail", out var detail) &&
|
||||
!string.IsNullOrWhiteSpace(detail.GetString()))
|
||||
throw new SchoolWeatherException(detail.GetString()!);
|
||||
}
|
||||
catch (JsonException) { }
|
||||
throw new SchoolWeatherException("Der Schulstandort konnte nicht gespeichert werden.");
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ public partial class DashboardViewModel : ObservableObject
|
||||
private readonly SchoolCalendarSettingsService _calendarSettings;
|
||||
private readonly ISubstitutionEntryRepository _substitutions;
|
||||
private readonly IAnnualPlanEventRepository? _annualPlanEvents;
|
||||
private readonly SchoolWeatherService? _schoolWeather;
|
||||
|
||||
private const int OpenExcuseMaxAgeDays = 21;
|
||||
private const int SupportPlanDueWithinDays = 14;
|
||||
@@ -56,6 +57,11 @@ public partial class DashboardViewModel : ObservableObject
|
||||
[ObservableProperty] private DateOnly _calendarMonth = FirstOfMonth(DateTime.Today);
|
||||
[ObservableProperty] private string _selectedDayLabel = "";
|
||||
[ObservableProperty] private bool _isDashboardSettingsOpen;
|
||||
[ObservableProperty] private bool _isWeatherPanelVisible;
|
||||
[ObservableProperty] private string _weatherSummary = "";
|
||||
[ObservableProperty] private string _weatherDetails = "";
|
||||
[ObservableProperty] private string _weatherStatus = "";
|
||||
[ObservableProperty] private bool _hasWeatherWarnings;
|
||||
|
||||
public string CalendarMonthLabel => CalendarMonth.ToString("MMMM yyyy", De);
|
||||
|
||||
@@ -72,6 +78,7 @@ public partial class DashboardViewModel : ObservableObject
|
||||
public ObservableCollection<DashboardAlertItem> Alerts { get; } = [];
|
||||
public ObservableCollection<CalendarEventItem> SelectedDayEvents { get; } = [];
|
||||
public ObservableCollection<DashboardCardOption> DashboardCards { get; } = [];
|
||||
public ObservableCollection<DashboardWeatherWarningItem> WeatherWarnings { get; } = [];
|
||||
public static string[] CalendarWeekdayHeaders { get; } = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
|
||||
|
||||
// Navigation-Callback – wird von App.axaml.cs verdrahtet
|
||||
@@ -111,7 +118,7 @@ public partial class DashboardViewModel : ObservableObject
|
||||
DashboardSettingsService dashboardSettings, ISchoolHolidayRepository schoolHolidays,
|
||||
PublicHolidayService publicHolidays, SchoolCalendarSettingsService calendarSettings,
|
||||
ISubstitutionEntryRepository substitutions, IAnnualPlanEventRepository? annualPlanEvents = null,
|
||||
AnnualPlanSyncService? annualPlanSync = null)
|
||||
AnnualPlanSyncService? annualPlanSync = null, SchoolWeatherService? schoolWeather = null)
|
||||
{
|
||||
_groups = groups; _subjects = subjects; _lessons = lessons; _exams = exams; _tasks = tasks;
|
||||
_examResults = examResults; _grades = grades; _reportGrades = reportGrades; _memberships = memberships;
|
||||
@@ -122,6 +129,7 @@ public partial class DashboardViewModel : ObservableObject
|
||||
_schoolHolidays = schoolHolidays; _publicHolidays = publicHolidays; _calendarSettings = calendarSettings;
|
||||
_substitutions = substitutions;
|
||||
_annualPlanEvents = annualPlanEvents;
|
||||
_schoolWeather = schoolWeather;
|
||||
if (annualPlanSync is not null)
|
||||
{
|
||||
annualPlanSync.DataChanged += () => Avalonia.Threading.Dispatcher.UIThread.Post(LoadCalendar);
|
||||
@@ -142,6 +150,7 @@ public partial class DashboardViewModel : ObservableObject
|
||||
CurrentDate = now.ToString("dddd, d. MMMM yyyy", De);
|
||||
CurrentSchoolYear = _sy.CurrentSchoolYear();
|
||||
Greeting = now.Hour < 12 ? "Guten Morgen" : now.Hour < 18 ? "Guten Tag" : "Guten Abend";
|
||||
_ = LoadWeatherAsync();
|
||||
|
||||
TodaysLessons.Clear();
|
||||
var groups = _groups.GetBySchoolYear(_sy.CurrentSchoolYear()).ToDictionary(g => g.Id);
|
||||
@@ -197,6 +206,57 @@ public partial class DashboardViewModel : ObservableObject
|
||||
LoadAlerts(groups, today);
|
||||
}
|
||||
|
||||
private async Task LoadWeatherAsync()
|
||||
{
|
||||
if (_schoolWeather?.IsAvailable != true) return;
|
||||
try
|
||||
{
|
||||
var snapshot = await _schoolWeather.GetWeatherAsync();
|
||||
if (snapshot is null) return;
|
||||
var current = snapshot.Forecast
|
||||
.Where(x => x.ValidAt.ToUniversalTime() >= DateTime.UtcNow.AddHours(-1))
|
||||
.MinBy(x => Math.Abs((x.ValidAt.ToUniversalTime() - DateTime.UtcNow).TotalMinutes));
|
||||
WeatherWarnings.Clear();
|
||||
foreach (var warning in snapshot.Warnings)
|
||||
WeatherWarnings.Add(new DashboardWeatherWarningItem(warning));
|
||||
HasWeatherWarnings = WeatherWarnings.Count > 0;
|
||||
|
||||
if (current is not null)
|
||||
{
|
||||
var temperature = current.TemperatureC is { } t ? $"{t:0.#} °C" : "Temperatur unbekannt";
|
||||
WeatherSummary = $"{temperature} · {WeatherDescription(current.WeatherCode)}";
|
||||
var details = new List<string>();
|
||||
if (current.WindSpeedKmh is { } wind) details.Add($"Wind {wind:0.#} km/h");
|
||||
if (current.WindGustKmh is { } gust) details.Add($"Böen {gust:0.#} km/h");
|
||||
if (current.PrecipitationMm is > 0) details.Add($"Niederschlag {current.PrecipitationMm:0.#} mm");
|
||||
details.Add($"DWD-Station {snapshot.StationName} ({snapshot.StationDistanceKm:0.#} km)");
|
||||
WeatherDetails = string.Join(" · ", details);
|
||||
}
|
||||
WeatherStatus = snapshot.IsStale
|
||||
? $"Letzter verfügbarer Stand vom {snapshot.RetrievedAt.ToLocalTime():dd.MM., HH:mm} Uhr"
|
||||
: "";
|
||||
IsWeatherPanelVisible = current is not null || HasWeatherWarnings;
|
||||
}
|
||||
catch (SchoolWeatherException ex)
|
||||
{
|
||||
WeatherStatus = ex.Message;
|
||||
IsWeatherPanelVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static string WeatherDescription(int? code) => code switch
|
||||
{
|
||||
0 => "klar",
|
||||
>= 1 and <= 3 => "bewölkt",
|
||||
45 or 48 => "Nebel",
|
||||
>= 51 and <= 67 => "Regen",
|
||||
>= 71 and <= 77 => "Schnee",
|
||||
>= 80 and <= 82 => "Regenschauer",
|
||||
85 or 86 => "Schneeschauer",
|
||||
>= 95 and <= 99 => "Gewitter",
|
||||
_ => "Wettervorhersage",
|
||||
};
|
||||
|
||||
// ── Fehlzeiten-Warnung (5.2.3) ────────────────────────────────────────────
|
||||
|
||||
private void LoadAttendanceWarnings(DateOnly today)
|
||||
@@ -761,6 +821,32 @@ public partial class DashboardViewModel : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DashboardWeatherWarningItem
|
||||
{
|
||||
public string Headline { get; }
|
||||
public string Description { get; }
|
||||
public string Instruction { get; }
|
||||
public string PeriodDisplay { get; }
|
||||
public string SeverityColor { get; }
|
||||
|
||||
public DashboardWeatherWarningItem(WeatherWarning warning)
|
||||
{
|
||||
Headline = string.IsNullOrWhiteSpace(warning.Headline) ? warning.Event : warning.Headline;
|
||||
Description = warning.Description;
|
||||
Instruction = warning.Instruction;
|
||||
PeriodDisplay = (warning.Onset, warning.Expires) switch
|
||||
{
|
||||
({ } onset, { } expires) => $"{onset.ToLocalTime():dd.MM., HH:mm}–{expires.ToLocalTime():HH:mm} Uhr",
|
||||
({ } onset, null) => $"ab {onset.ToLocalTime():dd.MM., HH:mm} Uhr",
|
||||
_ => "",
|
||||
};
|
||||
SeverityColor = warning.Severity switch
|
||||
{
|
||||
"Extreme" => "#7E0023", "Severe" => "#D32F2F", "Moderate" => "#F59E0B", _ => "#FDD835",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class LessonItem
|
||||
{
|
||||
public Guid LessonId { get; set; }
|
||||
|
||||
@@ -162,6 +162,16 @@ public partial class SettingsViewModel : ObservableObject
|
||||
[ObservableProperty] private string _newHolidayEndText = "";
|
||||
[ObservableProperty] private string _holidayNameError = "";
|
||||
[ObservableProperty] private string _holidayDateError = "";
|
||||
[ObservableProperty] private string _schoolName = "";
|
||||
[ObservableProperty] private string _schoolStreet = "";
|
||||
[ObservableProperty] private string _schoolPostalCode = "";
|
||||
[ObservableProperty] private string _schoolCity = "";
|
||||
[ObservableProperty] private string _schoolNameError = "";
|
||||
[ObservableProperty] private string _schoolStreetError = "";
|
||||
[ObservableProperty] private string _schoolPostalCodeError = "";
|
||||
[ObservableProperty] private string _schoolCityError = "";
|
||||
[ObservableProperty] private string _schoolLocationStatus = "";
|
||||
[ObservableProperty] private string _resolvedSchoolAddress = "";
|
||||
|
||||
public List<string> StateOptions { get; } = GermanStateDisplay.Options.ToList();
|
||||
public ObservableCollection<SchoolHolidayItem> SchoolHolidayEntries { get; } = [];
|
||||
@@ -283,6 +293,7 @@ public partial class SettingsViewModel : ObservableObject
|
||||
|
||||
private readonly ISchoolHolidayRepository _schoolHolidays;
|
||||
private readonly SchoolCalendarSettingsService _calendarSettings;
|
||||
private readonly SchoolWeatherService? _schoolWeather;
|
||||
private readonly PeriodScheduleService _periodSchedule;
|
||||
private readonly ISupervisionDutyRepository _supervisionDuties;
|
||||
private readonly AiSettingsService _aiSettings;
|
||||
@@ -318,7 +329,8 @@ public partial class SettingsViewModel : ObservableObject
|
||||
AppLogger logger, SyncKeyStatus syncKeyStatus, SyncKeyRecoveryService syncKeyRecovery,
|
||||
AppearanceSettingsService appearance, TrashViewModel trashTab,
|
||||
SnapshotService? snapshotService = null, SyncEngine? syncEngine = null,
|
||||
UntisSyncService? untisSync = null, AnnualPlanSyncService? annualPlanSync = null)
|
||||
UntisSyncService? untisSync = null, AnnualPlanSyncService? annualPlanSync = null,
|
||||
SchoolWeatherService? schoolWeather = null)
|
||||
{
|
||||
_logger = logger;
|
||||
_syncKeyRecovery = syncKeyRecovery;
|
||||
@@ -341,6 +353,7 @@ public partial class SettingsViewModel : ObservableObject
|
||||
_shorthandCodes = shorthandCodes;
|
||||
_schoolHolidays = schoolHolidays;
|
||||
_calendarSettings = calendarSettings;
|
||||
_schoolWeather = schoolWeather;
|
||||
_periodSchedule = periodSchedule;
|
||||
_supervisionDuties = supervisionDuties;
|
||||
_letterTemplates = letterTemplates;
|
||||
@@ -930,6 +943,60 @@ public partial class SettingsViewModel : ObservableObject
|
||||
partial void OnSelectedStateNameChanged(string value) =>
|
||||
_calendarSettings.SetState(GermanStateDisplay.FromLabel(value));
|
||||
|
||||
[RelayCommand]
|
||||
private async Task LoadSchoolLocation()
|
||||
{
|
||||
if (_schoolWeather is null) return;
|
||||
try
|
||||
{
|
||||
var location = await _schoolWeather.GetLocationAsync();
|
||||
if (location is null) return;
|
||||
SchoolName = location.SchoolName;
|
||||
SchoolStreet = location.Street;
|
||||
SchoolPostalCode = location.PostalCode;
|
||||
SchoolCity = location.City;
|
||||
SelectedStateName = GermanStateDisplay.Label(location.State);
|
||||
ResolvedSchoolAddress = location.ResolvedAddress;
|
||||
}
|
||||
catch (SchoolWeatherException ex) { SchoolLocationStatus = ex.Message; }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SaveSchoolLocation()
|
||||
{
|
||||
SchoolNameError = ""; SchoolStreetError = ""; SchoolPostalCodeError = "";
|
||||
SchoolCityError = ""; SchoolLocationStatus = "";
|
||||
var valid = true;
|
||||
if (string.IsNullOrWhiteSpace(SchoolName))
|
||||
{ SchoolNameError = "Name der Schule erforderlich."; valid = false; }
|
||||
if (string.IsNullOrWhiteSpace(SchoolStreet))
|
||||
{ SchoolStreetError = "Straße und Hausnummer erforderlich."; valid = false; }
|
||||
if (!System.Text.RegularExpressions.Regex.IsMatch(SchoolPostalCode.Trim(), @"^\d{5}$"))
|
||||
{ SchoolPostalCodeError = "Bitte eine fünfstellige PLZ angeben."; valid = false; }
|
||||
if (string.IsNullOrWhiteSpace(SchoolCity))
|
||||
{ SchoolCityError = "Ort erforderlich."; valid = false; }
|
||||
if (!valid) return;
|
||||
if (_schoolWeather is null)
|
||||
{
|
||||
SchoolLocationStatus = "Der Wetterdienst ist nicht verfügbar.";
|
||||
return;
|
||||
}
|
||||
|
||||
SchoolLocationStatus = "Adresse wird geprüft …";
|
||||
try
|
||||
{
|
||||
var profile = await _schoolWeather.SaveLocationAsync(new SchoolLocationRequest
|
||||
{
|
||||
SchoolName = SchoolName.Trim(), Street = SchoolStreet.Trim(),
|
||||
PostalCode = SchoolPostalCode.Trim(), City = SchoolCity.Trim(),
|
||||
State = GermanStateDisplay.FromLabel(SelectedStateName),
|
||||
});
|
||||
ResolvedSchoolAddress = profile.ResolvedAddress;
|
||||
SchoolLocationStatus = "Schulstandort gespeichert. Wetterdaten werden serverseitig abgerufen.";
|
||||
}
|
||||
catch (SchoolWeatherException ex) { SchoolLocationStatus = ex.Message; }
|
||||
}
|
||||
|
||||
private void LoadSchoolHolidays()
|
||||
{
|
||||
SchoolHolidayEntries.Clear();
|
||||
|
||||
@@ -54,6 +54,46 @@
|
||||
</ItemsControl>
|
||||
</Border>
|
||||
|
||||
<!-- Serverseitig gecachte DWD-Daten für den in den Einstellungen hinterlegten Schulstandort. -->
|
||||
<Border Background="{DynamicResource SystemControlBackgroundAltHighBrush}" CornerRadius="8"
|
||||
Padding="16" IsVisible="{Binding IsWeatherPanelVisible}">
|
||||
<StackPanel Spacing="10">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel>
|
||||
<TextBlock Text="WETTER AM SCHULSTANDORT" FontSize="11" FontWeight="Bold" Opacity="0.5"/>
|
||||
<TextBlock Text="{Binding WeatherSummary}" FontSize="20" FontWeight="SemiBold" Margin="0,5,0,0"
|
||||
IsVisible="{Binding WeatherSummary, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
<TextBlock Text="{Binding WeatherDetails}" FontSize="11" Opacity="0.65" TextWrapping="Wrap"
|
||||
IsVisible="{Binding WeatherDetails, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Content="Aktualisieren" FontSize="11" Padding="8,3"
|
||||
Command="{Binding RefreshCommand}" VerticalAlignment="Top"/>
|
||||
</Grid>
|
||||
<TextBlock Text="{Binding WeatherStatus}" FontSize="11" Opacity="0.65" TextWrapping="Wrap"
|
||||
IsVisible="{Binding WeatherStatus, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
<ItemsControl ItemsSource="{Binding WeatherWarnings}" IsVisible="{Binding HasWeatherWarnings}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:DashboardWeatherWarningItem">
|
||||
<Border BorderBrush="{Binding SeverityColor}" BorderThickness="4,0,0,0"
|
||||
Background="#14D32F2F" Padding="10" Margin="0,3" CornerRadius="4">
|
||||
<StackPanel Spacing="3">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Text="{Binding Headline}" FontSize="13" FontWeight="SemiBold" TextWrapping="Wrap"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding PeriodDisplay}" FontSize="10" Opacity="0.65"
|
||||
Margin="10,0,0,0"/>
|
||||
</Grid>
|
||||
<TextBlock Text="{Binding Description}" FontSize="11" TextWrapping="Wrap"/>
|
||||
<TextBlock Text="{Binding Instruction}" FontSize="11" Opacity="0.75" TextWrapping="Wrap"
|
||||
IsVisible="{Binding Instruction, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<TextBlock Text="Wetterdaten © Deutscher Wetterdienst" FontSize="10" Opacity="0.5"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto">
|
||||
|
||||
<!-- Heutige Stunden -->
|
||||
|
||||
@@ -415,6 +415,45 @@
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="32,20,32,28" Spacing="14" MaxWidth="480">
|
||||
|
||||
<TextBlock Text="Schulstandort & Wetter" FontSize="16" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="Die Adresse wird beim Speichern einmalig auf dem LehrerApp-Server geocodiert. Der Server ruft damit Wettervorhersagen und amtliche Warnungen des DWD ab."
|
||||
FontSize="12" Opacity="0.6" TextWrapping="Wrap"/>
|
||||
|
||||
<StackPanel Spacing="4">
|
||||
<TextBox Text="{Binding SchoolName}" PlaceholderText="Name der Schule"/>
|
||||
<TextBlock Text="{Binding SchoolNameError}" Foreground="Red" FontSize="11"
|
||||
IsVisible="{Binding SchoolNameError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
<TextBox Text="{Binding SchoolStreet}" PlaceholderText="Straße und Hausnummer"/>
|
||||
<TextBlock Text="{Binding SchoolStreetError}" Foreground="Red" FontSize="11"
|
||||
IsVisible="{Binding SchoolStreetError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
<Grid ColumnDefinitions="120,8,*">
|
||||
<TextBox Grid.Column="0" Text="{Binding SchoolPostalCode}" PlaceholderText="PLZ" MaxLength="5"/>
|
||||
<TextBox Grid.Column="2" Text="{Binding SchoolCity}" PlaceholderText="Ort"/>
|
||||
</Grid>
|
||||
<Grid ColumnDefinitions="120,8,*">
|
||||
<TextBlock Grid.Column="0" Text="{Binding SchoolPostalCodeError}" Foreground="Red" FontSize="11"
|
||||
TextWrapping="Wrap"
|
||||
IsVisible="{Binding SchoolPostalCodeError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
<TextBlock Grid.Column="2" Text="{Binding SchoolCityError}" Foreground="Red" FontSize="11"
|
||||
IsVisible="{Binding SchoolCityError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
</Grid>
|
||||
<Button Content="Adresse prüfen und speichern" Command="{Binding SaveSchoolLocationCommand}"
|
||||
HorizontalAlignment="Left" Margin="0,4,0,0"/>
|
||||
<TextBlock Text="{Binding SchoolLocationStatus}" FontSize="12" TextWrapping="Wrap"
|
||||
IsVisible="{Binding SchoolLocationStatus, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
<Border Background="{DynamicResource SystemControlBackgroundAltHighBrush}" CornerRadius="5" Padding="10"
|
||||
IsVisible="{Binding ResolvedSchoolAddress, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||
<StackPanel Spacing="3">
|
||||
<TextBlock Text="Gefundener Standort" FontSize="11" FontWeight="SemiBold" Opacity="0.7"/>
|
||||
<TextBlock Text="{Binding ResolvedSchoolAddress}" FontSize="12" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<TextBlock Text="Geocodierung © OpenStreetMap-Mitwirkende · Wetterdaten © Deutscher Wetterdienst"
|
||||
FontSize="10" Opacity="0.55" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
|
||||
<Separator Margin="0,4"/>
|
||||
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="Bundesland (für Feiertage)" FontSize="13" FontWeight="SemiBold"/>
|
||||
<ComboBox ItemsSource="{Binding StateOptions}" SelectedItem="{Binding SelectedStateName}"
|
||||
|
||||
@@ -31,6 +31,7 @@ public partial class SettingsView : UserControl
|
||||
vm.OnConfirmRecoveryRestore = ShowRecoveryRestoreConfirmDialog;
|
||||
vm.OnThemeChanged = App.ApplyTheme;
|
||||
vm.OnReviewUntisMapping = ShowUntisMappingReviewDialog;
|
||||
_ = vm.LoadSchoolLocationCommand.ExecuteAsync(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user