node: scan and share connection with qr code

This commit is contained in:
ardocrat
2026-03-23 04:46:29 +03:00
parent 05e18cf6c4
commit 497b967fd0
16 changed files with 336 additions and 99 deletions
+63 -10
View File
@@ -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)
+1 -1
View File
@@ -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);
+124 -46
View File
@@ -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")
+4 -1
View File
@@ -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());
}
}
+2 -2
View File
@@ -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| {
+1 -1
View File
@@ -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;
+2 -1
View File
@@ -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);
+10
View File
@@ -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
View File
@@ -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();
+1 -1
View File
@@ -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.
+4 -10
View File
@@ -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);
+4 -3
View File
@@ -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),
})
+1 -1
View File
@@ -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);
+7 -3
View File
@@ -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());