feat(core): reworked main window's messages

Some messages (relm4 component's input commands) were renamed

Added `UpdateLauncherState` message to, well, update launcher state.
By calling this status page will appear with information about
current status getting progress

Added some new error messages. Before, heavy tasks
were sending errors only to tracing logs. Now they also will generate toast messages

Added patch applying functionality to the main button.
Also made main window disappear when the game is running (like it works now in GTK launcher)
This commit is contained in:
Observer KRypt0n_ 2023-02-25 18:08:25 +02:00
parent c7a92718db
commit 0e07cb0698
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
5 changed files with 267 additions and 120 deletions

View file

@ -10,6 +10,10 @@ config-update-error = Failed to save config
wine-prefix-update-failed = Failed to update wine prefix
dxvk-install-failed = Failed to install DXVK
game-diff-finding-error = Failed to find game diff
patch-info-fetching-error = Failed to fetch patch info
launcher-state-updating-error = Failed to update launcher state
package-not-available = Package is not available: {$package}
wine-download-error = Failed to download wine
wine-unpack-errror = Failed to unpack wine
@ -17,3 +21,7 @@ wine-install-failed = Failed to install wine
dxvk-download-error = Failed to download dxvk
dxvk-unpack-error = Failed to unpack dxvk
dxvk-apply-error = Failed to apply DXVK
patch-sync-failed = Failed to sync patch folder
patch-state-check-failed = Failed to check patch folder state
game-patching-error = Failed to patch game

View file

@ -10,6 +10,10 @@ config-update-error = Ошибка сохранения настроек
wine-prefix-update-failed = Ошибка обновления префикса Wine
dxvk-install-failed = Ошибка установки DXVK
game-diff-finding-error = Не удалось вычислить обновление игры
patch-info-fetching-error = Не удалось получить информацию о патче
launcher-state-updating-error = Не удалось обновить состояние лаунчера
package-not-available = Пакет недоступен: {$package}
wine-download-error = Ошибка скачивания Wine
wine-unpack-errror = Ошибка распаковки Wine
@ -17,3 +21,7 @@ wine-install-failed = Ошибка установки Wine
dxvk-download-error = Ошибка скачивания DXVK
dxvk-unpack-error = Ошибка распаковки DXVK
dxvk-apply-error = Не удалось применить DXVK
patch-sync-failed = Ошибка синхронизации папки патча
patch-state-check-failed = Ошибка проверки статуса папки патча
game-patching-error = Не удалось установить патч игры

View file

@ -37,32 +37,38 @@ pub struct App {
loading: Option<Option<String>>,
style: LauncherStyle,
state: Option<LauncherState>
state: Option<LauncherState>,
disable_buttons: bool
}
#[derive(Debug)]
pub enum AppMsg {
UpdateLauncherState,
/// Supposed to be called automatically on app's run when the latest game version
/// was retrieved from the API
UpdateGameDiff(Option<VersionDiff>),
SetGameDiff(Option<VersionDiff>),
/// Supposed to be called automatically on app's run when the latest patch version
/// was retrieved from remote repos
UpdatePatch(Option<Patch>),
SetPatch(Option<Patch>),
/// Supposed to be called automatically on app's run when the launcher state was chosen
UpdateLauncherState(Option<LauncherState>),
SetLauncherState(Option<LauncherState>),
SetLauncherStyle(LauncherStyle),
SetLoadingStatus(Option<Option<String>>),
OpenPreferences,
ClosePreferences,
DisableButtons(bool),
PerformAction,
Toast {
title: String,
description: Option<String>
},
UpdateLoadingStatus(Option<Option<String>>),
OpenPreferences,
ClosePreferences,
UpdateLauncherStyle(LauncherStyle),
PerformAction
}
}
#[relm4::component(pub)]
@ -282,6 +288,9 @@ impl SimpleComponent for App {
_ => String::new()
}),
#[watch]
set_sensitive: !model.disable_buttons,
set_hexpand: false,
set_width_request: 200,
@ -295,6 +304,9 @@ impl SimpleComponent for App {
LauncherStyle::Classic => 40
},
#[watch]
set_sensitive: !model.disable_buttons,
set_icon_name: "emblem-system-symbolic",
connect_clicked => AppMsg::OpenPreferences
@ -319,7 +331,9 @@ impl SimpleComponent for App {
loading: Some(None),
style: CONFIG.launcher.style,
state: None
state: None,
disable_buttons: false
};
let toast_overlay = &model.toast_overlay;
@ -408,7 +422,7 @@ impl SimpleComponent for App {
// Download background picture if needed
if download_picture {
sender.input(AppMsg::UpdateLoadingStatus(Some(Some(tr("downloading-background-picture")))));
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr("downloading-background-picture")))));
if let Err(err) = crate::background::download_background() {
tracing::error!("Failed to download background picture");
@ -422,12 +436,17 @@ impl SimpleComponent for App {
// Update initial game version status
sender.input(AppMsg::UpdateLoadingStatus(Some(Some(tr("loading-game-version")))));
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr("loading-game-version")))));
sender.input(AppMsg::UpdateGameDiff(match GAME.try_get_diff() {
sender.input(AppMsg::SetGameDiff(match GAME.try_get_diff() {
Ok(diff) => Some(diff),
Err(err) => {
tracing::error!("Failed to get game diff: {err}");
tracing::error!("Failed to find game diff: {err}");
sender.input(AppMsg::Toast {
title: tr("game-diff-finding-error"),
description: Some(err.to_string())
});
None
}
@ -437,13 +456,18 @@ impl SimpleComponent for App {
// Update initial patch status
sender.input(AppMsg::UpdateLoadingStatus(Some(Some(tr("loading-patch-status")))));
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr("loading-patch-status")))));
sender.input(AppMsg::UpdatePatch(match Patch::try_fetch(&CONFIG.patch.servers, None) {
sender.input(AppMsg::SetPatch(match Patch::try_fetch(&CONFIG.patch.servers, None) {
Ok(patch) => Some(patch),
Err(err) => {
tracing::error!("Failed to fetch patch info: {err}");
sender.input(AppMsg::Toast {
title: tr("patch-info-fetching-error"),
description: Some(err.to_string())
});
None
}
}));
@ -451,42 +475,7 @@ impl SimpleComponent for App {
tracing::info!("Updated patch status");
// Update launcher state
let updater = clone!(@strong sender => move |state| {
use anime_launcher_sdk::states::StateUpdating;
match state {
StateUpdating::Game => {
sender.input(AppMsg::UpdateLoadingStatus(Some(Some(tr("loading-launcher-state--game")))));
}
StateUpdating::Voice(locale) => {
sender.input(AppMsg::UpdateLoadingStatus(Some(Some(tr_args("loading-launcher-state--voice", [
("locale", locale.to_name().to_owned().into())
])))));
}
StateUpdating::Patch => {
sender.input(AppMsg::UpdateLoadingStatus(Some(Some(tr("loading-launcher-state--patch")))));
}
}
});
sender.input(AppMsg::UpdateLoadingStatus(Some(Some(tr("loading-launcher-state")))));
sender.input(AppMsg::UpdateLauncherState(match LauncherState::get_from_config(updater) {
Ok(state) => Some(state),
Err(err) => {
tracing::error!("Failed to fetch patch info: {err}");
None
}
}));
tracing::info!("Updated launcher state");
// Hide loading page
sender.input(AppMsg::UpdateLoadingStatus(None));
sender.input(AppMsg::UpdateLauncherState);
// Mark app as loaded
unsafe {
@ -503,20 +492,203 @@ impl SimpleComponent for App {
tracing::debug!("Called main window event: {:?}", msg);
match msg {
#[allow(unused_must_use)]
AppMsg::UpdateGameDiff(diff) => unsafe {
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().sender().send(PreferencesAppMsg::UpdateGameDiff(diff));
AppMsg::UpdateLauncherState => {
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr("loading-launcher-state")))));
let updater = clone!(@strong sender => move |state| {
use anime_launcher_sdk::states::StateUpdating;
match state {
StateUpdating::Game => {
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr("loading-launcher-state--game")))));
}
StateUpdating::Voice(locale) => {
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr_args("loading-launcher-state--voice", [
("locale", locale.to_name().to_owned().into())
])))));
}
StateUpdating::Patch => {
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr("loading-launcher-state--patch")))));
}
}
});
let state = match LauncherState::get_from_config(updater) {
Ok(state) => Some(state),
Err(err) => {
tracing::error!("Failed to update launcher state: {err}");
sender.input(AppMsg::Toast {
title: tr("launcher-state-updating-error"),
description: Some(err.to_string())
});
None
}
};
sender.input(AppMsg::SetLauncherState(state));
sender.input(AppMsg::SetLoadingStatus(None));
}
#[allow(unused_must_use)]
AppMsg::UpdatePatch(patch) => unsafe {
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().sender().send(PreferencesAppMsg::UpdatePatch(patch));
AppMsg::SetGameDiff(diff) => unsafe {
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().sender().send(PreferencesAppMsg::SetGameDiff(diff));
}
AppMsg::UpdateLauncherState(state) => {
#[allow(unused_must_use)]
AppMsg::SetPatch(patch) => unsafe {
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().sender().send(PreferencesAppMsg::SetPatch(patch));
}
AppMsg::SetLauncherState(state) => {
self.state = state;
}
AppMsg::SetLoadingStatus(status) => {
self.loading = status;
}
AppMsg::SetLauncherStyle(style) => {
self.style = style;
}
AppMsg::OpenPreferences => unsafe {
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().widget().show();
}
AppMsg::ClosePreferences => unsafe {
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().widget().hide();
}
AppMsg::DisableButtons(state) => {
self.disable_buttons = state;
}
AppMsg::PerformAction => unsafe {
match self.state.as_ref().unwrap_unchecked() {
LauncherState::PatchAvailable(Patch::NotAvailable) |
LauncherState::PredownloadAvailable { .. } |
LauncherState::Launch => {
if let Err(err) = anime_launcher_sdk::game::run() {
tracing::error!("Failed to launch game: {err}");
sender.input(AppMsg::Toast {
title: tr("game-launching-failed"),
description: Some(err.to_string())
});
}
else {
MAIN_WINDOW.as_ref().unwrap_unchecked().hide();
std::thread::sleep(std::time::Duration::from_secs(2));
/*if config.launcher.discord_rpc.enabled {
this.widgets.preferences_stack.enhancements_page.discord_rpc.update(RpcUpdates::Connect);
}*/
loop {
std::thread::sleep(std::time::Duration::from_secs(3));
match std::process::Command::new("ps").arg("-A").stdout(std::process::Stdio::piped()).output() {
Ok(output) => {
let output = String::from_utf8_lossy(&output.stdout);
if !output.contains("GenshinImpact.e") && !output.contains("unlocker.exe") {
break;
}
}
Err(_) => break
}
}
/*if config.launcher.discord_rpc.enabled {
this.widgets.preferences_stack.enhancements_page.discord_rpc.update(RpcUpdates::Disconnect);
}*/
MAIN_WINDOW.as_ref().unwrap_unchecked().show();
}
}
LauncherState::PatchAvailable(patch) => {
match patch.to_owned() {
Patch::NotAvailable |
Patch::Outdated { .. } |
Patch::Preparation { .. } => unreachable!(),
Patch::Testing { version, host, .. } |
Patch::Available { version, host, .. } => {
self.disable_buttons = true;
let config = config::get().unwrap();
std::thread::spawn(move || {
let applier = PatchApplier::new(&config.patch.path);
let mut synced = false;
match applier.is_sync_with(&host) {
Ok(true) => synced = true,
Ok(false) => {
match applier.sync(host) {
Ok(true) => synced = true,
Ok(false) => {
sender.input(AppMsg::Toast {
title: tr("patch-sync-failed"),
description: None
});
}
Err(err) => {
sender.input(AppMsg::Toast {
title: tr("patch-sync-failed"),
description: Some(err.to_string())
});
}
}
}
Err(err) => {
sender.input(AppMsg::Toast {
title: tr("patch-state-check-failed"),
description: Some(err.to_string())
});
}
}
if synced {
if let Err(err) = applier.apply(&config.game.path, version, config.patch.root) {
sender.input(AppMsg::Toast {
title: tr("game-patching-error"),
description: Some(err.to_string())
});
}
}
sender.input(AppMsg::DisableButtons(false));
sender.input(AppMsg::UpdateLauncherState);
});
}
}
}
LauncherState::WineNotInstalled => todo!(),
LauncherState::PrefixNotExists => todo!(),
LauncherState::VoiceUpdateAvailable(_) => todo!(),
LauncherState::VoiceOutdated(_) => todo!(),
LauncherState::VoiceNotInstalled(_) => todo!(),
LauncherState::GameUpdateAvailable(_) => todo!(),
LauncherState::GameOutdated(_) => todo!(),
LauncherState::GameNotInstalled(_) => todo!(),
}
}
AppMsg::Toast { title, description } => unsafe {
let toast = adw::Toast::new(&title);
@ -550,48 +722,6 @@ impl SimpleComponent for App {
self.toast_overlay.add_toast(&toast);
}
AppMsg::UpdateLoadingStatus(status) => {
self.loading = status;
}
AppMsg::OpenPreferences => unsafe {
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().widget().show();
}
AppMsg::ClosePreferences => unsafe {
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().widget().hide();
}
AppMsg::UpdateLauncherStyle(style) => {
self.style = style;
}
AppMsg::PerformAction => unsafe {
match self.state.as_ref().unwrap_unchecked() {
LauncherState::Launch => {
if let Err(err) = anime_launcher_sdk::game::run() {
sender.input(AppMsg::Toast {
title: tr("game-launching-failed"),
description: Some(err.to_string())
});
tracing::error!("Failed to launch game: {err}");
}
}
LauncherState::PredownloadAvailable { .. } => todo!(),
LauncherState::PatchAvailable(_) => todo!(),
LauncherState::WineNotInstalled => todo!(),
LauncherState::PrefixNotExists => todo!(),
LauncherState::VoiceUpdateAvailable(_) => todo!(),
LauncherState::VoiceOutdated(_) => todo!(),
LauncherState::VoiceNotInstalled(_) => todo!(),
LauncherState::GameUpdateAvailable(_) => todo!(),
LauncherState::GameOutdated(_) => todo!(),
LauncherState::GameNotInstalled(_) => todo!(),
}
}
}
}
}

