From cf834f86fcffabd6c7e97a47ab6fd2081ba9ce13 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Tue, 1 Aug 2023 02:13:35 +0300 Subject: [PATCH] wallet + ui: external connection API token support, update translations --- locales/en.yml | 4 +- locales/ru.yml | 4 +- src/gui/views/wallets/setup/connection.rs | 99 +++++++++++++++++------ src/settings.rs | 47 +++++++---- src/wallet/mod.rs | 5 +- src/wallet/types.rs | 39 +++++++++ 6 files changed, 158 insertions(+), 40 deletions(-) create mode 100644 src/wallet/types.rs diff --git a/locales/en.yml b/locales/en.yml index 9d6a5fc..b7d6645 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -21,7 +21,9 @@ wallets: setup_conn_desc: Choose how your wallet connects to the network. conn_method: Connection method ext_conn: 'External connections:' - add_node_url: Add node URL + add_node: Add node + node_url: 'Node URL:' + node_secret: 'API Secret (optional):' invalid_url: Entered URL is invalid open: Open the wallet wrong_pass: Entered password is wrong diff --git a/locales/ru.yml b/locales/ru.yml index 9812a26..222301f 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -21,7 +21,9 @@ wallets: setup_conn_desc: Выберите способ подключения вашего кошелька к сети. conn_method: Способ подключения ext_conn: 'Внешние подключения:' - add_node_url: Добавить URL узла + add_node: Добавить узел + node_url: 'URL узла:' + node_secret: 'API токен (необязательно):' invalid_url: Введенный URL-адрес недействителен open: Открыть кошелёк wrong_pass: Введён неправильный пароль diff --git a/src/gui/views/wallets/setup/connection.rs b/src/gui/views/wallets/setup/connection.rs index 5e0124e..bd78c4a 100644 --- a/src/gui/views/wallets/setup/connection.rs +++ b/src/gui/views/wallets/setup/connection.rs @@ -19,16 +19,21 @@ use crate::AppConfig; use crate::gui::Colors; use crate::gui::icons::{GLOBE, GLOBE_SIMPLE}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, View}; +use crate::gui::views::{Modal, ModalPosition, View}; use crate::gui::views::wallets::setup::ConnectionMethod; +use crate::wallet::ExternalConnection; /// Wallet node connection method setup content. pub struct ConnectionSetup { /// Selected connection method. method: ConnectionMethod, + /// Flag to check if modal was just opened. + first_modal_launch: bool, /// External node connection URL value for [`Modal`]. ext_node_url_edit: String, + /// External node connection API secret value for [`Modal`]. + ext_node_secret_edit: String, /// Flag to show URL format error. ext_node_url_error: bool, } @@ -37,7 +42,9 @@ impl Default for ConnectionSetup { fn default() -> Self { Self { method: ConnectionMethod::Integrated, + first_modal_launch: true, ext_node_url_edit: "".to_string(), + ext_node_secret_edit: "".to_string(), ext_node_url_error: false } } @@ -82,25 +89,28 @@ impl ConnectionSetup { ui.add_space(6.0); // Show button to add new external node connection. - let add_node_text = format!("{} {}", GLOBE_SIMPLE, t!("wallets.add_node_url")); + let add_node_text = format!("{} {}", GLOBE_SIMPLE, t!("wallets.add_node")); View::button(ui, add_node_text, Colors::GOLD, || { // Setup values for Modal. + self.first_modal_launch = true; self.ext_node_url_edit = "".to_string(); + self.ext_node_secret_edit = "".to_string(); self.ext_node_url_error = false; // Show modal. Modal::new(Self::ADD_CONNECTION_URL_MODAL) - .title(t!("wallets.ext_conn")) + .position(ModalPosition::CenterTop) + .title(t!("wallets.add_node")) .show(); cb.show_keyboard(); }); ui.add_space(12.0); // Show external nodes URLs selection. - for conn in AppConfig::external_nodes_urls() { + for conn in AppConfig::external_connections() { View::radio_value(ui, &mut self.method, - ConnectionMethod::External(conn.clone()), - conn); + ConnectionMethod::External(conn.url.clone()), + conn.url); ui.add_space(12.0); } }); @@ -111,15 +121,41 @@ impl ConnectionSetup { pub fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { ui.add_space(6.0); ui.vertical_centered(|ui| { - // Draw external node URL text edit. - let text_edit_resp = egui::TextEdit::singleline(&mut self.ext_node_url_edit) - .id(Id::from(modal.id)) + ui.label(RichText::new(t!("wallets.node_url")) + .size(17.0) + .color(Colors::GRAY)); + ui.add_space(8.0); + + // Draw node URL text edit. + let url_edit_resp = egui::TextEdit::singleline(&mut self.ext_node_url_edit) + .id(Id::from(modal.id).with("node_url_edit")) .font(TextStyle::Heading) .desired_width(ui.available_width()) .cursor_at_end(true) .ui(ui); - text_edit_resp.request_focus(); - if text_edit_resp.clicked() { + ui.add_space(8.0); + if self.first_modal_launch { + self.first_modal_launch = false; + url_edit_resp.request_focus(); + } + if url_edit_resp.clicked() { + cb.show_keyboard(); + } + + ui.label(RichText::new(t!("wallets.node_secret")) + .size(17.0) + .color(Colors::GRAY)); + ui.add_space(8.0); + + // Draw node API secret text edit. + let secret_edit_resp = egui::TextEdit::singleline(&mut self.ext_node_secret_edit) + .id(Id::from(modal.id).with("node_secret_edit")) + .font(TextStyle::Heading) + .desired_width(ui.available_width()) + .cursor_at_end(true) + .ui(ui); + ui.add_space(8.0); + if secret_edit_resp.clicked() { cb.show_keyboard(); } @@ -138,18 +174,6 @@ impl ConnectionSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Add button callback. - let on_add = || { - let error = Url::parse(self.ext_node_url_edit.as_str()).is_err(); - self.ext_node_url_error = error; - if !error { - AppConfig::add_external_node_url(self.ext_node_url_edit.clone()); - // Close modal. - cb.hide_keyboard(); - modal.close(); - } - }; - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::WHITE, || { @@ -159,6 +183,35 @@ impl ConnectionSetup { }); }); columns[1].vertical_centered_justified(|ui| { + // Add connection button callback. + let mut on_add = || { + let error = Url::parse(self.ext_node_url_edit.as_str()).is_err(); + self.ext_node_url_error = error; + if !error { + // Save external connection. + let url = self.ext_node_url_edit.to_owned(); + let secret = if self.ext_node_secret_edit.is_empty() { + None + } else { + Some(self.ext_node_secret_edit.to_owned()) + }; + let ext_conn = ExternalConnection::new(url.clone(), secret); + AppConfig::add_external_connection(ext_conn); + + // Set added method as current. + self.method = ConnectionMethod::External(url); + + // Close modal. + cb.hide_keyboard(); + modal.close(); + } + }; + + // Add connection on Enter button press. + View::on_enter_key(ui, || { + (on_add)(); + }); + View::button(ui, t!("modal.add"), Colors::WHITE, on_add); }); }); diff --git a/src/settings.rs b/src/settings.rs index ae3417c..c0b7898 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -24,7 +24,7 @@ use serde::{Deserialize, Serialize}; use serde::de::DeserializeOwned; use crate::node::NodeConfig; -use crate::wallet::Wallets; +use crate::wallet::{ExternalConnection, Wallets}; lazy_static! { /// Static settings state to be accessible globally. @@ -34,9 +34,6 @@ lazy_static! { /// Application configuration file name. const APP_CONFIG_FILE_NAME: &'static str = "app.toml"; -/// Default external node URL. -const DEFAULT_EXTERNAL_NODE_URL: &'static str = "https://grinnnode.live:3413"; - /// Common application settings. #[derive(Serialize, Deserialize)] pub struct AppConfig { @@ -44,8 +41,8 @@ pub struct AppConfig { pub auto_start_node: bool, /// Chain type for node and wallets. chain_type: ChainTypes, - /// URLs of external nodes for wallets. - external_nodes_urls: Vec + /// URLs of external connections for wallets. + external_connections: Vec } impl Default for AppConfig { @@ -53,8 +50,8 @@ impl Default for AppConfig { Self { auto_start_node: false, chain_type: ChainTypes::default(), - external_nodes_urls: vec![ - DEFAULT_EXTERNAL_NODE_URL.to_string() + external_connections: vec![ + ExternalConnection::default() ], } } @@ -121,19 +118,41 @@ impl AppConfig { w_app_config.save(); } - /// Get external nodes URLs. - pub fn external_nodes_urls() -> Vec { + /// Get external connections for the wallet. + pub fn external_connections() -> Vec { let r_config = Settings::app_config_to_read(); - r_config.external_nodes_urls.clone() + r_config.external_connections.clone() } - /// Add external node URL. - pub fn add_external_node_url(address: String) { + /// Save external connection for the wallet in app config. + pub fn add_external_connection(conn: ExternalConnection) { let mut w_config = Settings::app_config_to_update(); - w_config.external_nodes_urls.insert(0, address); + let mut exists = false; + for mut c in w_config.external_connections.iter_mut() { + // Update connection if URL exists. + if c.url == conn.url { + c.secret = conn.secret.clone(); + exists = true; + break; + } + } + // Create new connection if URL not exists. + if !exists { + w_config.external_connections.insert(0, conn); + } w_config.save(); } + /// Get external node connection secret from provided URL. + pub fn get_external_connection_secret(url: String) -> Option { + let r_config = Settings::app_config_to_read(); + for c in &r_config.external_connections { + if c.url == url { + return c.secret.clone(); + } + } + None + } } /// Main application directory name. diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 5f0e099..f462b3f 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -21,4 +21,7 @@ mod wallets; pub use wallets::{Wallet, Wallets}; mod config; -pub use config::*; \ No newline at end of file +pub use config::*; + +mod types; +pub use types::*; \ No newline at end of file diff --git a/src/wallet/types.rs b/src/wallet/types.rs new file mode 100644 index 0000000..7cafd3a --- /dev/null +++ b/src/wallet/types.rs @@ -0,0 +1,39 @@ +// Copyright 2023 The Grim Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use serde_derive::{Deserialize, Serialize}; + +/// External node connection for the wallet. +#[derive(Serialize, Deserialize, Clone)] +pub struct ExternalConnection { + /// Node URL. + pub url: String, + /// Optional API secret key. + pub secret: Option +} + +impl ExternalConnection { + /// Default external node URL. + const DEFAULT_EXTERNAL_NODE_URL: &'static str = "https://grinnnode.live:3413"; + + pub fn new(url: String, secret: Option) -> Self { + Self { url, secret } + } +} + +impl Default for ExternalConnection { + fn default() -> Self { + Self { url: Self::DEFAULT_EXTERNAL_NODE_URL.to_string(), secret: None } + } +} \ No newline at end of file