using ControlzEx.Theming; using Flurl.Http; using MahApps.Metro.Controls; using MahApps.Metro.Controls.Dialogs; using Microsoft.AppCenter.Analytics; using Microsoft.WindowsAPICodePack.Dialogs; using Newtonsoft.Json.Linq; using OwlCore.AbstractUI.Models; using Syroot.Windows.IO; using System; using System.Collections.Generic; using System.IO; using System.Windows; using System.Windows.Navigation; using ZuneModCore; namespace ZuneModdingHelper { /// /// Interaction logic for MainWindow.xaml /// public partial class MainWindow : MetroWindow { private readonly MetroDialogSettings defaultMetroDialogSettings = new() { ColorScheme = MetroDialogColorScheme.Accented, AnimateShow = true, AnimateHide = true, AffirmativeButtonText = "OK" }; public MainWindow() { InitializeComponent(); ThemeManager.Current.ThemeSyncMode = ThemeSyncMode.SyncWithAppMode; ThemeManager.Current.SyncTheme(); } private void Window_Loaded(object sender, RoutedEventArgs e) { ModList.ItemsSource = Mod.AvailableMods; ZuneInstallDirBox.Text = Mod.ZuneInstallDir; } private async void ModInstallButton_Click(object sender, RoutedEventArgs e) { var progDialog = await this.ShowProgressAsync("Getting ready...", "Preparing to apply mod", settings: defaultMetroDialogSettings); Mod mod = (Mod)((FrameworkElement)sender).DataContext; Mod.ZuneInstallDir = ZuneInstallDirBox.Text; progDialog.Maximum = 3; int numCompleted = 0; // Stage 0: Initialize mod progDialog.SetTitle($"Installing '{mod.Title}'"); progDialog.SetMessage("Preparing to install..."); await mod.Init(); progDialog.SetProgress(++numCompleted); // Stage 1: Display AbstractUI for options progDialog.SetMessage("Awaiting options..."); if (mod.OptionsUI != null) { var optionsDialog = new AbstractUIGroupDialog(mod.OptionsUI); bool? optionsResult = optionsDialog.ShowDialog(); if (!(optionsResult.HasValue && optionsResult.Value)) { await progDialog.CloseAsync(); return; } mod.OptionsUI = (AbstractUICollection)optionsDialog.ViewModel.Model; } progDialog.SetProgress(++numCompleted); // Stage 2: Apply mod progDialog.SetMessage("Applying mod..."); string applyResult = await mod.Apply(); if (applyResult != null) { Analytics.TrackEvent("Failed to apply mod", new Dictionary { { "ModID", mod.Id }, { "ErrorMessage", applyResult } }); await progDialog.CloseAsync(); await this.ShowMessageAsync($"Failed to apply '{mod.Title}'", applyResult, settings: defaultMetroDialogSettings); return; } progDialog.SetProgress(++numCompleted); Analytics.TrackEvent("Applied mod", new Dictionary { { "ModID", mod.Id }, }); await progDialog.CloseAsync(); await this.ShowMessageAsync("Completed", $"Installed '{mod.Title}'", settings: defaultMetroDialogSettings); } private async void ModResetButton_Click(object sender, RoutedEventArgs e) { if (sender is FrameworkElement elem && elem.DataContext is Mod mod) { var progDialog = await this.ShowProgressAsync("Getting ready...", "Preparing to reset mod", settings: defaultMetroDialogSettings); Mod.ZuneInstallDir = ZuneInstallDirBox.Text; progDialog.Maximum = 2; int numCompleted = 0; progDialog.SetTitle("Resetting mod..."); progDialog.SetMessage($"Setting up '{mod.Title}'"); await mod.Init(); progDialog.SetProgress(++numCompleted); // TODO: Implement AbstractUI display for options //if (mod.OptionsUI != null) //{ // var optionsDialog = new AbstractUIGroupDialog(); // optionsDialog.OptionsUIPresenter.ViewModel = new AbstractUICollectionViewModel(mod.OptionsUI); // optionsDialog.ShowDialog(); //} progDialog.SetMessage($"Resetting '{mod.Title}'"); string resetResult = await mod.Reset(); if (resetResult != null) { Analytics.TrackEvent("Failed to reset mod", new Dictionary { { "ModID", mod.Id }, { "ErrorMessage", resetResult } }); await progDialog.CloseAsync(); await this.ShowMessageAsync("Completed", $"Failed to reset '{mod.Title}':\r\n{resetResult}", settings: defaultMetroDialogSettings); return; } progDialog.SetProgress(++numCompleted); Analytics.TrackEvent("Reset mod", new Dictionary { { "ModID", mod.Id }, }); await progDialog.CloseAsync(); await this.ShowMessageAsync("Completed", $"Successfully reset '{mod.Title}'", settings: defaultMetroDialogSettings); } } private void AboutButton_Click(object sender, RoutedEventArgs e) { AboutFlyout.Width = Math.Max(Math.Min(500, ActualWidth), ActualWidth / 3); AboutFlyout.IsOpen = true; } private void Link_RequestNavigate(object sender, RequestNavigateEventArgs e) { App.OpenInBrowser(e.Uri.AbsoluteUri); e.Handled = true; } private async void UpdatesButton_Click(object sender, RoutedEventArgs e) { var checkDialog = await this.ShowProgressAsync("Checking for updates...", "Please wait.", settings: defaultMetroDialogSettings); checkDialog.SetIndeterminate(); try { // Get releases list from GitHub List releases = await "https://api.github.com/repos/ZuneDev/ZuneModdingHelper/releases" .WithHeader("User-Agent", App.Title.Replace(" ", "") + "/" + App.VersionStr) .GetJsonAsync>(); JObject latest = releases[0]; string latestVerStr = latest["tag_name"].Value(); if (!ReleaseVersion.TryParse(latestVerStr, out var latestVer) || App.Version >= latestVer) { // Already up-to-date Analytics.TrackEvent("Checked for updates", new Dictionary { { "UpdatesFound", bool.FalseString }, }); await checkDialog.CloseAsync(); await this.ShowMessageAsync("No updates available", "You're already using the latest version.", settings: defaultMetroDialogSettings); return; } // Newer version available, prompt user to download MetroDialogSettings promptSettings = new() { ColorScheme = MetroDialogColorScheme.Accented, AnimateShow = true, AnimateHide = true, AffirmativeButtonText = "Download", NegativeButtonText = "Later" }; await checkDialog.CloseAsync(); var promptResult = await this.ShowMessageAsync("Update available", $"Release {latest["name"]} is available. Would you like to download it now?", MessageDialogStyle.AffirmativeAndNegative, promptSettings); bool acceptedUpdate = promptResult == MessageDialogResult.Affirmative; if (acceptedUpdate) { var progDialog = await this.ShowProgressAsync("Downloading update...", "This may take a few minutes.", settings: defaultMetroDialogSettings); progDialog.Maximum = 100; progDialog.SetIndeterminate(); // Download new version to AppData JObject asset = latest["assets"].ToObject>()[0]; string assetName = asset["name"].Value(); string downloadedFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), assetName); if (File.Exists(downloadedFile)) File.Delete(downloadedFile); using (var client = new System.Net.WebClient()) { client.DownloadProgressChanged += (object sender, System.Net.DownloadProgressChangedEventArgs e) => { // Update UI with download progress progDialog.SetProgress(e.ProgressPercentage); }; progDialog.SetProgress(0); await client.DownloadFileTaskAsync(new Uri(asset["browser_download_url"].Value()), downloadedFile); } // Ask user to save file Microsoft.Win32.SaveFileDialog saveFileDialog = new() { FileName = assetName, InitialDirectory = new KnownFolder(KnownFolderType.Downloads).Path }; bool dialogResult = saveFileDialog.ShowDialog() ?? false; await progDialog.CloseAsync(); if (dialogResult) { File.Copy(downloadedFile, saveFileDialog.FileName, true); await this.ShowMessageAsync("Update complete", "You may now exit this program and open the new version.", settings: defaultMetroDialogSettings); } } Analytics.TrackEvent("Checked for updates", new Dictionary { { "UpdatesFound", bool.TrueString }, { "Accepted", acceptedUpdate.ToString() }, }); } catch { App.OpenInBrowser("https://github.com/ZuneDev/ZuneModdingHelper/releases"); } finally { if (checkDialog.IsOpen) await checkDialog.CloseAsync(); } } private void DonateButton_Click(object sender, RoutedEventArgs e) => App.OpenInBrowser(App.DonateLink); private void LocateZuneButton_Click(object sender, RoutedEventArgs e) { if (CommonFileDialog.IsPlatformSupported) { CommonOpenFileDialog dialog = new() { IsFolderPicker = true, DefaultDirectory = Mod.ZuneInstallDir }; CommonFileDialogResult result = dialog.ShowDialog(); if (result == CommonFileDialogResult.Ok) { ZuneInstallDirBox.Text = dialog.FileName; } } else { // TODO: Fallback to pre-Vista dialog this.ShowMessageAsync("Error", "Please use File Explorer to locate and copy the path.", settings: defaultMetroDialogSettings); } } } }