feat: Dunkelmodus, Fenstergröße merken, Papierkorb für Löschvorgänge (12.4, 14.3, 14.6)
Dunkelmodus über neuen Einstellungen-Tab "Darstellung" (Systemvorgabe/Hell/Dunkel), Fenstergröße/Maximiert-Status wird über Sitzungen hinweg gemerkt (bewusst ohne Fensterposition), und ein generischer Snapshot-basierter Papierkorb (30 Tage) für Sitzpläne, Noten, Notenschlüssel-Vorlagen, Aufgaben und Zeiteinträge. Details und bewusste Scope-Entscheidungen (Spaltenbreiten zurückgestellt, Farb-Audit für Dunkelmodus offen, welche Entitäten der Papierkorb abdeckt) in TODO.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Styling;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Data;
|
||||
using LehrerApp.Desktop.Services;
|
||||
@@ -24,6 +26,13 @@ public class App : Application
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
// Vor jedem Fenster (auch dem DB-Passwort-Prompt) anwenden, damit kein kurzes Aufblitzen
|
||||
// der Systemvorgabe zu sehen ist, falls Hell/Dunkel manuell erzwungen wurde. Eigenständige
|
||||
// Instanz statt über den DI-Container (der erst in BuildServices() entsteht, die den
|
||||
// Passwort-Prompt bereits verzögert) - dieselbe appData-Datei wie später der
|
||||
// DI-Singleton, rein lesend also unproblematisch doppelt konstruiert.
|
||||
ApplyTheme(new AppearanceSettingsService(AppBootstrapper.ResolveAppDataPath()).Load());
|
||||
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
// Verschlüsselte Datenbank (13.3.4): Passwort abfragen, bevor die Datenbank
|
||||
@@ -57,6 +66,10 @@ public class App : Application
|
||||
Services = _serviceProvider;
|
||||
Services.GetRequiredService<AppLogger>().Info("Anwendung gestartet.");
|
||||
GlobalExceptionHandler.AttachNotifications(Services.GetRequiredService<NotificationService>());
|
||||
// Papierkorb (14.3): Einträge älter als 30 Tage endgültig entfernen. Beim Start statt per
|
||||
// Timer - reicht für ein Werkzeug, das ohnehin nur "Fehlklick eben rückgängig machen" sein
|
||||
// soll, kein dauerhaftes Archiv.
|
||||
Services.GetRequiredService<ITrashRepository>().PurgeOlderThan(DateTime.UtcNow.AddDays(-30));
|
||||
|
||||
if (!_exitHandlerAttached)
|
||||
{
|
||||
@@ -67,12 +80,24 @@ public class App : Application
|
||||
var mainVm = Services.GetRequiredService<MainWindowViewModel>();
|
||||
WireCallbacks(mainVm);
|
||||
var main = new MainWindow { DataContext = mainVm };
|
||||
main.EnableWindowSizePersistence(Services.GetRequiredService<WindowSettingsService>());
|
||||
if (Services.GetService<SyncEngine>() is { } syncEngine)
|
||||
main.EnableFinalSync(syncEngine);
|
||||
desktop.MainWindow = main;
|
||||
if (showImmediately) main.Show();
|
||||
}
|
||||
|
||||
/// <summary>Wechselt die Darstellung sofort, ohne Neustart (12.4) — Avalonia stylt den
|
||||
/// gesamten sichtbaren Baum automatisch neu, sobald sich <see cref="ThemeVariant"/>
|
||||
/// ändert.</summary>
|
||||
public static void ApplyTheme(AppTheme theme) =>
|
||||
Current!.RequestedThemeVariant = theme switch
|
||||
{
|
||||
AppTheme.Light => ThemeVariant.Light,
|
||||
AppTheme.Dark => ThemeVariant.Dark,
|
||||
_ => ThemeVariant.Default,
|
||||
};
|
||||
|
||||
private static void DisposeServices()
|
||||
{
|
||||
if (_serviceProvider is null) return;
|
||||
|
||||
@@ -139,6 +139,7 @@ public static class AppBootstrapper
|
||||
services.AddSingleton<IStudentRepository, StudentRepository>();
|
||||
services.AddSingleton<IGroupRepository, GroupRepository>();
|
||||
services.AddSingleton<ISeatingPlanRepository, SeatingPlanRepository>();
|
||||
services.AddSingleton<ITrashRepository, TrashRepository>();
|
||||
services.AddSingleton<IGroupMembershipRepository, GroupMembershipRepository>();
|
||||
services.AddSingleton<IExamRepository, ExamRepository>();
|
||||
services.AddSingleton<IExamResultRepository, ExamResultRepository>();
|
||||
@@ -175,6 +176,8 @@ public static class AppBootstrapper
|
||||
services.AddSingleton(_ => new PeriodScheduleService(appData));
|
||||
services.AddSingleton(_ => new WorkloadSettingsService(appData));
|
||||
services.AddSingleton(_ => new DashboardSettingsService(appData));
|
||||
services.AddSingleton(_ => new WindowSettingsService(appData));
|
||||
services.AddSingleton(_ => new AppearanceSettingsService(appData));
|
||||
services.AddSingleton(_ => new LetterTemplateService(appData));
|
||||
services.AddSingleton<PlanningExchangeService>();
|
||||
|
||||
@@ -270,6 +273,7 @@ public static class AppBootstrapper
|
||||
services.AddTransient<SeatingPlanTabViewModel>();
|
||||
services.AddTransient<GroupDocumentationTabViewModel>();
|
||||
services.AddTransient<AddGroupDialogViewModel>();
|
||||
services.AddTransient<TrashViewModel>();
|
||||
services.AddTransient<SettingsViewModel>();
|
||||
|
||||
var provider = services.BuildServiceProvider();
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace LehrerApp.Desktop.Services;
|
||||
|
||||
/// <summary>Systemvorgabe folgt <see cref="Avalonia.Styling.ThemeVariant.Default"/> (bisheriges,
|
||||
/// unverändertes Verhalten); Hell/Dunkel erzwingen die jeweilige Variante unabhängig vom
|
||||
/// Betriebssystem.</summary>
|
||||
public enum AppTheme { System, Light, Dark }
|
||||
|
||||
internal sealed class AppearanceSettingsConfig
|
||||
{
|
||||
public AppTheme Theme { get; set; } = AppTheme.System;
|
||||
}
|
||||
|
||||
/// <summary>Merkt sich die gewählte Darstellung (12.4) über Sitzungen hinweg.</summary>
|
||||
public sealed class AppearanceSettingsService
|
||||
{
|
||||
private readonly string _configPath;
|
||||
|
||||
public AppearanceSettingsService(string appDataPath) =>
|
||||
_configPath = Path.Combine(appDataPath, "appearancesettings.json");
|
||||
|
||||
public AppTheme Load()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(_configPath))
|
||||
return JsonSerializer.Deserialize<AppearanceSettingsConfig>(File.ReadAllText(_configPath))
|
||||
?.Theme ?? AppTheme.System;
|
||||
}
|
||||
catch { /* beschädigte Konfiguration -> Systemvorgabe */ }
|
||||
return AppTheme.System;
|
||||
}
|
||||
|
||||
public void Save(AppTheme theme) =>
|
||||
File.WriteAllText(_configPath, JsonSerializer.Serialize(new AppearanceSettingsConfig { Theme = theme }));
|
||||
}
|
||||
|
||||
// ComboBox-Anzeige: deutsche Beschriftung statt des rohen Enum-Namens (etabliertes Muster, siehe
|
||||
// z.B. NiveauDisplay/GradeCategoryDisplay).
|
||||
public static class AppThemeDisplay
|
||||
{
|
||||
public static string Label(AppTheme theme) => theme switch
|
||||
{
|
||||
AppTheme.Light => "Hell",
|
||||
AppTheme.Dark => "Dunkel",
|
||||
_ => "Systemvorgabe",
|
||||
};
|
||||
|
||||
public static string[] Options { get; } = Enum.GetValues<AppTheme>().Select(Label).ToArray();
|
||||
|
||||
public static AppTheme FromLabel(string? label) =>
|
||||
Enum.GetValues<AppTheme>().FirstOrDefault(t => Label(t) == label, AppTheme.System);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace LehrerApp.Desktop.Services;
|
||||
|
||||
public sealed class WindowSettings
|
||||
{
|
||||
public double Width { get; set; } = 1280;
|
||||
public double Height { get; set; } = 800;
|
||||
public bool IsMaximized { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merkt sich Fenstergröße und Maximiert-Status über Sitzungen hinweg (14.6). Bewusst OHNE
|
||||
/// Fensterposition: ein Laptop, der mal mit, mal ohne externen Monitor läuft, könnte sonst dazu
|
||||
/// führen, dass das Fenster bei der nächsten Sitzung außerhalb jedes sichtbaren Bildschirms
|
||||
/// landet — die Größe allein ist der Teil, der täglich beim Korrigieren am Abend stört.
|
||||
/// </summary>
|
||||
public sealed class WindowSettingsService
|
||||
{
|
||||
private readonly string _configPath;
|
||||
|
||||
public WindowSettingsService(string appDataPath) =>
|
||||
_configPath = Path.Combine(appDataPath, "windowsettings.json");
|
||||
|
||||
public WindowSettings Load()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(_configPath))
|
||||
{
|
||||
var loaded = JsonSerializer.Deserialize<WindowSettings>(File.ReadAllText(_configPath));
|
||||
if (loaded is { Width: >= 200, Height: >= 150 }) return loaded;
|
||||
}
|
||||
}
|
||||
catch { /* beschädigte Konfiguration -> Standardgröße */ }
|
||||
return new WindowSettings();
|
||||
}
|
||||
|
||||
public void Save(WindowSettings settings) =>
|
||||
File.WriteAllText(_configPath, JsonSerializer.Serialize(settings));
|
||||
}
|
||||
@@ -36,6 +36,8 @@ public enum SettingsTab
|
||||
Privacy = 10,
|
||||
Sync = 11,
|
||||
Ai = 12,
|
||||
Appearance = 13,
|
||||
Trash = 14,
|
||||
}
|
||||
|
||||
// ── Haupt-ViewModel ───────────────────────────────────────────────────────────
|
||||
@@ -237,6 +239,27 @@ public partial class SettingsViewModel : ObservableObject
|
||||
/// gerendert würde.
|
||||
public Func<string, Task>? OnShowPairingError { get; set; }
|
||||
|
||||
// ── Darstellung (12.4) ──────────────────────────────────────────────────────
|
||||
|
||||
[ObservableProperty] private AppTheme _selectedTheme;
|
||||
public string[] ThemeOptions { get; } = AppThemeDisplay.Options;
|
||||
public string SelectedThemeName
|
||||
{
|
||||
get => AppThemeDisplay.Label(SelectedTheme);
|
||||
set => SelectedTheme = AppThemeDisplay.FromLabel(value);
|
||||
}
|
||||
|
||||
/// Vom Code-Behind gesetzt: wendet die Theme-Variante sofort an (Application.Current, siehe
|
||||
/// App.ApplyTheme) — ViewModels fassen Avalonia-Framework-Typen nicht direkt an, gleiches
|
||||
/// Muster wie die übrigen Code-Behind-Hooks in dieser Klasse.
|
||||
public Action<AppTheme>? OnThemeChanged { get; set; }
|
||||
|
||||
partial void OnSelectedThemeChanged(AppTheme value)
|
||||
{
|
||||
_appearance.Save(value);
|
||||
OnThemeChanged?.Invoke(value);
|
||||
}
|
||||
|
||||
// ── Konstruktor ───────────────────────────────────────────────────────────
|
||||
|
||||
private readonly ISchoolHolidayRepository _schoolHolidays;
|
||||
@@ -251,9 +274,12 @@ public partial class SettingsViewModel : ObservableObject
|
||||
private readonly SyncEngine? _syncEngine;
|
||||
private readonly SnapshotService? _snapshotService;
|
||||
private readonly SyncKeyRecoveryService _syncKeyRecovery;
|
||||
private readonly AppearanceSettingsService _appearance;
|
||||
private readonly CompetencyCatalogImportService _catalogImport;
|
||||
private readonly AppLogger _logger;
|
||||
|
||||
public TrashViewModel TrashTab { get; }
|
||||
|
||||
public SettingsViewModel(ISubjectRepository subjects, ICompetencyDomainRepository domainRepo,
|
||||
IGradingKeyTemplateRepository gradingKeyTemplates, IGradingSchemeRepository gradingSchemes,
|
||||
GradingService grading, BackupService backups, DatabaseEncryptionService dbEncryption,
|
||||
@@ -265,11 +291,15 @@ public partial class SettingsViewModel : ObservableObject
|
||||
AiSettingsService aiSettings, AiPlanningService aiPlanning,
|
||||
SyncSettingsService syncSettings, SyncAuthService syncAuth, EventQueue eventQueue,
|
||||
AppLogger logger, SyncKeyStatus syncKeyStatus, SyncKeyRecoveryService syncKeyRecovery,
|
||||
AppearanceSettingsService appearance, TrashViewModel trashTab,
|
||||
SnapshotService? snapshotService = null, SyncEngine? syncEngine = null)
|
||||
{
|
||||
_logger = logger;
|
||||
_syncKeyRecovery = syncKeyRecovery;
|
||||
SyncKeyWasRegenerated = syncKeyStatus.KeyWasRegenerated;
|
||||
_appearance = appearance;
|
||||
_selectedTheme = appearance.Load();
|
||||
TrashTab = trashTab;
|
||||
_subjects = subjects;
|
||||
_domainRepo = domainRepo;
|
||||
_gradingKeyTemplates = gradingKeyTemplates;
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Models;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace LehrerApp.Desktop.ViewModels.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// Papierkorb (14.3): zeigt gelöschte Datensätze der abgedeckten Modelle (siehe
|
||||
/// TODO.md 14.3 für die vollständige Liste und die bewusst ausgeschlossenen, kaskadierenden
|
||||
/// Löschvorgänge wie eine ganze Lerngruppe) und erlaubt das Wiederherstellen. Rein lokal — der
|
||||
/// Papierkorb selbst wird nicht synchronisiert (siehe TrashRepository-Klassenkommentar);
|
||||
/// wiederhergestellte Einträge synchronisieren sich danach ganz normal wie jede andere Änderung.
|
||||
/// </summary>
|
||||
public partial class TrashViewModel : ObservableObject
|
||||
{
|
||||
private readonly ITrashRepository _trash;
|
||||
private readonly IGradeRepository _grades;
|
||||
private readonly IWorkTaskRepository _tasks;
|
||||
private readonly ITimeEntryRepository _timeEntries;
|
||||
private readonly ISeatingPlanRepository _seatingPlans;
|
||||
private readonly IGradingKeyTemplateRepository _gradingKeyTemplates;
|
||||
|
||||
[ObservableProperty] private string _status = "";
|
||||
|
||||
public ObservableCollection<TrashItemViewModel> Items { get; } = [];
|
||||
public bool HasItems => Items.Count > 0;
|
||||
|
||||
public TrashViewModel(ITrashRepository trash, IGradeRepository grades, IWorkTaskRepository tasks,
|
||||
ITimeEntryRepository timeEntries, ISeatingPlanRepository seatingPlans,
|
||||
IGradingKeyTemplateRepository gradingKeyTemplates)
|
||||
{
|
||||
_trash = trash;
|
||||
_grades = grades;
|
||||
_tasks = tasks;
|
||||
_timeEntries = timeEntries;
|
||||
_seatingPlans = seatingPlans;
|
||||
_gradingKeyTemplates = gradingKeyTemplates;
|
||||
Load();
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
Items.Clear();
|
||||
foreach (var item in _trash.GetAll())
|
||||
Items.Add(new TrashItemViewModel(item, TypeLabel(item.EntityType)));
|
||||
OnPropertyChanged(nameof(HasItems));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Restore(TrashItemViewModel? item)
|
||||
{
|
||||
if (item is null) return;
|
||||
switch (item.EntityType)
|
||||
{
|
||||
case nameof(Grade): _grades.Restore(item.Id); break;
|
||||
case nameof(WorkTask): _tasks.Restore(item.Id); break;
|
||||
case nameof(TimeEntry): _timeEntries.Restore(item.Id); break;
|
||||
case nameof(SeatingPlan): _seatingPlans.Restore(item.Id); break;
|
||||
case nameof(GradingKeyTemplate): _gradingKeyTemplates.Restore(item.Id); break;
|
||||
default: return; // Unbekannter Typ (sollte nicht vorkommen) - Eintrag unverändert lassen.
|
||||
}
|
||||
Status = $"„{item.Summary}“ wiederhergestellt.";
|
||||
Load();
|
||||
}
|
||||
|
||||
private static string TypeLabel(string entityType) => entityType switch
|
||||
{
|
||||
nameof(Grade) => "Note",
|
||||
nameof(WorkTask) => "Aufgabe",
|
||||
nameof(TimeEntry) => "Zeiteintrag",
|
||||
nameof(SeatingPlan) => "Sitzplan",
|
||||
nameof(GradingKeyTemplate) => "Notenschlüssel-Vorlage",
|
||||
_ => entityType,
|
||||
};
|
||||
}
|
||||
|
||||
public class TrashItemViewModel
|
||||
{
|
||||
private const int RetentionDays = 30;
|
||||
|
||||
public Guid Id { get; }
|
||||
public string EntityType { get; }
|
||||
public string TypeLabel { get; }
|
||||
public string Summary { get; }
|
||||
public string DeletedAtDisplay { get; }
|
||||
public string ExpiresInDisplay { get; }
|
||||
|
||||
public TrashItemViewModel(TrashedItem item, string typeLabel)
|
||||
{
|
||||
Id = item.Id;
|
||||
EntityType = item.EntityType;
|
||||
TypeLabel = typeLabel;
|
||||
Summary = item.Summary;
|
||||
DeletedAtDisplay = item.DeletedAt.ToLocalTime().ToString("dd.MM.yyyy HH:mm");
|
||||
var daysLeft = RetentionDays - (DateTime.UtcNow - item.DeletedAt).Days;
|
||||
ExpiresInDisplay = daysLeft <= 0 ? "läuft heute ab" : $"noch {daysLeft} Tag(e)";
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using LehrerApp.Desktop.Services;
|
||||
using LehrerApp.Desktop.ViewModels;
|
||||
using LehrerApp.Sync;
|
||||
|
||||
@@ -26,6 +27,32 @@ public partial class MainWindow : Window
|
||||
Closing += OnClosing;
|
||||
}
|
||||
|
||||
/// <summary>Wendet die zuletzt gespeicherte Fenstergröße an und merkt sich die aktuelle beim
|
||||
/// Schließen (14.6). Eigenständig von <see cref="EnableFinalSync"/> verdrahtet (mehrere
|
||||
/// Closing-Handler sind unproblematisch) und immer aktiv, auch ohne konfigurierten
|
||||
/// Sync-Server.</summary>
|
||||
public void EnableWindowSizePersistence(WindowSettingsService settings)
|
||||
{
|
||||
var saved = settings.Load();
|
||||
Width = saved.Width;
|
||||
Height = saved.Height;
|
||||
if (saved.IsMaximized) WindowState = WindowState.Maximized;
|
||||
|
||||
Closing += (_, _) =>
|
||||
{
|
||||
var current = settings.Load();
|
||||
settings.Save(new WindowSettings
|
||||
{
|
||||
IsMaximized = WindowState == WindowState.Maximized,
|
||||
// Im maximierten Zustand spiegeln Width/Height die Bildschirmgröße wider, nicht
|
||||
// die zuletzt genutzte Normalgröße - dann den vorherigen Wert beibehalten statt
|
||||
// ihn zu überschreiben.
|
||||
Width = WindowState == WindowState.Normal ? Width : current.Width,
|
||||
Height = WindowState == WindowState.Normal ? Height : current.Height,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private async void OnClosing(object? sender, WindowClosingEventArgs e)
|
||||
{
|
||||
if (_closeAfterFinalSync || _finalSyncStarted || _syncEngine is null)
|
||||
|
||||
@@ -920,6 +920,68 @@
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</ContentPage>
|
||||
|
||||
<!-- Tab: Darstellung (12.4) -->
|
||||
<ContentPage Header="Darstellung">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="32,20,32,28" Spacing="14" MaxWidth="420">
|
||||
|
||||
<TextBlock Text="Darstellung" FontSize="16" FontWeight="SemiBold"/>
|
||||
<TextBlock FontSize="12" Opacity="0.6" TextWrapping="Wrap"
|
||||
Text="Wechselt sofort, ohne Neustart."/>
|
||||
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="Design" FontSize="12" Opacity="0.7"/>
|
||||
<ComboBox ItemsSource="{Binding ThemeOptions}" SelectedItem="{Binding SelectedThemeName}"
|
||||
HorizontalAlignment="Left" MinWidth="180"/>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</ContentPage>
|
||||
|
||||
<!-- Tab: Papierkorb (14.3) -->
|
||||
<ContentPage Header="Papierkorb">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="32,20,32,28" Spacing="14" MaxWidth="560" DataContext="{Binding TrashTab}"
|
||||
x:DataType="vm:TrashViewModel">
|
||||
|
||||
<TextBlock Text="Papierkorb" FontSize="16" FontWeight="SemiBold"/>
|
||||
<TextBlock FontSize="12" Opacity="0.6" TextWrapping="Wrap"
|
||||
Text="Gelöschte Noten, Aufgaben, Zeiteinträge, Sitzpläne und Notenschlüssel-Vorlagen bleiben 30 Tage wiederherstellbar, danach werden sie endgültig entfernt. Andere Löschvorgänge (z. B. eine ganze Lerngruppe) sind davon nicht erfasst."/>
|
||||
|
||||
<TextBlock Text="{Binding Status}" Foreground="Green" FontSize="12"
|
||||
IsVisible="{Binding Status, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
|
||||
<TextBlock Text="Der Papierkorb ist leer." Classes="emptyhint"
|
||||
IsVisible="{Binding !HasItems}"/>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding Items}" IsVisible="{Binding HasItems}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:TrashItemViewModel">
|
||||
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
|
||||
BorderThickness="0,0,0,1" Padding="0,8">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0" Spacing="2">
|
||||
<TextBlock FontSize="13">
|
||||
<Run Text="{Binding TypeLabel}" FontWeight="SemiBold"/><Run Text=": "/><Run Text="{Binding Summary}"/>
|
||||
</TextBlock>
|
||||
<TextBlock FontSize="11" Opacity="0.6">
|
||||
<Run Text="Gelöscht am "/><Run Text="{Binding DeletedAtDisplay}"/><Run Text=" · "/><Run Text="{Binding ExpiresInDisplay}"/>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Content="Wiederherstellen" FontSize="12" Padding="10,4"
|
||||
Command="{Binding $parent[ItemsControl].((vm:TrashViewModel)DataContext).RestoreCommand}"
|
||||
CommandParameter="{Binding}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</ContentPage>
|
||||
</TabbedPage>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -25,6 +25,7 @@ public partial class SettingsView : UserControl
|
||||
vm.OnSaveRecoveryFile = SaveRecoveryFile;
|
||||
vm.OnPickRecoveryFile = PickRecoveryFile;
|
||||
vm.OnConfirmRecoveryRestore = ShowRecoveryRestoreConfirmDialog;
|
||||
vm.OnThemeChanged = App.ApplyTheme;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user