455 lines
19 KiB
C#
455 lines
19 KiB
C#
using System.Diagnostics;
|
||
using System.IO;
|
||
using System.Windows;
|
||
using System.Windows.Controls;
|
||
using System.Windows.Media;
|
||
using System.Windows.Media.Animation;
|
||
using System.Windows.Threading;
|
||
|
||
namespace GitManagerSetup;
|
||
|
||
public partial class MainWindow : Window
|
||
{
|
||
private readonly SetupMode _mode;
|
||
private int _page;
|
||
private bool _busy;
|
||
private bool _done;
|
||
private string? _installedExe;
|
||
|
||
private const int PageNumOptions = 1;
|
||
private const int PageNumProgress = 2;
|
||
|
||
public MainWindow(SetupMode mode)
|
||
{
|
||
InitializeComponent();
|
||
_mode = mode;
|
||
VersionText.Text = $"Version {Installer.Version}";
|
||
|
||
var installed = Installer.InstalledDir;
|
||
DirBox.Text = installed ?? Installer.DefaultDir;
|
||
DesktopLinkBox.IsChecked = installed is null || Installer.DesktopLinkExists;
|
||
|
||
if (installed is not null)
|
||
{
|
||
UpdateHint.Visibility = Visibility.Visible;
|
||
UpdateHintText.Text = Installer.InstalledVersion == Installer.Version
|
||
? $"Git Manager GUI {Installer.Version} ist bereits installiert und wird neu installiert. Zugangsdaten und Einstellungen bleiben erhalten."
|
||
: $"Die vorhandene Installation{(Installer.InstalledVersion is { } v ? $" (Version {v})" : "")} wird durch Version {Installer.Version} ersetzt. Zugangsdaten und Einstellungen bleiben erhalten.";
|
||
}
|
||
if (Installer.IsRunning(installed)) RunningHint.Visibility = Visibility.Visible;
|
||
|
||
switch (mode)
|
||
{
|
||
case SetupMode.Uninstall:
|
||
Title = WindowTitle.Text = "Git Manager GUI deinstallieren";
|
||
Steps.Visibility = Visibility.Collapsed;
|
||
ShowOnly(PageUninstall);
|
||
NextButton.Content = "Deinstallieren";
|
||
BackButton.Visibility = Visibility.Collapsed;
|
||
LoadDataInfo();
|
||
break;
|
||
case SetupMode.Update:
|
||
// aus der App heraus: ohne Rückfragen direkt aktualisieren und danach wieder starten
|
||
Title = WindowTitle.Text = "Git Manager GUI wird aktualisiert";
|
||
Steps.Visibility = Visibility.Collapsed;
|
||
Loaded += async (_, _) => await RunInstall(DirBox.Text, DesktopLinkBox.IsChecked == true, update: true);
|
||
ShowOnly(PageProgress);
|
||
ProgressTitle.Text = "Git Manager GUI wird aktualisiert …";
|
||
BackButton.Visibility = Visibility.Hidden;
|
||
break;
|
||
default:
|
||
ShowPage(0);
|
||
break;
|
||
}
|
||
}
|
||
|
||
// ---------- Seiten ----------
|
||
|
||
private UIElement? _shownPage;
|
||
|
||
/// <summary>Seite wechseln – mit kurzem Einblenden von rechts.</summary>
|
||
private void ShowOnly(UIElement page)
|
||
{
|
||
foreach (var p in new UIElement[] { PageWelcome, PageOptions, PageProgress, PageUninstall })
|
||
p.Visibility = p == page ? Visibility.Visible : Visibility.Collapsed;
|
||
if (page == _shownPage) return;
|
||
_shownPage = page;
|
||
|
||
var ease = new CubicEase { EasingMode = EasingMode.EaseOut };
|
||
var slide = new TranslateTransform(18, 0);
|
||
page.RenderTransform = slide;
|
||
slide.BeginAnimation(TranslateTransform.XProperty, new DoubleAnimation(18, 0, TimeSpan.FromMilliseconds(260)) { EasingFunction = ease });
|
||
page.BeginAnimation(OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(220)));
|
||
}
|
||
|
||
// Farben der Schritt-Anzeige in der (immer dunklen) Seitenleiste – Cyan wie in der App
|
||
private static readonly Brush StepDoneFill = Frozen(Color.FromRgb(0x58, 0xD5, 0xFF));
|
||
private static readonly Brush StepDoneGlyph = Frozen(Color.FromRgb(0x04, 0x10, 0x1F));
|
||
private static readonly Brush StepCurrentFill = Brushes.White;
|
||
private static readonly Brush StepCurrentText = Frozen(Color.FromRgb(0x13, 0x30, 0x5C));
|
||
private static readonly Brush StepUpcoming = Frozen(Color.FromRgb(0x70, 0x83, 0xA4));
|
||
private static readonly Brush StepUpcomingStroke = Frozen(Color.FromRgb(0x3A, 0x4E, 0x72));
|
||
private static readonly Brush StepDoneText = Frozen(Color.FromRgb(0xAE, 0xBD, 0xD8));
|
||
private static readonly Brush StepLineIdle = Frozen(Color.FromRgb(0x24, 0x36, 0x58));
|
||
|
||
private static Brush Frozen(Color c)
|
||
{
|
||
var b = new SolidColorBrush(c);
|
||
b.Freeze();
|
||
return b;
|
||
}
|
||
|
||
private void UpdateSteps(int page)
|
||
{
|
||
var dots = new[] { Dot0, Dot1, Dot2 };
|
||
var nums = new[] { Num0, Num1, Num2 };
|
||
var labels = new[] { Label0, Label1, Label2 };
|
||
var lines = new[] { Line0, Line1 };
|
||
var iconFont = (FontFamily)FindResource("IconFont");
|
||
var uiFont = (FontFamily)FindResource("UiFont");
|
||
|
||
for (int i = 0; i < dots.Length; i++)
|
||
{
|
||
bool done = i < page || (_done && i == page);
|
||
bool current = i == page && !done;
|
||
dots[i].Background = done ? StepDoneFill : current ? StepCurrentFill : Brushes.Transparent;
|
||
dots[i].BorderBrush = done ? StepDoneFill : current ? StepCurrentFill : StepUpcomingStroke;
|
||
nums[i].Text = done ? "" : (i + 1).ToString();
|
||
nums[i].FontFamily = done ? iconFont : uiFont;
|
||
nums[i].FontSize = done ? 11 : 12;
|
||
nums[i].Foreground = done ? StepDoneGlyph : current ? StepCurrentText : StepUpcoming;
|
||
labels[i].Foreground = current ? Brushes.White : done ? StepDoneText : StepUpcoming;
|
||
labels[i].FontWeight = current ? FontWeights.SemiBold : FontWeights.Normal;
|
||
}
|
||
for (int i = 0; i < lines.Length; i++)
|
||
lines[i].Background = i < page ? StepDoneFill : StepLineIdle;
|
||
}
|
||
|
||
private void ShowPage(int page)
|
||
{
|
||
_page = page;
|
||
ShowOnly(page switch { 0 => PageWelcome, PageNumOptions => PageOptions, _ => PageProgress });
|
||
UpdateSteps(page);
|
||
|
||
BackButton.Visibility = page == PageNumOptions ? Visibility.Visible : Visibility.Hidden;
|
||
NextButton.Content = page switch
|
||
{
|
||
PageNumOptions => "Installieren",
|
||
PageNumProgress => "Fertigstellen",
|
||
_ => "Weiter",
|
||
};
|
||
}
|
||
|
||
private void Back_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
if (!_busy && _page > 0) ShowPage(_page - 1);
|
||
}
|
||
|
||
private async void Next_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
if (_busy) return;
|
||
|
||
if (_mode == SetupMode.Uninstall)
|
||
{
|
||
if (_done) { Close(); return; }
|
||
await RunUninstall();
|
||
return;
|
||
}
|
||
if (_mode == SetupMode.Update)
|
||
{
|
||
Close(); // nur nach einem Fehler erreichbar
|
||
return;
|
||
}
|
||
|
||
switch (_page)
|
||
{
|
||
case 0: ShowPage(PageNumOptions); break;
|
||
case PageNumOptions:
|
||
var dir = DirBox.Text.Trim();
|
||
if (!IsFullPath(dir) || dir.IndexOfAny(Path.GetInvalidPathChars()) >= 0)
|
||
{
|
||
await Frage("Ungültiger Ordner", "Bitte einen vollständigen Ordner angeben, z. B. C:\\Programme\\Git Manager GUI.", "OK");
|
||
return;
|
||
}
|
||
if (dir.TrimEnd('\\', '/').Length > Installer.MaxDirLength)
|
||
{
|
||
await Frage("Ordnerpfad zu lang",
|
||
$"Der Pfad hat {dir.TrimEnd('\\', '/').Length} Zeichen, Windows erlaubt hier höchstens {Installer.MaxDirLength}. " +
|
||
"Bitte einen kürzeren Ordner wählen.", "OK");
|
||
return;
|
||
}
|
||
await RunInstall(dir, DesktopLinkBox.IsChecked == true, update: false);
|
||
break;
|
||
case PageNumProgress:
|
||
// Start im Hintergrund: Process.Start kann blockieren, solange Windows
|
||
// die EXE prüft – das Fenster darf dabei nie einfrieren
|
||
if (LaunchBox.IsChecked == true)
|
||
{
|
||
_busy = true;
|
||
NextButton.IsEnabled = false;
|
||
NextButton.Content = "Wird gestartet …";
|
||
await Task.Run(StartApp);
|
||
_busy = false;
|
||
}
|
||
Close();
|
||
break;
|
||
}
|
||
}
|
||
|
||
private void ChangeDir_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
using var dlg = new System.Windows.Forms.FolderBrowserDialog
|
||
{
|
||
Description = "Installationsordner wählen",
|
||
SelectedPath = Directory.Exists(DirBox.Text) ? DirBox.Text : Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||
ShowNewFolderButton = true,
|
||
};
|
||
if (dlg.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;
|
||
var chosen = dlg.SelectedPath;
|
||
// wie bei üblichen Installern: Unterordner "Git Manager GUI" anhängen
|
||
DirBox.Text = Path.GetFileName(chosen.TrimEnd('\\')).Equals(Installer.AppName, StringComparison.OrdinalIgnoreCase)
|
||
? chosen : Path.Combine(chosen, Installer.AppName);
|
||
}
|
||
|
||
// ---------- Installation ----------
|
||
|
||
private async Task RunInstall(string dir, bool desktop, bool update)
|
||
{
|
||
_busy = true;
|
||
if (!update) ShowPage(PageNumProgress);
|
||
SetButtons(false);
|
||
var progress = new Progress<(double Value, string Text)>(p => SetProgress(p.Value, p.Text));
|
||
try
|
||
{
|
||
await Task.Run(() => Installer.Install(dir, desktop, progress, waitForExit: update));
|
||
_installedExe = Path.Combine(dir, Installer.ExeName);
|
||
_done = true;
|
||
|
||
if (update)
|
||
{
|
||
ShowSuccess("Update abgeschlossen", $"Git Manager GUI {Installer.Version} wird gestartet …");
|
||
StartApp();
|
||
_busy = false;
|
||
var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1.5) };
|
||
timer.Tick += (_, _) => { timer.Stop(); Close(); };
|
||
timer.Start();
|
||
return;
|
||
}
|
||
|
||
ShowSuccess("Alles bereit!", "Git Manager GUI ist installiert. Du findest es im Startmenü" + (desktop ? " und auf dem Desktop." : "."));
|
||
DonePanel.Visibility = Visibility.Visible;
|
||
ShowPage(PageNumProgress);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ProgressTitle.Text = update ? "Update fehlgeschlagen" : "Installation fehlgeschlagen";
|
||
ProgressBarPanel.Visibility = Visibility.Collapsed;
|
||
ProgressText.Text = ex.Message;
|
||
ProgressText.Foreground = (Brush)FindResource("DangerBrush");
|
||
NextButton.Content = "Schließen";
|
||
_installedExe = null;
|
||
}
|
||
_busy = false;
|
||
NextButton.IsEnabled = true;
|
||
CloseButton.IsEnabled = true;
|
||
BackButton.Visibility = Visibility.Hidden;
|
||
CancelButton.Visibility = Visibility.Collapsed;
|
||
}
|
||
|
||
/// <summary>Fortschritt ausblenden und den Haken mit kurzem "Aufploppen" zeigen.</summary>
|
||
private void ShowSuccess(string title, string text)
|
||
{
|
||
ProgressBarPanel.Visibility = Visibility.Collapsed;
|
||
ProgressTitle.Text = title;
|
||
ProgressText.Text = text;
|
||
SuccessIcon.Visibility = Visibility.Visible;
|
||
var pop = new DoubleAnimation(0.4, 1, TimeSpan.FromMilliseconds(420)) { EasingFunction = new BackEase { EasingMode = EasingMode.EaseOut, Amplitude = 0.5 } };
|
||
SuccessScale.BeginAnimation(ScaleTransform.ScaleXProperty, pop);
|
||
SuccessScale.BeginAnimation(ScaleTransform.ScaleYProperty, pop);
|
||
SuccessIcon.BeginAnimation(OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(200)));
|
||
}
|
||
|
||
private void StartApp()
|
||
{
|
||
if (_installedExe is null || !File.Exists(_installedExe)) return;
|
||
try
|
||
{
|
||
Process.Start(new ProcessStartInfo(_installedExe)
|
||
{
|
||
UseShellExecute = true,
|
||
WorkingDirectory = Path.GetDirectoryName(_installedExe)!,
|
||
});
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
// ---------- Deinstallation ----------
|
||
|
||
private async void LoadDataInfo()
|
||
{
|
||
var root = Installer.DataDir;
|
||
if (!Directory.Exists(root))
|
||
{
|
||
DataInfo.Text = "Es wurden keine App-Daten gefunden.";
|
||
RemoveDataBox.IsEnabled = false;
|
||
return;
|
||
}
|
||
DataInfo.Text = $"Sie liegen in {root}. Größe wird berechnet …";
|
||
var size = await Task.Run(Installer.DataSize);
|
||
DataInfo.Text = $"Sie liegen in {root} ({Installer.FormatSize(size)}) und stehen wieder bereit, falls du Git Manager GUI später neu installierst.";
|
||
}
|
||
|
||
private void RemoveDataBox_Click(object sender, RoutedEventArgs e) =>
|
||
RemoveDataWarning.Visibility = RemoveDataBox.IsChecked == true ? Visibility.Visible : Visibility.Collapsed;
|
||
|
||
private async Task RunUninstall()
|
||
{
|
||
_busy = true;
|
||
bool removeData = RemoveDataBox.IsChecked == true;
|
||
ShowOnly(PageProgress);
|
||
ProgressTitle.Text = "Git Manager GUI wird entfernt …";
|
||
LaunchBox.Visibility = Visibility.Collapsed;
|
||
SetButtons(false);
|
||
var progress = new Progress<(double Value, string Text)>(p => SetProgress(p.Value, p.Text));
|
||
try
|
||
{
|
||
await Task.Run(() => Installer.Uninstall(removeData, progress));
|
||
ShowSuccess("Git Manager GUI wurde entfernt", removeData
|
||
? "Programm, Zugangsdaten und Einstellungen wurden entfernt (die Daten liegen im Papierkorb)."
|
||
: "Deine Zugangsdaten und Einstellungen liegen weiterhin in " + Installer.DataDir + " – falls du Git Manager GUI später wieder installierst.");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ProgressTitle.Text = "Deinstallation fehlgeschlagen";
|
||
ProgressBarPanel.Visibility = Visibility.Collapsed;
|
||
ProgressText.Text = ex.Message;
|
||
}
|
||
_done = true;
|
||
_busy = false;
|
||
CloseButton.IsEnabled = true;
|
||
NextButton.Content = "Schließen";
|
||
NextButton.IsEnabled = true;
|
||
CancelButton.Visibility = Visibility.Collapsed;
|
||
}
|
||
|
||
// ---------- Helfer ----------
|
||
|
||
private void SetButtons(bool enabled)
|
||
{
|
||
NextButton.IsEnabled = enabled;
|
||
BackButton.IsEnabled = enabled;
|
||
CancelButton.IsEnabled = enabled;
|
||
CloseButton.IsEnabled = enabled;
|
||
}
|
||
|
||
private void SetProgress(double value, string text)
|
||
{
|
||
if (_done) return;
|
||
ProgressText.Text = text;
|
||
PercentText.Text = $"{(int)Math.Round(value * 100)} %";
|
||
_progress = value;
|
||
AnimateProgress();
|
||
}
|
||
|
||
private double _progress;
|
||
|
||
private void AnimateProgress()
|
||
{
|
||
var target = Math.Max(0, ProgressTrack.ActualWidth * _progress);
|
||
ProgressFill.BeginAnimation(WidthProperty,
|
||
new DoubleAnimation(target, TimeSpan.FromMilliseconds(250)) { EasingFunction = new CubicEase() });
|
||
}
|
||
|
||
// Kommt der erste Wert, bevor die Seite fertig angeordnet ist (Breite 0), nachträglich richtig setzen
|
||
private void ProgressTrack_SizeChanged(object sender, SizeChangedEventArgs e) => AnimateProgress();
|
||
|
||
private static bool IsFullPath(string p) =>
|
||
p.Length >= 3 && char.IsLetter(p[0]) && p[1] == ':' && (p[2] == '\\' || p[2] == '/');
|
||
|
||
private void Minimize_Click(object sender, RoutedEventArgs e) => WindowState = WindowState.Minimized;
|
||
|
||
private async void Cancel_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
if (_busy || _dialog is not null) return;
|
||
if (!_done && _mode == SetupMode.Install && _page > 0)
|
||
{
|
||
bool abbrechen = await Frage("Installation abbrechen?",
|
||
"Git Manager GUI wird nicht installiert. Du kannst das Setup später jederzeit erneut starten.",
|
||
"Ja, abbrechen", "Weiter installieren", "\uE897");
|
||
if (!abbrechen) return;
|
||
}
|
||
Close();
|
||
}
|
||
|
||
// ---------- eigene Rückfragen im Setup-Look ----------
|
||
|
||
private TaskCompletionSource<bool>? _dialog;
|
||
|
||
/// <summary>
|
||
/// Zeigt eine Rückfrage über dem Fenster. Mit <paramref name="zurueck"/>: zwei Knöpfe –
|
||
/// der hervorgehobene ist der sichere Weg zurück (liefert false), der andere bestätigt (true).
|
||
/// Ohne: nur ein Hinweis mit einem Knopf (liefert true).
|
||
/// </summary>
|
||
private Task<bool> Frage(string titel, string text, string bestaetigen, string? zurueck = null, string symbol = "\uE7BA")
|
||
{
|
||
_dialog?.TrySetResult(false);
|
||
_dialog = new TaskCompletionSource<bool>();
|
||
DialogTitle.Text = titel;
|
||
DialogText.Text = text;
|
||
DialogIcon.Text = symbol;
|
||
if (zurueck is null)
|
||
{
|
||
DialogSecondary.Visibility = Visibility.Collapsed;
|
||
DialogPrimary.Content = bestaetigen;
|
||
}
|
||
else
|
||
{
|
||
DialogSecondary.Visibility = Visibility.Visible;
|
||
DialogSecondary.Content = bestaetigen;
|
||
DialogPrimary.Content = zurueck;
|
||
}
|
||
|
||
DialogOverlay.Visibility = Visibility.Visible;
|
||
var ein = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(160));
|
||
DialogOverlay.BeginAnimation(OpacityProperty, ein);
|
||
var groesse = new DoubleAnimation(0.94, 1, TimeSpan.FromMilliseconds(220)) { EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut } };
|
||
DialogScale.BeginAnimation(ScaleTransform.ScaleXProperty, groesse);
|
||
DialogScale.BeginAnimation(ScaleTransform.ScaleYProperty, groesse);
|
||
DialogPrimary.Focus();
|
||
return _dialog.Task;
|
||
}
|
||
|
||
private void SchliesseFrage(bool ergebnis)
|
||
{
|
||
if (_dialog is null) return;
|
||
var tcs = _dialog;
|
||
_dialog = null;
|
||
DialogOverlay.Visibility = Visibility.Collapsed;
|
||
tcs.TrySetResult(ergebnis);
|
||
}
|
||
|
||
// hervorgehobener Knopf: bei Rückfragen "zurück" (false), beim reinen Hinweis "OK" (true)
|
||
private void DialogPrimary_Click(object sender, RoutedEventArgs e) =>
|
||
SchliesseFrage(DialogSecondary.Visibility != Visibility.Visible);
|
||
|
||
private void DialogSecondary_Click(object sender, RoutedEventArgs e) => SchliesseFrage(true);
|
||
|
||
protected override void OnPreviewKeyDown(System.Windows.Input.KeyEventArgs e)
|
||
{
|
||
// solange die Rückfrage offen ist: Enter = hervorgehobener Knopf, Esc = zurück
|
||
if (_dialog is not null)
|
||
{
|
||
if (e.Key == System.Windows.Input.Key.Escape) { SchliesseFrage(DialogSecondary.Visibility != Visibility.Visible); e.Handled = true; return; }
|
||
if (e.Key == System.Windows.Input.Key.Enter) { DialogPrimary_Click(this, new RoutedEventArgs()); e.Handled = true; return; }
|
||
}
|
||
base.OnPreviewKeyDown(e);
|
||
}
|
||
|
||
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
|
||
{
|
||
if (_busy) e.Cancel = true; // während der Installation nicht schließen
|
||
_dialog?.TrySetResult(false);
|
||
base.OnClosing(e);
|
||
}
|
||
}
|