diff --git a/locales/de.yml b/locales/de.yml index 86599643..09413f92 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -29,6 +29,7 @@ choose_file: Datei auswählen crash_report: Absturzbericht crash_report_warning: Anwendung wurde beim letzten Mal unerwartet geschlossen, Sie können den Absturzbericht mit Entwicklern teilen. confirmation: Bestätigung +enter_url: URL eingeben wallets: await_conf_amount: Erwarte Bestätigung await_fin_amount: Warten auf die Fertigstellung @@ -292,6 +293,9 @@ modal: modal_exit: description: Sind Sie sicher, dass Sie die Anwendung beenden wollen? exit: Schließen +app_settings: + proxy: Proxy + proxy_desc: Lohnt es sich, einen Proxy für Netzwerkanfragen von der Anwendung zu verwenden. keyboard: 1: 1 2: 2 diff --git a/locales/en.yml b/locales/en.yml index 399779cb..407ba2aa 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -29,6 +29,7 @@ choose_file: Choose file crash_report: Crash report crash_report_warning: Application closed unexpectedly last time, you can share crash report with developers. confirmation: Confirmation +enter_url: Enter URL wallets: await_conf_amount: Awaiting confirmation await_fin_amount: Awaiting finalization @@ -292,6 +293,9 @@ modal: modal_exit: description: Are you sure you want to quit the application? exit: Exit +app_settings: + proxy: Proxy + proxy_desc: Whether to use proxy for network requests from the application. keyboard: 1: 1 2: 2 diff --git a/locales/fr.yml b/locales/fr.yml index eeae9964..e0c3b469 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -29,6 +29,7 @@ choose_file: Choisir un fichier crash_report: Rapport d'échec crash_report_warning: L'application s'est fermée de manière inattendue la dernière fois, vous pouvez partager un rapport d'incident avec les développeurs. confirmation: Confirmation +enter_url: Entrez l'URL wallets: await_conf_amount: En attente de confirmation await_fin_amount: En attente de finalisation @@ -292,6 +293,9 @@ modal: modal_exit: description: "Êtes-vous sûr de vouloir quitter l'application ?" exit: Quitter +app_settings: + proxy: Proxy + proxy_desc: Vaut-il la peine d'utiliser un proxy pour les requêtes réseau de l'application. keyboard: 1: 1 2: 2 diff --git a/locales/ru.yml b/locales/ru.yml index a3d819cd..3f7dadf1 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -29,6 +29,7 @@ choose_file: Выбрать файл crash_report: Отчёт о сбое crash_report_warning: В прошлый раз приложение неожиданно закрылось, вы можете поделиться отчетом о сбое с разработчиками. confirmation: Подтверждение +enter_url: Введите URL-адрес wallets: await_conf_amount: Ожидает подтверждения await_fin_amount: Ожидает завершения @@ -292,6 +293,9 @@ modal: modal_exit: description: Вы уверены, что хотите выйти из приложения? exit: Выход +app_settings: + proxy: Прокси + proxy_desc: Стоит ли использовать прокси для сетевых запросов из приложения. keyboard: 1: 1 2: 2 diff --git a/locales/tr.yml b/locales/tr.yml index 8d21e2b4..8edc5e7f 100644 --- a/locales/tr.yml +++ b/locales/tr.yml @@ -29,6 +29,7 @@ choose_file: Dosya seçin crash_report: Ariza Raporu crash_report_warning: Uygulama beklenmedik bir sekilde kapandi son kez, kilitlenme raporunu gelistiricilerle paylasabilirsiniz. confirmation: Onay +enter_url: URL'yi girin wallets: await_conf_amount: Onay bekleniyor await_fin_amount: Tamamlanma bekleniyor @@ -292,6 +293,9 @@ modal: modal_exit: description: Uygulamadan cikmak için exit, emin misiniz? exit: Exit +app_settings: + proxy: Proxy + proxy_desc: Uygulamadan gelen ağ istekleri için bir proxy kullanmaya değer mi. keyboard: 1: 1 2: 2 diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index a0fdcd09..574d9292 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -29,6 +29,7 @@ choose_file: 选择文件 crash_report: 崩溃报告 crash_report_warning: 上次应用程序意外关闭,您可以报告开发人员崩溃事件. confirmation: 确认 +enter_url: 输入 URL wallets: await_conf_amount: 等待确认中 await_fin_amount: 等待确定中 @@ -292,6 +293,9 @@ modal: modal_exit: description: 您确定要退出应用程序吗? exit: 退出手 +app_settings: + proxy: 代理 + proxy_desc: 是否值得对来自应用程序的网络请求使用代理. keyboard: 1: 1 2: 2 diff --git a/src/gui/views/settings/content.rs b/src/gui/views/settings/content.rs index cef29094..7f03ec5a 100644 --- a/src/gui/views/settings/content.rs +++ b/src/gui/views/settings/content.rs @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::gui::Colors; +use crate::gui::icons::{DATABASE, GLOBE_SIMPLE}; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::settings::interface::InterfaceSettingsContent; use crate::gui::views::settings::network::NetworkSettingsContent; use crate::gui::views::settings::storage::StorageSettingsContent; use crate::gui::views::types::ContentContainer; use crate::gui::views::View; +use crate::gui::Colors; /// Application settings content. pub struct SettingsContent { @@ -49,11 +50,23 @@ impl SettingsContent { ui.add_space(8.0); View::horizontal_line(ui, Colors::stroke()); ui.add_space(6.0); - + + View::sub_title(ui, format!("{} {}", GLOBE_SIMPLE, t!("network.self"))); + View::horizontal_line(ui, Colors::stroke()); + ui.add_space(6.0); + // Show network settings. - //self.network_settings.ui(ui, cb); + self.network_settings.ui(ui, cb); + + ui.add_space(8.0); + View::horizontal_line(ui, Colors::stroke()); + ui.add_space(6.0); + + // View::sub_title(ui, format!("{} {}", DATABASE, t!("network_node.data"))); + // View::horizontal_line(ui, Colors::stroke()); + // ui.add_space(6.0); // Show storage settings. - // self.storage_settings.ui(ui, cb); + self.storage_settings.ui(ui, cb); } } \ No newline at end of file diff --git a/src/gui/views/settings/interface.rs b/src/gui/views/settings/interface.rs index e79c584c..3e648f21 100644 --- a/src/gui/views/settings/interface.rs +++ b/src/gui/views/settings/interface.rs @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use egui::{Align, Layout, RichText, ScrollArea, StrokeKind}; use egui::scroll_area::ScrollBarVisibility; -use crate::gui::icons::{CHECK, CHECK_FAT, GLOBE_SIMPLE, PENCIL}; +use egui::{Align, Layout, RichText, ScrollArea, StrokeKind}; + +use crate::gui::icons::{CHECK, CHECK_FAT, PENCIL, TRANSLATE}; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::types::{ContentContainer, ModalPosition}; use crate::gui::views::{Modal, View}; @@ -64,14 +65,12 @@ impl ContentContainer for InterfaceSettingsContent { View::radio_value(ui, &mut selected_use_dark, true, t!("dark")); }) }); - ui.add_space(8.0); + ui.add_space(14.0); if saved_use_dark != selected_use_dark { AppConfig::set_dark_theme(selected_use_dark); crate::setup_visuals(ui.ctx()); } - ui.add_space(6.0); - // Draw language selection. self.language_item_ui(self.locale.clone().as_str(), ui, true, 0, 1); ui.add_space(4.0); @@ -155,17 +154,14 @@ impl InterfaceSettingsContent { ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| { ui.add_space(12.0); ui.vertical(|ui| { - // Draw connections URL. ui.add_space(4.0); View::ellipsize_text(ui, t!("lang_name", locale = locale), 18.0, Colors::title(false)); ui.add_space(1.0); - - // Setup connection status text. let value = format!("{} {}", - GLOBE_SIMPLE, + TRANSLATE, t!("language")); ui.label(RichText::new(value).size(15.0).color(Colors::gray())); ui.add_space(3.0); diff --git a/src/gui/views/settings/network.rs b/src/gui/views/settings/network.rs index 98ed4c04..cbdefb2e 100644 --- a/src/gui/views/settings/network.rs +++ b/src/gui/views/settings/network.rs @@ -12,38 +12,271 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::gui::icons::COMPUTER_TOWER; +use egui::{Align, Id, Layout, RichText, StrokeKind}; +use url::Url; + +use crate::gui::icons::{CLOUD_CHECK, CLOUD_SLASH, PENCIL}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::types::ContentContainer; -use crate::gui::views::{Modal, View}; +use crate::gui::views::types::{ContentContainer, ModalPosition}; +use crate::gui::views::{Modal, TextEdit, View}; use crate::gui::Colors; +use crate::AppConfig; /// Network communication settings content. pub struct NetworkSettingsContent { - + /// Proxy URL input value for [`Modal`]. + proxy_url_edit: String, + /// Flag to check if entered proxy address was correct. + proxy_url_error: bool, } +/// Identifier for proxy URL edit [`Modal`]. +const PROXY_URL_EDIT_MODAL: &'static str = "settings_proxy_edit_modal"; + impl ContentContainer for NetworkSettingsContent { fn modal_ids(&self) -> Vec<&'static str> { vec![ - + PROXY_URL_EDIT_MODAL ] } fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + match modal.id { + PROXY_URL_EDIT_MODAL => self.proxy_modal_ui(ui, cb), + _ => {} + } } - fn container_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { - View::sub_title(ui, format!("{} {}", COMPUTER_TOWER, t!("network.self"))); - View::horizontal_line(ui, Colors::stroke()); - ui.add_space(6.0); + fn container_ui(&mut self, ui: &mut egui::Ui, _: &dyn PlatformCallbacks) { + let use_proxy = AppConfig::use_proxy(); + View::checkbox(ui, use_proxy, t!("app_settings.proxy"), || { + // Show edit modal when both URLs are empty. + if AppConfig::http_proxy_url().is_none() && AppConfig::socks_proxy_url().is_none() && + !use_proxy { + Modal::new(PROXY_URL_EDIT_MODAL) + .position(ModalPosition::CenterTop) + .title(t!("app_settings.proxy")) + .show(); + } else { + AppConfig::toggle_use_proxy(); + } + }); + if !use_proxy { + ui.add_space(4.0); + ui.label(RichText::new(t!("app_settings.proxy_desc")) + .size(16.0) + .color(Colors::inactive_text()) + ); + ui.add_space(8.0); + } else { + ui.add_space(8.0); + + // Draw proxy type selection. + Self::proxy_type_ui(ui); + + // Draw proxy URL info. + self.proxy_item_ui(ui); + ui.add_space(6.0); + } } } impl Default for NetworkSettingsContent { fn default() -> Self { Self { - + proxy_url_edit: "".to_string(), + proxy_url_error: false, + } + } +} + +impl NetworkSettingsContent { + /// Draw proxy edit modal content. + fn proxy_modal_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { + let on_save = |c: &mut NetworkSettingsContent| { + // Check if URL proxy is correct. + let http = "http://"; + let socks = "socks5://"; + let proxy = c.proxy_url_edit.trim().to_string(); + let use_socks = AppConfig::use_socks_proxy(); + // Clear value if empty. + if proxy.is_empty() { + if use_socks { + AppConfig::save_socks_proxy_url(None); + } else { + AppConfig::save_http_proxy_url(None); + } + Modal::close(); + return; + } + let url = if use_socks { + let p = proxy.replace(http, ""); + if !p.contains(socks) { + format!("{}{}", socks, p) + } else { + p + } + } else { + let p = proxy.replace(socks, ""); + if !p.contains(http) { + format!("{}{}", http, p) + } else { + p + } + }; + c.proxy_url_error = Url::parse(url.as_str()).is_err(); + if !c.proxy_url_error { + // Save result when no error. + if !AppConfig::use_proxy() { + AppConfig::toggle_use_proxy(); + } + if use_socks { + AppConfig::save_socks_proxy_url(Some(url)) + } else { + AppConfig::save_http_proxy_url(Some(url)); + } + Modal::close(); + } + }; + + ui.add_space(6.0); + ui.vertical_centered(|ui| { + let label = format!("{}:", t!("enter_url")); + ui.label(RichText::new(label).size(17.0).color(Colors::gray())); + ui.add_space(8.0); + + // Draw proxy URL text edit. + let mut edit = TextEdit::new( + Id::from("proxy_url_edit") + .with(PROXY_URL_EDIT_MODAL) + .with(if AppConfig::use_proxy() { + "socks5" + } else { + "http" + }) + ).paste(); + edit.ui(ui, &mut self.proxy_url_edit, cb); + if edit.enter_pressed { + on_save(self); + } + + // Show error when specified address is incorrect. + if self.proxy_url_error { + ui.add_space(10.0); + ui.label(RichText::new(t!("wallets.invalid_url")) + .size(16.0) + .color(Colors::red())); + } + ui.add_space(12.0); + + // Show type selection when both URLs are empty. + if AppConfig::socks_proxy_url().is_none() && AppConfig::http_proxy_url().is_none() { + ui.add_space(6.0); + ui.vertical_centered(|ui| { + Self::proxy_type_ui(ui); + }); + ui.add_space(4.0); + } + + // Show modal buttons. + ui.scope(|ui| { + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); + + ui.columns(2, |columns| { + columns[0].vertical_centered_justified(|ui| { + View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { + Modal::close(); + }); + }); + columns[1].vertical_centered_justified(|ui| { + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); + }); + }); + ui.add_space(6.0); + }); + }); + } + + /// Draw language selection item content. + fn proxy_item_ui(&mut self, ui: &mut egui::Ui) { + // Setup layout size. + let mut rect = ui.available_rect_before_wrap(); + rect.set_height(56.0); + + // Draw round background. + let bg_rect = rect.clone(); + let item_rounding = View::item_rounding(0, 1, false); + ui.painter().rect(bg_rect, + item_rounding, + Colors::fill(), + View::item_stroke(), + StrokeKind::Middle); + + ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| { + View::item_button(ui, View::item_rounding(0, 1, true), PENCIL, None, || { + let url = if AppConfig::use_socks_proxy() { + AppConfig::socks_proxy_url().unwrap_or("".to_string()) + } else { + AppConfig::http_proxy_url().unwrap_or("".to_string()) + }; + self.proxy_url_edit = url; + // Show proxy URL edit modal. + Modal::new(PROXY_URL_EDIT_MODAL) + .position(ModalPosition::CenterTop) + .title(t!("app_settings.proxy")) + .show(); + }); + let layout_size = ui.available_size(); + ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| { + ui.add_space(12.0); + ui.vertical(|ui| { + ui.add_space(4.0); + let use_socks = AppConfig::use_socks_proxy(); + let proxy_url = if use_socks { + AppConfig::socks_proxy_url() + } else { + AppConfig::http_proxy_url() + }; + let (url, color, icon, text) = if let Some(url) = proxy_url { + (url, Colors::title(false), CLOUD_CHECK, t!("network_settings.enabled")) + } else { + ( + t!("enter_url"), + Colors::inactive_text(), + CLOUD_SLASH, + t!("network_settings.disabled") + ) + }; + View::ellipsize_text(ui, url, 18.0, color); + ui.add_space(1.0); + + let value = format!("{} {}", icon, text); + ui.label(RichText::new(value).size(15.0).color(Colors::gray())); + ui.add_space(3.0); + }); + }); + }); + } + + /// Draw proxy type selection. + fn proxy_type_ui(ui: &mut egui::Ui) { + // Draw proxy type selection. + let saved_use_socks = AppConfig::use_socks_proxy(); + let mut selected_use_socks = saved_use_socks; + ui.columns(2, |columns| { + columns[0].vertical_centered(|ui| { + View::radio_value(ui, &mut selected_use_socks, true, "SOCKS5".to_string()); + }); + columns[1].vertical_centered(|ui| { + View::radio_value(ui, &mut selected_use_socks, false, "HTTP".to_string()); + }) + }); + ui.add_space(14.0); + if saved_use_socks != selected_use_socks { + AppConfig::toggle_use_socks_proxy(); } } } \ No newline at end of file diff --git a/src/gui/views/settings/storage.rs b/src/gui/views/settings/storage.rs index 9587b69f..6e04c279 100644 --- a/src/gui/views/settings/storage.rs +++ b/src/gui/views/settings/storage.rs @@ -34,9 +34,7 @@ impl ContentContainer for StorageSettingsContent { } fn container_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { - View::sub_title(ui, format!("{} {}", DATABASE, t!("network_node.data"))); - View::horizontal_line(ui, Colors::stroke()); - ui.add_space(6.0); + } } diff --git a/src/settings/config.rs b/src/settings/config.rs index 0fa68474..3e15548f 100644 --- a/src/settings/config.rs +++ b/src/settings/config.rs @@ -52,6 +52,15 @@ pub struct AppConfig { /// Flag to check if dark theme should be used, use system settings if not set. use_dark_theme: Option, + + /// Flag to use proxy for network requests. + use_proxy: Option, + /// Flag to use SOCKS5 or HTTP proxy for network requests. + use_socks_proxy: Option, + /// HTTP proxy URL. + http_proxy_url: Option, + /// SOCKS5 proxy URL. + socks_proxy_url: Option, } impl Default for AppConfig { @@ -69,6 +78,10 @@ impl Default for AppConfig { lang: None, english_keyboard: None, use_dark_theme: None, + use_proxy: None, + use_socks_proxy: None, + http_proxy_url: None, + socks_proxy_url: None, } } } @@ -261,4 +274,59 @@ impl AppConfig { w_config.use_dark_theme = Some(use_dark); w_config.save(); } + + /// Check if proxy for network requests is needed. + pub fn use_proxy() -> bool { + let r_config = Settings::app_config_to_read(); + r_config.use_proxy.clone().unwrap_or(false) + } + + /// Enable or disable proxy for network requests. + pub fn toggle_use_proxy() { + let use_proxy = Self::use_proxy(); + let mut w_config = Settings::app_config_to_update(); + w_config.use_proxy = Some(!use_proxy); + w_config.save(); + } + + /// Check if SOCKS5 or HTTP proxy should be used. + pub fn use_socks_proxy() -> bool { + let r_config = Settings::app_config_to_read(); + r_config.use_socks_proxy.clone().unwrap_or(true) + } + + /// Enable SOCKS5 or HTTP proxy. + pub fn toggle_use_socks_proxy() { + let use_proxy = Self::use_socks_proxy(); + let mut w_config = Settings::app_config_to_update(); + w_config.use_socks_proxy = Some(!use_proxy); + w_config.save(); + } + + /// Get SOCKS proxy URL. + pub fn socks_proxy_url() -> Option { + let r_config = Settings::app_config_to_read(); + r_config.socks_proxy_url.clone() + } + + /// Save SOCKS proxy URL. + pub fn save_socks_proxy_url(url: Option) { + let mut w_config = Settings::app_config_to_update(); + w_config.socks_proxy_url = url; + w_config.save(); + } + + /// Get HTTP proxy URL. + pub fn http_proxy_url() -> Option { + let r_config = Settings::app_config_to_read(); + r_config.http_proxy_url.clone() + } + + /// Save HTTP proxy URL. + pub fn save_http_proxy_url(url: Option) { + let mut w_config = Settings::app_config_to_update(); + w_config.http_proxy_url = url; + w_config.save(); + } + } \ No newline at end of file