mirror of
https://code.gri.mw/GUI/grim.git
synced 2026-07-04 05:57:29 +00:00
node: scan and share connection with qr code
This commit is contained in:
@@ -15,9 +15,10 @@
|
||||
use eframe::epaint::RectShape;
|
||||
use egui::{Align, Color32, CornerRadius, CursorIcon, Layout, RichText, Sense, StrokeKind, UiBuilder};
|
||||
|
||||
use crate::gui::icons::{CHECK_CIRCLE, COMPUTER_TOWER, DOTS_THREE_CIRCLE, GLOBE_SIMPLE, PLUS_CIRCLE, POWER, TRASH, WARNING_CIRCLE, X_CIRCLE};
|
||||
use crate::gui::icons::{CHECK_CIRCLE, COMPUTER_TOWER, DOTS_THREE_CIRCLE, GLOBE_SIMPLE, PLUS_CIRCLE, POWER, QR_CODE, TRASH, WARNING_CIRCLE, X_CIRCLE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::network::modals::ExternalConnectionModal;
|
||||
use crate::gui::views::network::modals::{ExternalConnectionModal, ShareConnectionContent};
|
||||
use crate::gui::views::network::types::ShareConnection;
|
||||
use crate::gui::views::network::NodeSetup;
|
||||
use crate::gui::views::types::{ContentContainer, ModalPosition};
|
||||
use crate::gui::views::{Modal, View};
|
||||
@@ -30,23 +31,32 @@ use crate::AppConfig;
|
||||
pub struct ConnectionsContent {
|
||||
/// Flag to check connections state on first draw.
|
||||
first_draw: bool,
|
||||
|
||||
/// External connection [`Modal`] content.
|
||||
ext_conn_modal: ExternalConnectionModal,
|
||||
ext_conn_modal_content: ExternalConnectionModal,
|
||||
|
||||
/// [`Modal`] content to share connection with QR code.
|
||||
share_conn_modal_content: Option<ShareConnectionContent>
|
||||
}
|
||||
|
||||
impl Default for ConnectionsContent {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
first_draw: true,
|
||||
ext_conn_modal: ExternalConnectionModal::new(None),
|
||||
ext_conn_modal_content: ExternalConnectionModal::new(None),
|
||||
share_conn_modal_content: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifier for [`Modal`] to share connection.
|
||||
const SHARE_CONN_QR_MODAL: &'static str = "share_conn_qr_modal";
|
||||
|
||||
impl ContentContainer for ConnectionsContent {
|
||||
fn modal_ids(&self) -> Vec<&'static str> {
|
||||
vec![
|
||||
ExternalConnectionModal::NETWORK_ID
|
||||
ExternalConnectionModal::NETWORK_ID,
|
||||
SHARE_CONN_QR_MODAL
|
||||
]
|
||||
}
|
||||
|
||||
@@ -56,8 +66,13 @@ impl ContentContainer for ConnectionsContent {
|
||||
cb: &dyn PlatformCallbacks) {
|
||||
match modal.id {
|
||||
ExternalConnectionModal::NETWORK_ID => {
|
||||
self.ext_conn_modal.ui(ui, cb, modal, |_| {});
|
||||
self.ext_conn_modal_content.ui(ui, cb, modal, |_| {});
|
||||
},
|
||||
SHARE_CONN_QR_MODAL => {
|
||||
if let Some(c) = self.share_conn_modal_content.as_mut() {
|
||||
c.ui(ui, modal, cb);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +99,24 @@ impl ContentContainer for ConnectionsContent {
|
||||
// Show integrated node info content.
|
||||
Self::integrated_node_item_ui(ui, Colors::fill_lite(), (true, || {
|
||||
AppConfig::toggle_show_connections_network_panel();
|
||||
}), |_| false);
|
||||
}), |ui| {
|
||||
let r = View::item_rounding(0, 1, true);
|
||||
View::item_button(ui, r, QR_CODE, None, || {
|
||||
if let Ok(c) = ShareConnectionContent::new(ShareConnection {
|
||||
url: format!("http://{}", NodeConfig::get_api_address()),
|
||||
username: "grin".to_string(),
|
||||
secret: NodeConfig::get_api_secret(true).unwrap_or("".to_string()),
|
||||
}) {
|
||||
self.share_conn_modal_content = Some(c);
|
||||
// Show QR code to share integrated node connection.
|
||||
Modal::new(SHARE_CONN_QR_MODAL)
|
||||
.position(ModalPosition::Center)
|
||||
.title(t!("network.node"))
|
||||
.show();
|
||||
}
|
||||
});
|
||||
true
|
||||
});
|
||||
|
||||
// Show external connections.
|
||||
ui.add_space(8.0);
|
||||
@@ -105,16 +137,37 @@ impl ContentContainer for ConnectionsContent {
|
||||
ui.add_space(8.0);
|
||||
for (i, c) in ext_conn_list.iter().enumerate() {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
let mut show_qr_content: Option<ShareConnectionContent> = None;
|
||||
// Draw external connection list item.
|
||||
let bg = Colors::fill_lite();
|
||||
Self::ext_conn_item_ui(ui, bg, c, i, len, (true, || {
|
||||
self.show_add_ext_conn_modal(Some(c.clone()));
|
||||
}), |ui| {
|
||||
let button_rounding = View::item_rounding(i, len, true);
|
||||
View::item_button(ui, button_rounding, TRASH, None, || {
|
||||
// Draw button to delete connection.
|
||||
let r = View::item_rounding(i, len, true);
|
||||
View::item_button(ui, r, TRASH, Some(Colors::inactive_text()), || {
|
||||
ConnectionsConfig::remove_ext_conn(c.id);
|
||||
});
|
||||
// Draw button to share connection
|
||||
let r = CornerRadius::default();
|
||||
View::item_button(ui, r, QR_CODE, None, || {
|
||||
if let Ok(c) = ShareConnectionContent::new(ShareConnection {
|
||||
url: c.url.clone(),
|
||||
username: "grin".to_string(),
|
||||
secret: c.secret.clone().unwrap_or("".to_string()),
|
||||
}) {
|
||||
show_qr_content = Some(c);
|
||||
}
|
||||
});
|
||||
});
|
||||
if let Some(c) = show_qr_content {
|
||||
self.share_conn_modal_content = Some(c);
|
||||
// Show QR code to share external connection.
|
||||
Modal::new(SHARE_CONN_QR_MODAL)
|
||||
.position(ModalPosition::Center)
|
||||
.title(t!("wallets.ext_conn").replace(":", ""))
|
||||
.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -277,7 +330,7 @@ impl ConnectionsContent {
|
||||
|
||||
/// Show [`Modal`] to add external connection.
|
||||
pub fn show_add_ext_conn_modal(&mut self, conn: Option<ExternalConnection>) {
|
||||
self.ext_conn_modal = ExternalConnectionModal::new(conn);
|
||||
self.ext_conn_modal_content = ExternalConnectionModal::new(conn);
|
||||
// Show modal.
|
||||
Modal::new(ExternalConnectionModal::NETWORK_ID)
|
||||
.position(ModalPosition::CenterTop)
|
||||
|
||||
@@ -152,7 +152,7 @@ fn block_item_ui(ui: &mut egui::Ui, db: &DiffBlock, rounding: CornerRadius) {
|
||||
rect.max -= vec2(8.0, 0.0);
|
||||
ui.painter().rect(rect,
|
||||
rounding,
|
||||
Colors::white_or_black(false),
|
||||
Colors::fill(),
|
||||
View::item_stroke(),
|
||||
StrokeKind::Outside);
|
||||
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
use egui::{Id, RichText};
|
||||
use url::Url;
|
||||
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::SCAN;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Modal, TextEdit, View};
|
||||
use crate::gui::views::network::types::ShareConnection;
|
||||
use crate::gui::views::{CameraContent, Modal, TextEdit, View};
|
||||
use crate::gui::Colors;
|
||||
use crate::wallet::{ConnectionsConfig, ExternalConnection};
|
||||
|
||||
/// Content to create or update external wallet connection.
|
||||
@@ -25,14 +27,21 @@ pub struct ExternalConnectionModal {
|
||||
/// Flag to check if content was just rendered.
|
||||
first_draw: bool,
|
||||
|
||||
/// External connection URL value for [`Modal`].
|
||||
ext_node_url_edit: String,
|
||||
/// External connection API secret value for [`Modal`].
|
||||
ext_node_secret_edit: String,
|
||||
/// Flag to show URL format error at [`Modal`].
|
||||
ext_node_url_error: bool,
|
||||
/// Editing external connection identifier for [`Modal`].
|
||||
ext_conn_id: Option<i64>,
|
||||
/// Editing external connection identifier.
|
||||
id: Option<i64>,
|
||||
|
||||
/// External connection URL.
|
||||
url_edit: String,
|
||||
/// Flag to show URL format error.
|
||||
url_error: bool,
|
||||
|
||||
/// External connection username.
|
||||
username_edit: String,
|
||||
/// External connection API secret.
|
||||
secret_edit: String,
|
||||
|
||||
/// QR code scanner content.
|
||||
scan_qr_content: Option<CameraContent>,
|
||||
}
|
||||
|
||||
impl ExternalConnectionModal {
|
||||
@@ -43,17 +52,21 @@ impl ExternalConnectionModal {
|
||||
|
||||
/// Create new instance from optional provided connection to update.
|
||||
pub fn new(conn: Option<ExternalConnection>) -> Self {
|
||||
let (ext_node_url_edit, ext_node_secret_edit, ext_conn_id) = if let Some(c) = conn {
|
||||
(c.url, c.secret.unwrap_or("".to_string()), Some(c.id))
|
||||
let (url_edit, username_edit, secret_edit, id) = if let Some(c) = conn {
|
||||
let username = c.username.unwrap_or("grin".to_string());
|
||||
let secret = c.secret.unwrap_or("".to_string());
|
||||
(c.url, username, secret, Some(c.id))
|
||||
} else {
|
||||
("".to_string(), "".to_string(), None)
|
||||
("".to_string(), "grin".to_string(), "".to_string(), None)
|
||||
};
|
||||
Self {
|
||||
first_draw: true,
|
||||
ext_node_url_edit,
|
||||
ext_node_secret_edit,
|
||||
ext_node_url_error: false,
|
||||
ext_conn_id,
|
||||
url_edit,
|
||||
url_error: false,
|
||||
username_edit,
|
||||
secret_edit,
|
||||
id,
|
||||
scan_qr_content: None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,25 +76,69 @@ impl ExternalConnectionModal {
|
||||
cb: &dyn PlatformCallbacks,
|
||||
modal: &Modal,
|
||||
on_save: impl Fn(ExternalConnection)) {
|
||||
// Show QR code scanner content.
|
||||
if let Some(scan_content) = self.scan_qr_content.as_mut() {
|
||||
if let Some(result) = scan_content.qr_scan_result() {
|
||||
cb.stop_camera();
|
||||
modal.enable_closing();
|
||||
self.scan_qr_content = None;
|
||||
// Parse scan result.
|
||||
if let Ok(c) = serde_json::from_str::<ShareConnection>(&result.text()) {
|
||||
let ext_conn = ExternalConnection::new(c.url, Some(c.username), Some(c.secret));
|
||||
ConnectionsConfig::add_ext_conn(ext_conn.clone());
|
||||
ExternalConnection::check(Some(ext_conn.id), ui.ctx());
|
||||
Modal::close();
|
||||
}
|
||||
} else {
|
||||
scan_content.ui(ui, cb);
|
||||
}
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
||||
|
||||
// Show buttons to close modal or scanner.
|
||||
ui.columns(2, |cols| {
|
||||
cols[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("close"), Colors::white_or_black(false), || {
|
||||
cb.stop_camera();
|
||||
self.scan_qr_content = None;
|
||||
Modal::close();
|
||||
});
|
||||
});
|
||||
cols[1].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("back"), Colors::white_or_black(false), || {
|
||||
cb.stop_camera();
|
||||
self.scan_qr_content = None;
|
||||
modal.enable_closing();
|
||||
});
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Add connection button callback.
|
||||
let on_add = |ui: &mut egui::Ui, m: &mut ExternalConnectionModal| {
|
||||
let url = if !m.ext_node_url_edit.starts_with("http") {
|
||||
format!("https://{}", m.ext_node_url_edit)
|
||||
let url = if !m.url_edit.starts_with("http") {
|
||||
format!("https://{}", m.url_edit)
|
||||
} else {
|
||||
m.ext_node_url_edit.clone()
|
||||
m.url_edit.clone()
|
||||
};
|
||||
let error = Url::parse(url.trim()).is_err();
|
||||
m.ext_node_url_error = error;
|
||||
m.url_error = error;
|
||||
if !error {
|
||||
let secret = if m.ext_node_secret_edit.is_empty() {
|
||||
let username = if m.secret_edit.is_empty() {
|
||||
Some("grin".to_string())
|
||||
} else {
|
||||
Some(m.secret_edit.clone())
|
||||
};
|
||||
let secret = if m.secret_edit.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(m.ext_node_secret_edit.clone())
|
||||
Some(m.secret_edit.clone())
|
||||
};
|
||||
|
||||
// Update or create new connection.
|
||||
let mut ext_conn = ExternalConnection::new(url, secret);
|
||||
if let Some(id) = m.ext_conn_id {
|
||||
let mut ext_conn = ExternalConnection::new(url, username, secret);
|
||||
if let Some(id) = m.id {
|
||||
ext_conn.id = id;
|
||||
}
|
||||
ConnectionsConfig::add_ext_conn(ext_conn.clone());
|
||||
@@ -89,9 +146,9 @@ impl ExternalConnectionModal {
|
||||
on_save(ext_conn);
|
||||
|
||||
// Close modal.
|
||||
m.ext_node_url_edit = "".to_string();
|
||||
m.ext_node_secret_edit = "".to_string();
|
||||
m.ext_node_url_error = false;
|
||||
m.url_edit = "".to_string();
|
||||
m.secret_edit = "".to_string();
|
||||
m.url_error = false;
|
||||
Modal::close();
|
||||
}
|
||||
};
|
||||
@@ -104,11 +161,24 @@ impl ExternalConnectionModal {
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw node URL text edit.
|
||||
let url_edit_id = Id::from(modal.id).with(self.ext_conn_id).with("node_url");
|
||||
let mut url_edit = TextEdit::new(url_edit_id)
|
||||
.paste()
|
||||
.focus(self.first_draw);
|
||||
url_edit.ui(ui, &mut self.ext_node_url_edit, cb);
|
||||
let url_edit_id = Id::from(modal.id).with(self.id).with("node_url");
|
||||
let mut url_edit = TextEdit::new(url_edit_id).paste().focus(self.first_draw);
|
||||
let url_edit_before = self.url_edit.clone();
|
||||
url_edit.ui(ui, &mut self.url_edit, cb);
|
||||
if self.url_edit != url_edit_before {
|
||||
self.url_error = false;
|
||||
}
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.label(RichText::new(t!("wallets.name"))
|
||||
.size(17.0)
|
||||
.color(Colors::gray()));
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw node username text edit (disabled by default).
|
||||
let username_edit_id = Id::from(modal.id).with(self.id).with("node_username");
|
||||
let mut username_edit = TextEdit::new(username_edit_id).focus(false).disable();
|
||||
username_edit.ui(ui, &mut self.username_edit, cb);
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.label(RichText::new(t!("wallets.node_secret"))
|
||||
@@ -117,29 +187,37 @@ impl ExternalConnectionModal {
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Draw node API secret text edit.
|
||||
let secret_edit_id = Id::from(modal.id).with(self.ext_conn_id).with("node_secret");
|
||||
let mut secret_edit = TextEdit::new(secret_edit_id)
|
||||
.password()
|
||||
.paste()
|
||||
.focus(false);
|
||||
let secret_edit_id = Id::from(modal.id).with(self.id).with("node_secret");
|
||||
let mut secret_edit = TextEdit::new(secret_edit_id).password().paste().focus(false);
|
||||
if url_edit.enter_pressed {
|
||||
secret_edit.focus_request();
|
||||
}
|
||||
secret_edit.ui(ui, &mut self.ext_node_secret_edit, cb);
|
||||
secret_edit.ui(ui, &mut self.secret_edit, cb);
|
||||
if secret_edit.enter_pressed {
|
||||
(on_add)(ui, self);
|
||||
on_add(ui, self);
|
||||
}
|
||||
|
||||
// Show error when specified URL is not valid.
|
||||
if self.ext_node_url_error {
|
||||
if self.url_error {
|
||||
ui.add_space(12.0);
|
||||
ui.label(RichText::new(t!("wallets.invalid_url"))
|
||||
.size(17.0)
|
||||
.color(Colors::red()));
|
||||
}
|
||||
ui.add_space(12.0);
|
||||
|
||||
let scan_text = format!("{} {}", SCAN, t!("scan"));
|
||||
View::button(ui, scan_text, Colors::white_or_black(false), || {
|
||||
modal.disable_closing();
|
||||
self.scan_qr_content = Some(CameraContent::default());
|
||||
cb.start_camera();
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
View::horizontal_line(ui, Colors::item_stroke());
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Show modal buttons.
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between buttons.
|
||||
@@ -149,14 +227,14 @@ impl ExternalConnectionModal {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||
// Close modal.
|
||||
self.ext_node_url_edit = "".to_string();
|
||||
self.ext_node_secret_edit = "".to_string();
|
||||
self.ext_node_url_error = false;
|
||||
self.url_edit = "".to_string();
|
||||
self.secret_edit = "".to_string();
|
||||
self.url_error = false;
|
||||
Modal::close();
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
View::button_ui(ui, if self.ext_conn_id.is_some() {
|
||||
View::button_ui(ui, if self.id.is_some() {
|
||||
t!("modal.save")
|
||||
} else {
|
||||
t!("modal.add")
|
||||
|
||||
@@ -13,4 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
mod ext_conn;
|
||||
pub use ext_conn::*;
|
||||
pub use ext_conn::*;
|
||||
|
||||
mod share_conn;
|
||||
pub use share_conn::*;
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2024 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 crate::AppConfig;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::network::types::ShareConnection;
|
||||
use crate::gui::views::{Modal, QrCodeContent, View};
|
||||
|
||||
/// [`Modal`] content to share connection with QR code.
|
||||
pub struct ShareConnectionContent {
|
||||
/// QR code content.
|
||||
pub qr_details_content: QrCodeContent,
|
||||
}
|
||||
|
||||
impl ShareConnectionContent {
|
||||
/// Create new content instance from connection details.
|
||||
pub fn new(details: ShareConnection) -> Result<Self, serde_json::Error> {
|
||||
let details = serde_json::to_string_pretty(&details)?;
|
||||
let c = Self {
|
||||
qr_details_content: QrCodeContent::new(details, false).hide_text().no_copy(),
|
||||
};
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
/// Draw QR code content.
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||
let dark_theme = AppConfig::dark_theme().unwrap_or(false);
|
||||
// Set light theme for better scanning.
|
||||
AppConfig::set_dark_theme(false);
|
||||
modal.set_background_color(Colors::FILL_DEEP);
|
||||
crate::setup_visuals(ui.ctx());
|
||||
// Draw QR code content.
|
||||
ui.add_space(6.0);
|
||||
self.qr_details_content.ui(ui, cb);
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
View::button(ui, t!("close"), Colors::white_or_black(false), || {
|
||||
Modal::close();
|
||||
});
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
// Set color theme back.
|
||||
AppConfig::set_dark_theme(dark_theme);
|
||||
crate::setup_visuals(ui.ctx());
|
||||
}
|
||||
}
|
||||
@@ -176,7 +176,7 @@ fn node_stats_ui(ui: &mut egui::Ui) {
|
||||
const PEER_ITEM_HEIGHT: f32 = 77.0;
|
||||
|
||||
/// Draw connected peer info item.
|
||||
fn peer_item_ui(ui: &mut egui::Ui, peer: &PeerStats, rounding: CornerRadius) {
|
||||
fn peer_item_ui(ui: &mut egui::Ui, peer: &PeerStats, r: CornerRadius) {
|
||||
let mut rect = ui.available_rect_before_wrap();
|
||||
rect.set_height(PEER_ITEM_HEIGHT);
|
||||
ui.allocate_ui(rect.size(), |ui| {
|
||||
@@ -184,7 +184,7 @@ fn peer_item_ui(ui: &mut egui::Ui, peer: &PeerStats, rounding: CornerRadius) {
|
||||
ui.add_space(4.0);
|
||||
|
||||
// Draw round background.
|
||||
ui.painter().rect(rect, rounding, Colors::fill_lite(), View::item_stroke(), StrokeKind::Outside);
|
||||
ui.painter().rect(rect, r, Colors::fill(), View::item_stroke(), StrokeKind::Outside);
|
||||
|
||||
// Draw IP address.
|
||||
ui.horizontal(|ui| {
|
||||
|
||||
@@ -17,7 +17,7 @@ use eframe::epaint::{RectShape, StrokeKind};
|
||||
use egui::{CursorIcon, Id, Layout, RichText, Sense, UiBuilder};
|
||||
use grin_core::global::ChainTypes;
|
||||
|
||||
use crate::gui::icons::{CLOCK_CLOCKWISE, COMPUTER_TOWER, FOLDERS, PENCIL, PLUG, POWER, SHIELD, SHIELD_SLASH};
|
||||
use crate::gui::icons::{CLOCK_CLOCKWISE, COMPUTER_TOWER, FOLDERS, PLUG, POWER, SHIELD, SHIELD_SLASH};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::network::settings::NetworkSettings;
|
||||
use crate::gui::views::network::NetworkContent;
|
||||
|
||||
@@ -794,7 +794,8 @@ fn peer_item_ui(ui: &mut egui::Ui,
|
||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||
// Draw delete button for non-default seed peers.
|
||||
if peer_type != &PeerType::DefaultSeed {
|
||||
View::item_button(ui, View::item_rounding(index, len, true), TRASH, None, || {
|
||||
let r = View::item_rounding(index, len, true);
|
||||
View::item_button(ui, r, TRASH, Some(Colors::inactive_text()), || {
|
||||
match peer_type {
|
||||
PeerType::CustomSeed => {
|
||||
NodeConfig::remove_custom_seed(peer_addr);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
|
||||
/// Integrated node tab content interface.
|
||||
@@ -38,4 +39,13 @@ impl NodeTabType {
|
||||
NodeTabType::Settings => t!("network.settings").into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Connection details to share.
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ShareConnection {
|
||||
#[serde(rename(serialize = "ipPort", deserialize = "ipPort"))]
|
||||
pub url: String,
|
||||
pub username: String,
|
||||
pub secret: String
|
||||
}
|
||||
+54
-19
@@ -32,6 +32,10 @@ use crate::gui::Colors;
|
||||
pub struct QrCodeContent {
|
||||
/// QR code text.
|
||||
pub text: String,
|
||||
/// Flag to show text below QR code.
|
||||
show_text: bool,
|
||||
/// Flag to copy text below QR code.
|
||||
can_copy_text: bool,
|
||||
|
||||
/// Maximum QR code size.
|
||||
max_size: f32,
|
||||
@@ -56,6 +60,8 @@ impl QrCodeContent {
|
||||
pub fn new(text: String, animated: bool) -> Self {
|
||||
Self {
|
||||
text,
|
||||
show_text: true,
|
||||
can_copy_text: true,
|
||||
max_size: DEFAULT_QR_SIZE as f32,
|
||||
animated,
|
||||
animated_index: None,
|
||||
@@ -71,6 +77,18 @@ impl QrCodeContent {
|
||||
self
|
||||
}
|
||||
|
||||
/// Hide text below QR code.
|
||||
pub fn hide_text(mut self) -> Self {
|
||||
self.show_text = false;
|
||||
self
|
||||
}
|
||||
|
||||
/// Do not show button to copy QR code text.
|
||||
pub fn no_copy(mut self) -> Self {
|
||||
self.can_copy_text = false;
|
||||
self
|
||||
}
|
||||
|
||||
/// Draw QR code.
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
if self.animated {
|
||||
@@ -123,7 +141,9 @@ impl QrCodeContent {
|
||||
self.qr_image_ui(svg, ui);
|
||||
|
||||
// Show QR code text.
|
||||
self.text_ui(ui);
|
||||
if self.show_text {
|
||||
self.text_ui(ui);
|
||||
}
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
let sharing = {
|
||||
@@ -202,32 +222,47 @@ impl QrCodeContent {
|
||||
self.qr_image_ui(svg, ui);
|
||||
|
||||
// Show QR code text.
|
||||
self.text_ui(ui);
|
||||
if self.show_text {
|
||||
self.text_ui(ui);
|
||||
} else {
|
||||
ui.add_space(8.0);
|
||||
}
|
||||
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0);
|
||||
if self.can_copy_text {
|
||||
// Setup spacing between buttons.
|
||||
ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0);
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
// Draw copy button.
|
||||
let copy_text = format!("{} {}", COPY, t!("copy"));
|
||||
View::button(ui, copy_text, Colors::white_or_black(false), || {
|
||||
cb.copy_string_to_buffer(self.text.clone());
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
// Draw copy button.
|
||||
let copy_text = format!("{} {}", COPY, t!("copy"));
|
||||
View::button(ui, copy_text, Colors::white_or_black(false), || {
|
||||
cb.copy_string_to_buffer(self.text.clone());
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
self.share_static_button_ui(ui, cb);
|
||||
});
|
||||
});
|
||||
columns[1].vertical_centered_justified(|ui| {
|
||||
let share_text = format!("{} {}", IMAGES_SQUARE, t!("share"));
|
||||
View::colored_text_button(ui,
|
||||
share_text,
|
||||
Colors::blue(),
|
||||
Colors::white_or_black(false), || {
|
||||
self.share_static(cb);
|
||||
});
|
||||
} else {
|
||||
ui.vertical_centered(|ui| {
|
||||
self.share_static_button_ui(ui, cb);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw button to share static QR code.
|
||||
fn share_static_button_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
let share_text = format!("{} {}", IMAGES_SQUARE, t!("share"));
|
||||
View::colored_text_button(ui,
|
||||
share_text,
|
||||
Colors::blue(),
|
||||
Colors::white_or_black(false), || {
|
||||
self.share_static(cb);
|
||||
});
|
||||
}
|
||||
|
||||
/// Share static QR code image.
|
||||
fn share_static(&self, cb: &dyn PlatformCallbacks) {
|
||||
let text = self.text.as_str();
|
||||
|
||||
@@ -458,7 +458,7 @@ impl View {
|
||||
ne: if r[1] { 8.0 as u8 } else { 0.0 as u8 },
|
||||
sw: if r[2] { 8.0 as u8 } else { 0.0 as u8 },
|
||||
se: if r[3] { 8.0 as u8 } else { 0.0 as u8 },
|
||||
}, Colors::fill_lite(), Self::item_stroke(), StrokeKind::Outside);
|
||||
}, Colors::fill(), Self::item_stroke(), StrokeKind::Outside);
|
||||
let bg_idx = ui.painter().add(bg_shape.clone());
|
||||
|
||||
// Draw box content.
|
||||
|
||||
@@ -232,9 +232,7 @@ impl WalletCreationContent {
|
||||
columns[0].vertical_centered_justified(|ui| {
|
||||
match self.mnemonic_setup.mnemonic.mode() {
|
||||
PhraseMode::Generate => {
|
||||
let c_t = format!("{} {}",
|
||||
COPY,
|
||||
t!("copy").to_uppercase());
|
||||
let c_t = format!("{} {}", COPY, t!("copy"));
|
||||
View::button(ui, c_t, Colors::white_or_black(false), || {
|
||||
cb.copy_string_to_buffer(self.mnemonic_setup
|
||||
.mnemonic
|
||||
@@ -242,9 +240,7 @@ impl WalletCreationContent {
|
||||
});
|
||||
}
|
||||
PhraseMode::Import => {
|
||||
let p_t = format!("{} {}",
|
||||
CLIPBOARD_TEXT,
|
||||
t!("paste").to_uppercase());
|
||||
let p_t = format!("{} {}", CLIPBOARD_TEXT, t!("paste"));
|
||||
View::button(ui, p_t, Colors::white_or_black(false), || {
|
||||
let data = ZeroingString::from(cb.get_string_from_buffer());
|
||||
self.mnemonic_setup.mnemonic.import(&data);
|
||||
@@ -257,9 +253,7 @@ impl WalletCreationContent {
|
||||
if next {
|
||||
self.next_step_button_ui(ui, on_create);
|
||||
} else {
|
||||
let scan_text = format!("{} {}",
|
||||
SCAN,
|
||||
t!("scan").to_uppercase());
|
||||
let scan_text = format!("{} {}", SCAN, t!("scan"));
|
||||
View::button(ui, scan_text, Colors::white_or_black(false), || {
|
||||
self.scan_modal_content = Some(CameraScanContent::default());
|
||||
// Show QR code scan modal.
|
||||
@@ -279,7 +273,7 @@ impl WalletCreationContent {
|
||||
if next {
|
||||
self.next_step_button_ui(ui, on_create);
|
||||
} else {
|
||||
let paste_text = format!("{} {}", CLIPBOARD_TEXT, t!("paste").to_uppercase());
|
||||
let paste_text = format!("{} {}", CLIPBOARD_TEXT, t!("paste"));
|
||||
View::button(ui, paste_text, Colors::white_or_black(false), || {
|
||||
let data = ZeroingString::from(cb.get_string_from_buffer());
|
||||
self.mnemonic_setup.mnemonic.import(&data);
|
||||
|
||||
@@ -28,11 +28,12 @@ pub struct MessageInputContent {
|
||||
message_edit: String,
|
||||
/// Flag to check if error happened at Slatepack message parsing.
|
||||
parse_error: bool,
|
||||
/// QR code scanner content.
|
||||
scan_qr_content: Option<CameraContent>,
|
||||
/// Button to parse picked file content.
|
||||
file_pick_button: FilePickContent,
|
||||
|
||||
/// QR code scanner content.
|
||||
scan_qr_content: Option<CameraContent>,
|
||||
|
||||
/// Payment proof input content.
|
||||
pub proof_content: Option<PaymentProofContent>,
|
||||
}
|
||||
@@ -45,10 +46,10 @@ impl Default for MessageInputContent {
|
||||
Self {
|
||||
message_edit: "".to_string(),
|
||||
parse_error: false,
|
||||
scan_qr_content: None,
|
||||
file_pick_button: FilePickContent::new(
|
||||
FilePickContentType::Button(t!("choose_file").into())
|
||||
),
|
||||
scan_qr_content: None,
|
||||
proof_content: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +115,7 @@ impl ContentContainer for ConnectionSettings {
|
||||
ext_conn_list.push(ExternalConnection {
|
||||
id: *id,
|
||||
url: url.clone(),
|
||||
username: Some("grin".to_string()),
|
||||
secret: None,
|
||||
available: Some(true),
|
||||
})
|
||||
|
||||
@@ -64,7 +64,7 @@ impl ConnectionsConfig {
|
||||
pub fn add_ext_conn(conn: ExternalConnection) {
|
||||
let mut w_config = Settings::conn_config_to_update();
|
||||
if let Some(pos) = w_config.external.iter().position(|c| {
|
||||
c.id == conn.id
|
||||
c.id == conn.id || c.url == conn.url
|
||||
}) {
|
||||
w_config.external.remove(pos);
|
||||
w_config.external.insert(pos, conn);
|
||||
|
||||
@@ -28,6 +28,8 @@ pub struct ExternalConnection {
|
||||
pub id: i64,
|
||||
/// Node URL.
|
||||
pub url: String,
|
||||
/// Optional username.
|
||||
pub username: Option<String>,
|
||||
/// Optional API secret key.
|
||||
pub secret: Option<String>,
|
||||
|
||||
@@ -61,6 +63,7 @@ impl ExternalConnection {
|
||||
ExternalConnection {
|
||||
id: index as i64,
|
||||
url: url.to_string(),
|
||||
username: Some("grin".to_string()),
|
||||
secret: None,
|
||||
available: None,
|
||||
}
|
||||
@@ -68,11 +71,12 @@ impl ExternalConnection {
|
||||
}
|
||||
|
||||
/// Create new external connection.
|
||||
pub fn new(url: String, secret: Option<String>) -> Self {
|
||||
pub fn new(url: String, username: Option<String>, secret: Option<String>) -> Self {
|
||||
let id = chrono::Utc::now().timestamp();
|
||||
Self {
|
||||
id,
|
||||
url,
|
||||
username,
|
||||
secret,
|
||||
available: None,
|
||||
}
|
||||
@@ -128,7 +132,6 @@ fn check_ext_conn(conn: &ExternalConnection, ui_ctx: &egui::Context) {
|
||||
return;
|
||||
}
|
||||
let url = url_res.unwrap();
|
||||
|
||||
if let Ok(_) = url.socket_addrs(|| None) {
|
||||
let addr = format!("{}v2/foreign", url.to_string());
|
||||
let mut req_setup = hyper::Request::builder()
|
||||
@@ -136,9 +139,10 @@ fn check_ext_conn(conn: &ExternalConnection, ui_ctx: &egui::Context) {
|
||||
.uri(addr.clone());
|
||||
// Setup secret key auth.
|
||||
if let Some(key) = conn.secret {
|
||||
let username = conn.username.unwrap_or("grin".to_string());
|
||||
let basic_auth = format!(
|
||||
"Basic {}",
|
||||
to_base64(&format!("grin:{}", key))
|
||||
to_base64(&format!("{}:{}", username, key))
|
||||
);
|
||||
req_setup = req_setup
|
||||
.header(hyper::header::AUTHORIZATION, basic_auth.clone());
|
||||
|
||||
Reference in New Issue
Block a user