mirror of
https://github.com/ALEZ-DEV/Babylonia-terminal.git
synced 2025-12-16 09:28:53 +00:00
can now download dxvk, fonts and the denpendecies correctly
the fonts installation seems to fail, idk why because it work in ulterior version but not on this one and I have litteraly changed nothing, do idk what's going on, but I need to implement error handling and fix this little issue before continuing
This commit is contained in:
parent
fa242c1cca
commit
23acb09ef3
@ -4,7 +4,8 @@ use babylonia_terminal_sdk::{
|
|||||||
components::proton_component::ProtonComponent, game_config::GameConfig,
|
components::proton_component::ProtonComponent, game_config::GameConfig,
|
||||||
game_manager::GameManager, utils::github_requester::GithubRelease,
|
game_manager::GameManager, utils::github_requester::GithubRelease,
|
||||||
};
|
};
|
||||||
use log::error;
|
use downloader::download;
|
||||||
|
use log::{debug, error};
|
||||||
use relm4::{
|
use relm4::{
|
||||||
tokio::{self, sync::OnceCell},
|
tokio::{self, sync::OnceCell},
|
||||||
Worker,
|
Worker,
|
||||||
@ -117,6 +118,13 @@ impl Worker for HandleComponentInstallation {
|
|||||||
let _ = sender.output(
|
let _ = sender.output(
|
||||||
download_components::DownloadComponentsMsg::UpdateProgressBarMsg(
|
download_components::DownloadComponentsMsg::UpdateProgressBarMsg(
|
||||||
String::from("Starting download for proton"),
|
String::from("Starting download for proton"),
|
||||||
|
Some(String::from("Unpacking and initializing proton")),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = sender.output(
|
||||||
|
download_components::DownloadComponentsMsg::UpdateCurrentlyInstalling(
|
||||||
|
download_components::CurrentlyInstalling::Proton,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -132,17 +140,39 @@ impl Worker for HandleComponentInstallation {
|
|||||||
GameConfig::get_config_directory().await
|
GameConfig::get_config_directory().await
|
||||||
};
|
};
|
||||||
|
|
||||||
GameManager::install_wine(game_dir, proton_release, Some(progress_bar))
|
GameManager::install_wine(game_dir.clone(), proton_release, Some(progress_bar.clone()))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let _ = sender.output(
|
let _ = sender
|
||||||
download_components::DownloadComponentsMsg::UpdateProgressBarMsg(
|
.output(download_components::DownloadComponentsMsg::UpdateProgressBarMsg(String::from("Starting download for DXVK"), Some(String::from("Installing DXVK"))));
|
||||||
String::from("Unpacking proton"),
|
|
||||||
),
|
let _ = sender.output(download_components::DownloadComponentsMsg::UpdateCurrentlyInstalling(download_components::CurrentlyInstalling::DXVK));
|
||||||
);
|
|
||||||
|
let _ = sender.output(download_components::DownloadComponentsMsg::UpdateDownloadedComponentName(String::from("DXVK")));
|
||||||
|
|
||||||
|
GameManager::install_dxvk(&get_proton().await, game_dir, dxvk_release, Some(progress_bar.clone())).await;
|
||||||
|
|
||||||
let _ = sender
|
let _ = sender
|
||||||
.output(download_components::DownloadComponentsMsg::UpdateGameState);
|
.output(download_components::DownloadComponentsMsg::UpdateProgressBarMsg(String::from("Downloading and installing fonts"), Some(String::from("Fonts installed"))));
|
||||||
|
|
||||||
|
let _ = sender.output(download_components::DownloadComponentsMsg::UpdateCurrentlyInstalling(download_components::CurrentlyInstalling::Fonts));
|
||||||
|
|
||||||
|
let _ = sender.output(download_components::DownloadComponentsMsg::UpdateDownloadedComponentName(String::from("fonts")));
|
||||||
|
|
||||||
|
GameManager::install_font(&get_proton().await, Some(progress_bar.clone())).await;
|
||||||
|
|
||||||
|
let _ = sender
|
||||||
|
.output(download_components::DownloadComponentsMsg::UpdateProgressBarMsg(String::from("Download and installing denpendecies"), None));
|
||||||
|
|
||||||
|
let _ = sender.output(download_components::DownloadComponentsMsg::UpdateCurrentlyInstalling(download_components::CurrentlyInstalling::Denpendecies));
|
||||||
|
|
||||||
|
let _ = sender.output(download_components::DownloadComponentsMsg::UpdateDownloadedComponentName(String::from("denpendecies")));
|
||||||
|
|
||||||
|
GameManager::install_dependencies(&get_proton().await).await;
|
||||||
|
|
||||||
|
debug!("Finished to installing the components!");
|
||||||
|
|
||||||
|
let _ = sender.output(download_components::DownloadComponentsMsg::Finish);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -143,6 +143,7 @@ impl SimpleAsyncComponent for MainWindow {
|
|||||||
gtk::Box {
|
gtk::Box {
|
||||||
set_orientation: gtk::Orientation::Vertical,
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
|
||||||
|
#[watch]
|
||||||
set_visible: !model.game_state.is_environment_ready(),
|
set_visible: !model.game_state.is_environment_ready(),
|
||||||
|
|
||||||
model.setup_page.widget(),
|
model.setup_page.widget(),
|
||||||
@ -210,6 +211,8 @@ impl SimpleAsyncComponent for MainWindow {
|
|||||||
|
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
debug!("current GameState : {:?}", model.game_state);
|
||||||
|
|
||||||
AsyncComponentParts { model, widgets }
|
AsyncComponentParts { model, widgets }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,6 +242,10 @@ impl SimpleAsyncComponent for MainWindow {
|
|||||||
MainWindowMsg::SetIsGameRunning(value) => self.is_game_running = value,
|
MainWindowMsg::SetIsGameRunning(value) => self.is_game_running = value,
|
||||||
MainWindowMsg::UpdateGameState => {
|
MainWindowMsg::UpdateGameState => {
|
||||||
self.game_state = GameState::get_current_state().await.unwrap();
|
self.game_state = GameState::get_current_state().await.unwrap();
|
||||||
|
debug!(
|
||||||
|
"is_environment_ready : {}",
|
||||||
|
self.game_state.is_environment_ready()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,9 +6,12 @@ use babylonia_terminal_sdk::{
|
|||||||
proton_component::{self, ProtonComponent},
|
proton_component::{self, ProtonComponent},
|
||||||
},
|
},
|
||||||
game_state::GameState,
|
game_state::GameState,
|
||||||
utils::github_requester::{GithubRelease, GithubRequester},
|
utils::{
|
||||||
|
github_requester::{GithubRelease, GithubRequester},
|
||||||
|
kuro_prod_api::CurrentGameInfo,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use log::{error, info};
|
use log::{debug, error, info};
|
||||||
use relm4::{
|
use relm4::{
|
||||||
self,
|
self,
|
||||||
gtk::{self, prelude::*},
|
gtk::{self, prelude::*},
|
||||||
@ -25,18 +28,25 @@ use super::SetupPageMsg;
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum DownloadComponentsMsg {
|
pub enum DownloadComponentsMsg {
|
||||||
Next,
|
|
||||||
UpdateGameState,
|
|
||||||
UpdateProgressBar((u64, u64)), // current and max_progress
|
UpdateProgressBar((u64, u64)), // current and max_progress
|
||||||
UpdateProgressBarMsg(String),
|
UpdateProgressBarMsg(String, Option<String>), // current msg and when done msg
|
||||||
|
ShowDoneMsg,
|
||||||
UpdateDownloadedComponentName(String),
|
UpdateDownloadedComponentName(String),
|
||||||
|
UpdateCurrentlyInstalling(CurrentlyInstalling),
|
||||||
|
Finish,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum CurrentlyInstalling {
|
||||||
|
None,
|
||||||
|
Proton,
|
||||||
|
DXVK,
|
||||||
|
Fonts,
|
||||||
|
Denpendecies,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DownloadComponentsPage {
|
pub struct DownloadComponentsPage {
|
||||||
//state
|
|
||||||
game_state: GameState,
|
|
||||||
|
|
||||||
// widgets
|
// widgets
|
||||||
proton_combo: adw::ComboRow,
|
proton_combo: adw::ComboRow,
|
||||||
dxvk_combo: adw::ComboRow,
|
dxvk_combo: adw::ComboRow,
|
||||||
@ -54,9 +64,10 @@ pub struct DownloadComponentsPage {
|
|||||||
show_progress_bar: bool,
|
show_progress_bar: bool,
|
||||||
|
|
||||||
// download part
|
// download part
|
||||||
is_installing: bool,
|
|
||||||
installation_handler: WorkerController<manager::HandleComponentInstallation>,
|
installation_handler: WorkerController<manager::HandleComponentInstallation>,
|
||||||
downloaded_component_name: String,
|
downloaded_component_name: String,
|
||||||
|
currently_installing: CurrentlyInstalling,
|
||||||
|
msg_when_done: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[relm4::component(async, pub)]
|
#[relm4::component(async, pub)]
|
||||||
@ -65,7 +76,7 @@ impl SimpleAsyncComponent for DownloadComponentsPage {
|
|||||||
|
|
||||||
type Output = SetupPageMsg;
|
type Output = SetupPageMsg;
|
||||||
|
|
||||||
type Init = GameState;
|
type Init = ();
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
#[root]
|
#[root]
|
||||||
@ -73,7 +84,7 @@ impl SimpleAsyncComponent for DownloadComponentsPage {
|
|||||||
adw::PreferencesPage {
|
adw::PreferencesPage {
|
||||||
set_hexpand: true,
|
set_hexpand: true,
|
||||||
#[watch]
|
#[watch]
|
||||||
set_visible: !model.is_installing,
|
set_visible: model.currently_installing == CurrentlyInstalling::None,
|
||||||
|
|
||||||
add = &adw::PreferencesGroup {
|
add = &adw::PreferencesGroup {
|
||||||
set_valign: gtk::Align::Center,
|
set_valign: gtk::Align::Center,
|
||||||
@ -124,7 +135,7 @@ impl SimpleAsyncComponent for DownloadComponentsPage {
|
|||||||
set_hexpand: false,
|
set_hexpand: false,
|
||||||
set_width_request: 200,
|
set_width_request: 200,
|
||||||
|
|
||||||
connect_clicked => DownloadComponentsMsg::Next,
|
connect_clicked => DownloadComponentsMsg::UpdateCurrentlyInstalling(CurrentlyInstalling::Proton),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -132,7 +143,7 @@ impl SimpleAsyncComponent for DownloadComponentsPage {
|
|||||||
adw::PreferencesPage {
|
adw::PreferencesPage {
|
||||||
set_hexpand: true,
|
set_hexpand: true,
|
||||||
#[watch]
|
#[watch]
|
||||||
set_visible: model.is_installing,
|
set_visible: model.currently_installing != CurrentlyInstalling::None,
|
||||||
|
|
||||||
add = &adw::PreferencesGroup {
|
add = &adw::PreferencesGroup {
|
||||||
set_valign: gtk::Align::Center,
|
set_valign: gtk::Align::Center,
|
||||||
@ -149,21 +160,72 @@ impl SimpleAsyncComponent for DownloadComponentsPage {
|
|||||||
set_vexpand: true,
|
set_vexpand: true,
|
||||||
|
|
||||||
adw::ActionRow {
|
adw::ActionRow {
|
||||||
|
set_title: "Proton",
|
||||||
#[watch]
|
#[watch]
|
||||||
set_title: match &model.selected_proton_version {
|
set_subtitle: match &model.selected_proton_version {
|
||||||
Some(release) => &release.tag_name,
|
Some(release) => &release.tag_name,
|
||||||
None => "WTF??!! there's no proton version found ????",
|
None => "WTF??!! there's no proton version found ????",
|
||||||
},
|
},
|
||||||
set_subtitle: "Proton version",
|
|
||||||
|
|
||||||
#[watch]
|
#[watch]
|
||||||
set_icon_name: if model.game_state == GameState::ProtonNotInstalled { Some("emblem-ok-symbolic") } else { Some("process-working") },
|
set_icon_name: if model.currently_installing == CurrentlyInstalling::Proton { Some("process-working") } else { Some("emblem-ok-symbolic") },
|
||||||
|
|
||||||
add_prefix = >k::Spinner {
|
add_prefix = >k::Spinner {
|
||||||
set_spinning: true,
|
set_spinning: true,
|
||||||
|
|
||||||
#[watch]
|
#[watch]
|
||||||
set_visible: model.game_state == GameState::ProtonNotInstalled,
|
set_visible: model.currently_installing == CurrentlyInstalling::Proton,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
adw::ActionRow {
|
||||||
|
set_title: "DXVK",
|
||||||
|
#[watch]
|
||||||
|
set_subtitle: match &model.selected_dxvk_version {
|
||||||
|
Some(release) => &release.tag_name,
|
||||||
|
None => "WTF??!! there's no proton version found ????",
|
||||||
|
},
|
||||||
|
|
||||||
|
#[watch]
|
||||||
|
set_icon_name: if model.currently_installing == CurrentlyInstalling::DXVK { Some("process-working") } else { Some("emblem-ok-symbolic") },
|
||||||
|
|
||||||
|
add_prefix = >k::Spinner {
|
||||||
|
set_spinning: true,
|
||||||
|
|
||||||
|
#[watch]
|
||||||
|
set_visible: model.currently_installing == CurrentlyInstalling::DXVK,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
adw::ActionRow {
|
||||||
|
#[watch]
|
||||||
|
set_title: "Fonts",
|
||||||
|
set_subtitle: "Arial",
|
||||||
|
|
||||||
|
#[watch]
|
||||||
|
set_icon_name: if model.currently_installing == CurrentlyInstalling::Fonts { Some("process-working") } else { Some("emblem-ok-symbolic") },
|
||||||
|
|
||||||
|
add_prefix = >k::Spinner {
|
||||||
|
set_spinning: true,
|
||||||
|
|
||||||
|
#[watch]
|
||||||
|
set_visible: model.currently_installing == CurrentlyInstalling::Fonts,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
adw::ActionRow {
|
||||||
|
#[watch]
|
||||||
|
set_title: "vcrun2022",
|
||||||
|
set_subtitle: "Denpendecies",
|
||||||
|
|
||||||
|
#[watch]
|
||||||
|
set_icon_name: if model.currently_installing == CurrentlyInstalling::Denpendecies { Some("process-working") } else { Some("emblem-ok-symbolic") },
|
||||||
|
|
||||||
|
add_prefix = >k::Spinner {
|
||||||
|
set_spinning: true,
|
||||||
|
|
||||||
|
#[watch]
|
||||||
|
set_visible: model.currently_installing == CurrentlyInstalling::Denpendecies,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -172,6 +234,8 @@ impl SimpleAsyncComponent for DownloadComponentsPage {
|
|||||||
set_valign: gtk::Align::Center,
|
set_valign: gtk::Align::Center,
|
||||||
set_vexpand: true,
|
set_vexpand: true,
|
||||||
|
|
||||||
|
set_visible: model.currently_installing != CurrentlyInstalling::Fonts && model.currently_installing != CurrentlyInstalling::Denpendecies,
|
||||||
|
|
||||||
gtk::ProgressBar {
|
gtk::ProgressBar {
|
||||||
#[watch]
|
#[watch]
|
||||||
set_fraction: model.fraction,
|
set_fraction: model.fraction,
|
||||||
@ -186,7 +250,7 @@ impl SimpleAsyncComponent for DownloadComponentsPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn init(
|
async fn init(
|
||||||
game_state: Self::Init,
|
_: Self::Init,
|
||||||
root: Self::Root,
|
root: Self::Root,
|
||||||
sender: AsyncComponentSender<Self>,
|
sender: AsyncComponentSender<Self>,
|
||||||
) -> AsyncComponentParts<Self> {
|
) -> AsyncComponentParts<Self> {
|
||||||
@ -203,8 +267,6 @@ impl SimpleAsyncComponent for DownloadComponentsPage {
|
|||||||
.unwrap(); //TODO: remove unwrap()
|
.unwrap(); //TODO: remove unwrap()
|
||||||
|
|
||||||
let model = DownloadComponentsPage {
|
let model = DownloadComponentsPage {
|
||||||
game_state,
|
|
||||||
|
|
||||||
proton_combo: adw::ComboRow::new(),
|
proton_combo: adw::ComboRow::new(),
|
||||||
dxvk_combo: adw::ComboRow::new(),
|
dxvk_combo: adw::ComboRow::new(),
|
||||||
|
|
||||||
@ -218,11 +280,12 @@ impl SimpleAsyncComponent for DownloadComponentsPage {
|
|||||||
fraction: 0.0,
|
fraction: 0.0,
|
||||||
show_progress_bar: false,
|
show_progress_bar: false,
|
||||||
|
|
||||||
is_installing: false,
|
|
||||||
installation_handler: manager::HandleComponentInstallation::builder()
|
installation_handler: manager::HandleComponentInstallation::builder()
|
||||||
.detach_worker(())
|
.detach_worker(())
|
||||||
.forward(sender.input_sender(), identity),
|
.forward(sender.input_sender(), identity),
|
||||||
downloaded_component_name: String::new(),
|
downloaded_component_name: String::new(),
|
||||||
|
currently_installing: CurrentlyInstalling::None,
|
||||||
|
msg_when_done: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let proton_combo = &model.proton_combo;
|
let proton_combo = &model.proton_combo;
|
||||||
@ -235,10 +298,43 @@ impl SimpleAsyncComponent for DownloadComponentsPage {
|
|||||||
|
|
||||||
async fn update(&mut self, message: Self::Input, sender: AsyncComponentSender<Self>) -> () {
|
async fn update(&mut self, message: Self::Input, sender: AsyncComponentSender<Self>) -> () {
|
||||||
match message {
|
match message {
|
||||||
DownloadComponentsMsg::Next => {
|
DownloadComponentsMsg::UpdateDownloadedComponentName(name) => {
|
||||||
if !self.is_installing {
|
self.downloaded_component_name = name;
|
||||||
self.is_installing = true;
|
}
|
||||||
|
DownloadComponentsMsg::UpdateProgressBar((current, max_progress)) => {
|
||||||
|
self.fraction = if current == 0 {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
current as f64 / max_progress as f64
|
||||||
|
};
|
||||||
|
|
||||||
|
self.progress_bar_message = format!(
|
||||||
|
"Downloading {} : {:.2}%",
|
||||||
|
self.downloaded_component_name,
|
||||||
|
self.fraction * 100.0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
DownloadComponentsMsg::UpdateProgressBarMsg(message, message_when_done) => {
|
||||||
|
self.progress_bar_message = message;
|
||||||
|
self.msg_when_done = message_when_done;
|
||||||
|
}
|
||||||
|
DownloadComponentsMsg::ShowDoneMsg => {
|
||||||
|
if let Some(msg) = self.msg_when_done.clone() {
|
||||||
|
self.progress_bar_message = msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DownloadComponentsMsg::UpdateCurrentlyInstalling(currently_installing) => {
|
||||||
|
self.currently_installing = currently_installing;
|
||||||
|
}
|
||||||
|
DownloadComponentsMsg::Finish => {
|
||||||
|
let _ = sender.output(SetupPageMsg::Finish);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.selected_proton_version.is_none()
|
||||||
|
&& self.selected_dxvk_version.is_none()
|
||||||
|
&& self.currently_installing != CurrentlyInstalling::None
|
||||||
|
{
|
||||||
let proton_index = self.proton_combo.selected() as usize;
|
let proton_index = self.proton_combo.selected() as usize;
|
||||||
let dxvk_index = self.dxvk_combo.selected() as usize;
|
let dxvk_index = self.dxvk_combo.selected() as usize;
|
||||||
|
|
||||||
@ -254,32 +350,6 @@ impl SimpleAsyncComponent for DownloadComponentsPage {
|
|||||||
self.progress_bar_reporter.clone(),
|
self.progress_bar_reporter.clone(),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
let _ = sender.output(SetupPageMsg::Finish);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DownloadComponentsMsg::UpdateDownloadedComponentName(name) => {
|
|
||||||
self.downloaded_component_name = name;
|
|
||||||
}
|
|
||||||
DownloadComponentsMsg::UpdateGameState => {
|
|
||||||
self.game_state = GameState::get_current_state().await.unwrap();
|
|
||||||
}
|
|
||||||
DownloadComponentsMsg::UpdateProgressBar((current, max_progress)) => {
|
|
||||||
self.fraction = if current == 0 {
|
|
||||||
0.0
|
|
||||||
} else {
|
|
||||||
current as f64 / max_progress as f64
|
|
||||||
};
|
|
||||||
|
|
||||||
self.progress_bar_message = format!(
|
|
||||||
"Downloading {} : {:.2}%",
|
|
||||||
self.downloaded_component_name,
|
|
||||||
self.fraction * 100.0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
DownloadComponentsMsg::UpdateProgressBarMsg(message) => {
|
|
||||||
self.progress_bar_message = message;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -324,7 +394,7 @@ impl downloader::progress::Reporter for DownloadComponentProgressBarReporter {
|
|||||||
fn set_message(&self, message: &str) {}
|
fn set_message(&self, message: &str) {}
|
||||||
|
|
||||||
fn done(&self) {
|
fn done(&self) {
|
||||||
self.sender.input(DownloadComponentsMsg::Next);
|
self.sender.input(DownloadComponentsMsg::ShowDoneMsg);
|
||||||
let mut guard = self.private.lock().unwrap();
|
let mut guard = self.private.lock().unwrap();
|
||||||
*guard = None;
|
*guard = None;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -80,7 +80,7 @@ impl SimpleAsyncComponent for SetupPage {
|
|||||||
.launch(())
|
.launch(())
|
||||||
.forward(sender.input_sender(), identity);
|
.forward(sender.input_sender(), identity);
|
||||||
let download_components_page = DownloadComponentsPage::builder()
|
let download_components_page = DownloadComponentsPage::builder()
|
||||||
.launch(game_state.clone())
|
.launch(())
|
||||||
.forward(sender.input_sender(), identity);
|
.forward(sender.input_sender(), identity);
|
||||||
|
|
||||||
let carousel = adw::Carousel::new();
|
let carousel = adw::Carousel::new();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user