View file

@ -38,11 +38,11 @@ pub struct GeneralApp {
pub enum GeneralAppMsg {
/// Supposed to be called automatically on app's run when the latest game version
/// was retrieved from the API
UpdateGameDiff(Option<VersionDiff>),
SetGameDiff(Option<VersionDiff>),
/// Supposed to be called automatically on app's run when the latest patch version
/// was retrieved from remote repos
UpdatePatch(Option<Patch>),
SetPatch(Option<Patch>),
Toast {
title: String,
@ -522,11 +522,11 @@ impl SimpleAsyncComponent for GeneralApp {
tracing::debug!("Called general settings event: {:?}", msg);
match msg {
GeneralAppMsg::UpdateGameDiff(diff) => {
GeneralAppMsg::SetGameDiff(diff) => {
self.game_diff = diff;
}
GeneralAppMsg::UpdatePatch(patch) => {
GeneralAppMsg::SetPatch(patch) => {
self.patch = patch;
}
@ -553,7 +553,7 @@ impl SimpleAsyncComponent for GeneralApp {
self.style = style;
sender.output(Self::Output::UpdateLauncherStyle(style));
sender.output(Self::Output::SetLauncherStyle(style));
}
#[allow(unused_must_use)]

View file

@ -24,17 +24,18 @@ pub struct PreferencesApp {
pub enum PreferencesAppMsg {
/// Supposed to be called automatically on app's run when the latest game version
/// was retrieved from the API
UpdateGameDiff(Option<VersionDiff>),
SetGameDiff(Option<VersionDiff>),
/// Supposed to be called automatically on app's run when the latest patch version
/// was retrieved from remote repos
UpdatePatch(Option<Patch>),
SetPatch(Option<Patch>),
SetLauncherStyle(LauncherStyle),
Toast {
title: String,
description: Option<String>
},
UpdateLauncherStyle(LauncherStyle)
}
}
#[relm4::component(async, pub)]
@ -104,13 +105,18 @@ impl SimpleAsyncComponent for PreferencesApp {
match msg {
#[allow(unused_must_use)]
PreferencesAppMsg::UpdateGameDiff(diff) => {
self.general.sender().send(GeneralAppMsg::UpdateGameDiff(diff));
PreferencesAppMsg::SetGameDiff(diff) => {
self.general.sender().send(GeneralAppMsg::SetGameDiff(diff));
}
#[allow(unused_must_use)]
PreferencesAppMsg::UpdatePatch(patch) => {
self.general.sender().send(GeneralAppMsg::UpdatePatch(patch));
PreferencesAppMsg::SetPatch(patch) => {
self.general.sender().send(GeneralAppMsg::SetPatch(patch));
}
#[allow(unused_must_use)]
PreferencesAppMsg::SetLauncherStyle(style) => {
sender.output(Self::Output::SetLauncherStyle(style));
}
PreferencesAppMsg::Toast { title, description } => unsafe {
@ -146,11 +152,6 @@ impl SimpleAsyncComponent for PreferencesApp {
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().add_toast(&toast);
}
#[allow(unused_must_use)]
PreferencesAppMsg::UpdateLauncherStyle(style) => {
sender.output(Self::Output::UpdateLauncherStyle(style));
}
}
}
}