From f5231fe637ddb179fcd8eb7a1f95035627ab1774 Mon Sep 17 00:00:00 2001 From: Observer KRypt0n_ Date: Tue, 28 Feb 2023 22:42:08 +0200 Subject: [PATCH] feat: added voice packages selection to settings --- assets/locales/de/errors.ftl | 1 + assets/locales/en/errors.ftl | 1 + assets/locales/ru/errors.ftl | 1 + src/ui/main.rs | 4 + src/ui/preferences/general.rs | 238 +++++++++++++++++++++++++--------- src/ui/preferences/main.rs | 13 ++ 6 files changed, 198 insertions(+), 60 deletions(-) diff --git a/assets/locales/de/errors.ftl b/assets/locales/de/errors.ftl index 50456e6..acc4f24 100644 --- a/assets/locales/de/errors.ftl +++ b/assets/locales/de/errors.ftl @@ -12,6 +12,7 @@ background-downloading-failed = Download des Hintergrundbildes fehlgeschlagen config-update-error = Speichern der Konfiguration fehlgeschlagen wine-prefix-update-failed = Aktualisierung des wine prefix fehlgeschlagen dxvk-install-failed = DXVK konnte nicht installiert werden +voice-package-deletion-error = Failed to delete voice package game-diff-finding-error = Spiel-Diff nicht gefunden patch-info-fetching-error = Patch-Informationen konnten nicht abgerufen werden diff --git a/assets/locales/en/errors.ftl b/assets/locales/en/errors.ftl index cfffb5d..83fbbe7 100644 --- a/assets/locales/en/errors.ftl +++ b/assets/locales/en/errors.ftl @@ -12,6 +12,7 @@ background-downloading-failed = Failed to download background picture config-update-error = Failed to save config wine-prefix-update-failed = Failed to update wine prefix dxvk-install-failed = Failed to install DXVK +voice-package-deletion-error = Failed to delete voice package game-diff-finding-error = Failed to find game diff patch-info-fetching-error = Failed to fetch patch info diff --git a/assets/locales/ru/errors.ftl b/assets/locales/ru/errors.ftl index 4a1bb99..39cce4d 100644 --- a/assets/locales/ru/errors.ftl +++ b/assets/locales/ru/errors.ftl @@ -12,6 +12,7 @@ background-downloading-failed = Не удалось загрузить фоно config-update-error = Ошибка сохранения настроек wine-prefix-update-failed = Ошибка обновления префикса Wine dxvk-install-failed = Ошибка установки DXVK +voice-package-deletion-error = Не удалось удалить языковой пакет game-diff-finding-error = Не удалось вычислить обновление игры patch-info-fetching-error = Не удалось получить информацию о патче diff --git a/src/ui/main.rs b/src/ui/main.rs index 3ae1384..421fef6 100644 --- a/src/ui/main.rs +++ b/src/ui/main.rs @@ -637,6 +637,8 @@ impl SimpleComponent for App { AppMsg::UpdateLauncherState { perform_on_download_needed, show_status_page } => { if show_status_page { sender.input(AppMsg::SetLoadingStatus(Some(Some(tr("loading-launcher-state"))))); + } else { + self.disabled_buttons = true; } let updater = clone!(@strong sender => move |state| { @@ -676,6 +678,8 @@ impl SimpleComponent for App { if show_status_page { sender.input(AppMsg::SetLoadingStatus(None)); + } else { + self.disabled_buttons = false; } if perform_on_download_needed { diff --git a/src/ui/preferences/general.rs b/src/ui/preferences/general.rs index fff3ca0..82427c7 100644 --- a/src/ui/preferences/general.rs +++ b/src/ui/preferences/general.rs @@ -1,5 +1,10 @@ use relm4::prelude::*; use relm4::component::*; +use relm4::factory::{ + AsyncFactoryVecDeque, + AsyncFactoryComponent, + AsyncFactorySender +}; use gtk::prelude::*; use adw::prelude::*; @@ -10,12 +15,92 @@ use anime_launcher_sdk::anime_game_core::prelude::*; use anime_launcher_sdk::components::*; use anime_launcher_sdk::wincompatlib::prelude::*; +use super::main::PreferencesAppMsg; use crate::ui::components; use crate::ui::components::*; use crate::i18n::*; use crate::*; +#[derive(Debug)] +struct VoicePackageComponent { + locale: VoiceLocale, + installed: bool, + sensitive: bool +} + +#[relm4::factory(async)] +impl AsyncFactoryComponent for VoicePackageComponent { + type Init = (VoiceLocale, bool); + type Input = GeneralAppMsg; + type Output = GeneralAppMsg; + type CommandOutput = (); + type ParentInput = GeneralAppMsg; + type ParentWidget = adw::ExpanderRow; + + view! { + root = adw::ActionRow { + set_title: &tr(&self.locale.to_name().to_ascii_lowercase()), + + add_suffix = >k::Button { + #[watch] + set_visible: self.installed, + + #[watch] + set_sensitive: self.sensitive, + + set_icon_name: "user-trash-symbolic", + add_css_class: "flat", + set_valign: gtk::Align::Center, + + connect_clicked[sender, index] => move |_| { + sender.input(GeneralAppMsg::RemoveVoicePackage(index.clone())); + } + }, + + add_suffix = >k::Button { + #[watch] + set_visible: !self.installed, + + #[watch] + set_sensitive: self.sensitive, + + set_icon_name: "document-save-symbolic", + add_css_class: "flat", + set_valign: gtk::Align::Center, + + connect_clicked[sender, index] => move |_| { + sender.input(GeneralAppMsg::AddVoicePackage(index.clone())); + } + } + } + } + + fn output_to_parent_input(output: Self::Output) -> Option { + Some(output) + } + + async fn init_model( + init: Self::Init, + _index: &DynamicIndex, + _sender: AsyncFactorySender, + ) -> Self { + Self { + locale: init.0, + installed: init.1, + sensitive: true + } + } + + async fn update(&mut self, msg: Self::Input, sender: AsyncFactorySender) { + self.installed = !self.installed; + + sender.output(msg); + } +} + pub struct GeneralApp { + voice_packages: AsyncFactoryVecDeque, + wine_components: AsyncController>, dxvk_components: AsyncController>, @@ -46,27 +131,38 @@ pub enum GeneralAppMsg { /// was retrieved from remote repos SetPatch(Option), + // If one ever wich to change it to accept VoiceLocale + // I'd recommend to use clone!(@strong self.locale as locale => move |_| { .. }) + // in the VoicePackage component + AddVoicePackage(DynamicIndex), + RemoveVoicePackage(DynamicIndex), + SetVoicePackageSensitivity(DynamicIndex, bool), + + UpdateLauncherStyle(LauncherStyle), + + WineRecommendedOnly(bool), + DxvkRecommendedOnly(bool), + + UpdateDownloadedWine, + UpdateDownloadedDxvk, + + SelectWine(usize), + SelectDxvk(usize), + + ResetWineSelection(usize), + ResetDxvkSelection(usize), + Toast { title: String, description: Option - }, - - UpdateLauncherStyle(LauncherStyle), - WineRecommendedOnly(bool), - DxvkRecommendedOnly(bool), - UpdateDownloadedWine, - UpdateDownloadedDxvk, - SelectWine(usize), - SelectDxvk(usize), - ResetWineSelection(usize), - ResetDxvkSelection(usize) + } } #[relm4::component(async, pub)] impl SimpleAsyncComponent for GeneralApp { type Init = (); type Input = GeneralAppMsg; - type Output = super::main::PreferencesAppMsg; + type Output = PreferencesAppMsg; view! { adw::PreferencesPage { @@ -192,48 +288,9 @@ impl SimpleAsyncComponent for GeneralApp { } }, - adw::ExpanderRow { - set_title: &tr("game-voiceovers"), - - add_row = &adw::ActionRow { - set_title: &tr("english"), - - add_suffix = >k::Button { - add_css_class: "flat", - set_icon_name: "user-trash-symbolic", - set_valign: gtk::Align::Center - } - }, - - add_row = &adw::ActionRow { - set_title: &tr("japanese"), - - add_suffix = >k::Button { - add_css_class: "flat", - set_icon_name: "user-trash-symbolic", - set_valign: gtk::Align::Center - } - }, - - add_row = &adw::ActionRow { - set_title: &tr("korean"), - - add_suffix = >k::Button { - add_css_class: "flat", - set_icon_name: "user-trash-symbolic", - set_valign: gtk::Align::Center - } - }, - - add_row = &adw::ActionRow { - set_title: &tr("chinese"), - - add_suffix = >k::Button { - add_css_class: "flat", - set_icon_name: "user-trash-symbolic", - set_valign: gtk::Align::Center - } - } + #[local_ref] + voice_packages -> adw::ExpanderRow { + set_title: &tr("game-voiceovers") }, gtk::Box { @@ -474,7 +531,9 @@ impl SimpleAsyncComponent for GeneralApp { ) -> AsyncComponentParts { tracing::info!("Initializing general settings"); - let model = Self { + let mut model = Self { + voice_packages: AsyncFactoryVecDeque::new(adw::ExpanderRow::new(), sender.input_sender()), + wine_components: ComponentsList::builder() .launch(ComponentsListInit { pattern: ComponentsListPattern { @@ -514,6 +573,15 @@ impl SimpleAsyncComponent for GeneralApp { selecting_dxvk_version: false }; + for package in VoiceLocale::list() { + model.voice_packages.guard().push_back(( + *package, + CONFIG.game.voices.iter().any(|voice| VoiceLocale::from_str(voice) == Some(*package)) + )); + } + + let voice_packages = model.voice_packages.widget(); + let widgets = view_output!(); AsyncComponentParts { model, widgets } @@ -531,6 +599,56 @@ impl SimpleAsyncComponent for GeneralApp { self.patch = patch; } + #[allow(unused_must_use)] + GeneralAppMsg::AddVoicePackage(index) => { + if let Some(package) = self.voice_packages.get(index.current_index()) { + if let Ok(mut config) = config::get() { + if !config.game.voices.iter().any(|voice| VoiceLocale::from_str(voice) == Some(package.locale)) { + config.game.voices.push(package.locale.to_code().to_string()); + + config::update(config); + + sender.output(PreferencesAppMsg::UpdateLauncherState); + } + } + } + } + + #[allow(unused_must_use)] + GeneralAppMsg::RemoveVoicePackage(index) => { + if let Some(package) = self.voice_packages.guard().get_mut(index.current_index()) { + if let Ok(mut config) = config::get() { + package.sensitive = false; + + config.game.voices.retain(|voice| VoiceLocale::from_str(voice) != Some(package.locale)); + + config::update(config.clone()); + + let package = VoicePackage::with_locale(package.locale).unwrap(); + + std::thread::spawn(move || { + if let Err(err) = package.delete_in(&config.game.path) { + tracing::error!("Failed to delete voice package: {:?}", package.locale()); + + sender.input(GeneralAppMsg::Toast { + title: tr("voice-package-deletion-error"), + description: Some(err.to_string()) + }); + } + + sender.input(GeneralAppMsg::SetVoicePackageSensitivity(index, true)); + sender.output(PreferencesAppMsg::UpdateLauncherState); + }); + } + } + } + + GeneralAppMsg::SetVoicePackageSensitivity(index, sensitive) => { + if let Some(package) = self.voice_packages.guard().get_mut(index.current_index()) { + package.sensitive = sensitive; + } + } + #[allow(unused_must_use)] GeneralAppMsg::UpdateLauncherStyle(style) => { if style == LauncherStyle::Classic && !BACKGROUND_FILE.exists() { @@ -557,11 +675,6 @@ impl SimpleAsyncComponent for GeneralApp { sender.output(Self::Output::SetLauncherStyle(style)); } - #[allow(unused_must_use)] - GeneralAppMsg::Toast { title, description } => { - sender.output(Self::Output::Toast { title, description }); - } - GeneralAppMsg::WineRecommendedOnly(state) => { // todo self.wine_components.sender().send(components::list::AppMsg::ShowRecommendedOnly(state)).unwrap(); @@ -689,6 +802,11 @@ impl SimpleAsyncComponent for GeneralApp { self.selecting_dxvk_version = false; self.selected_dxvk_version = index as u32; } + + #[allow(unused_must_use)] + GeneralAppMsg::Toast { title, description } => { + sender.output(Self::Output::Toast { title, description }); + } } } } diff --git a/src/ui/preferences/main.rs b/src/ui/preferences/main.rs index 5128d9a..39a3d28 100644 --- a/src/ui/preferences/main.rs +++ b/src/ui/preferences/main.rs @@ -34,6 +34,8 @@ pub enum PreferencesAppMsg { SetLauncherStyle(LauncherStyle), + UpdateLauncherState, + Toast { title: String, description: Option @@ -53,6 +55,9 @@ impl SimpleAsyncComponent for PreferencesApp { set_hide_on_close: true, set_modal: true, + // FIXME: doesn't work for any reason + set_search_enabled: false, + add = model.general.widget(), add = model.enhancements.widget(), add = model.environment.widget(), @@ -126,6 +131,14 @@ impl SimpleAsyncComponent for PreferencesApp { sender.output(Self::Output::SetLauncherStyle(style)); } + #[allow(unused_must_use)] + PreferencesAppMsg::UpdateLauncherState => { + sender.output(Self::Output::UpdateLauncherState { + perform_on_download_needed: false, + show_status_page: false + }); + } + PreferencesAppMsg::Toast { title, description } => unsafe { let toast = adw::Toast::new(&title);