qr: parse scan result, slatepack address image

This commit is contained in:
ardocrat 2024-05-04 12:20:35 +03:00
parent 7a79b88e68
commit 0aaebd1ef2
9 changed files with 460 additions and 78 deletions

146
Cargo.lock generated
View file

@ -1000,6 +1000,12 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.22.0" version = "0.22.0"
@ -2168,6 +2174,12 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
[[package]]
name = "data-url"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
[[package]] [[package]]
name = "der" name = "der"
version = "0.7.9" version = "0.7.9"
@ -2659,6 +2671,7 @@ dependencies = [
"image 0.24.9", "image 0.24.9",
"log", "log",
"mime_guess2", "mime_guess2",
"resvg",
"serde", "serde",
] ]
@ -3114,6 +3127,12 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "float-cmp"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
[[package]] [[package]]
name = "fluent" name = "fluent"
version = "0.16.0" version = "0.16.0"
@ -3775,6 +3794,7 @@ dependencies = [
"local-ip-address", "local-ip-address",
"log", "log",
"openssl-sys", "openssl-sys",
"qrcodegen",
"rand 0.8.5", "rand 0.8.5",
"rqrr", "rqrr",
"rust-i18n", "rust-i18n",
@ -4847,6 +4867,12 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "imagesize"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284"
[[package]] [[package]]
name = "imgref" name = "imgref"
version = "1.10.1" version = "1.10.1"
@ -5150,6 +5176,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "kurbo"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b"
dependencies = [
"arrayvec 0.7.4",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@ -6557,6 +6592,12 @@ dependencies = [
"siphasher", "siphasher",
] ]
[[package]]
name = "pico-args"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.1.5" version = "1.1.5"
@ -6892,6 +6933,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5520fbcd7da152a449261c5a533a1c7fad044e9e8aa9528cfec3f464786c7926" checksum = "5520fbcd7da152a449261c5a533a1c7fad044e9e8aa9528cfec3f464786c7926"
[[package]]
name = "qrcodegen"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4339fc7a1021c9c1621d87f5e3505f2805c8c105420ba2f2a4df86814590c142"
[[package]] [[package]]
name = "quick-error" name = "quick-error"
version = "1.2.3" version = "1.2.3"
@ -7221,6 +7268,12 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "rctree"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f"
[[package]] [[package]]
name = "rdrand" name = "rdrand"
version = "0.4.0" version = "0.4.0"
@ -7355,6 +7408,20 @@ dependencies = [
"winreg", "winreg",
] ]
[[package]]
name = "resvg"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cadccb3d99a9efb8e5e00c16fbb732cbe400db2ec7fc004697ee7d97d86cf1f4"
dependencies = [
"log",
"pico-args",
"rgb",
"svgtypes",
"tiny-skia",
"usvg",
]
[[package]] [[package]]
name = "retry-error" name = "retry-error"
version = "0.5.2" version = "0.5.2"
@ -7436,6 +7503,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "roxmltree"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f"
[[package]] [[package]]
name = "rqrr" name = "rqrr"
version = "0.7.1" version = "0.7.1"
@ -8176,6 +8249,15 @@ dependencies = [
"time", "time",
] ]
[[package]]
name = "simplecss"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d"
dependencies = [
"log",
]
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "0.3.11" version = "0.3.11"
@ -8368,6 +8450,9 @@ name = "strict-num"
version = "0.1.1" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
dependencies = [
"float-cmp",
]
[[package]] [[package]]
name = "strsim" name = "strsim"
@ -8461,6 +8546,16 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "171758edb47aa306a78dfa4ab9aeb5167405bd4e3dc2b64e88f6a84bbe98bd63" checksum = "171758edb47aa306a78dfa4ab9aeb5167405bd4e3dc2b64e88f6a84bbe98bd63"
[[package]]
name = "svgtypes"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e44e288cd960318917cbd540340968b90becc8bc81f171345d706e7a89d9d70"
dependencies = [
"kurbo",
"siphasher",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "0.15.44" version = "0.15.44"
@ -8735,6 +8830,7 @@ dependencies = [
"bytemuck", "bytemuck",
"cfg-if 1.0.0", "cfg-if 1.0.0",
"log", "log",
"png",
"tiny-skia-path", "tiny-skia-path",
] ]
@ -10264,6 +10360,50 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "usvg"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b0a51b72ab80ca511d126b77feeeb4fb1e972764653e61feac30adc161a756"
dependencies = [
"base64 0.21.7",
"log",
"pico-args",
"usvg-parser",
"usvg-tree",
"xmlwriter",
]
[[package]]
name = "usvg-parser"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bd4e3c291f45d152929a31f0f6c819245e2921bfd01e7bd91201a9af39a2bdc"
dependencies = [
"data-url",
"flate2",
"imagesize",
"kurbo",
"log",
"roxmltree",
"simplecss",
"siphasher",
"svgtypes",
"usvg-tree",
]
[[package]]
name = "usvg-tree"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ee3d202ebdb97a6215604b8f5b4d6ef9024efd623cf2e373a6416ba976ec7d3"
dependencies = [
"rctree",
"strict-num",
"svgtypes",
"tiny-skia-path",
]
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.1" version = "0.2.1"
@ -11336,6 +11476,12 @@ version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193"
[[package]]
name = "xmlwriter"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
[[package]] [[package]]
name = "xxhash-rust" name = "xxhash-rust"
version = "0.8.10" version = "0.8.10"

View file

@ -34,7 +34,7 @@ grin_wallet_controller = "5.3.0"
## ui ## ui
egui = { version = "0.27.2", default-features = false } egui = { version = "0.27.2", default-features = false }
egui_extras = { version = "0.27.2", features = ["image"] } egui_extras = { version = "0.27.2", features = ["image", "svg"] }
rust-i18n = "2.3.1" rust-i18n = "2.3.1"
## other ## other
@ -53,8 +53,9 @@ rand = "0.8.5"
serde_derive = "1.0.197" serde_derive = "1.0.197"
serde_json = "1.0.115" serde_json = "1.0.115"
tokio = { version = "1.37.0", features = ["full"] } tokio = { version = "1.37.0", features = ["full"] }
image = { version = "0.25.1" } image = "0.25.1"
rqrr = "0.7.1" rqrr = "0.7.1"
qrcodegen = "1.8.0"
## tor ## tor
arti = { version = "1.2.0", features = ["pt-client", "static"] } arti = { version = "1.2.0", features = ["pt-client", "static"] }

View file

@ -13,20 +13,22 @@
// limitations under the License. // limitations under the License.
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use std::thread;
use eframe::emath::Align; use eframe::emath::Align;
use egui::load::SizedTexture; use egui::load::SizedTexture;
use egui::{Layout, Pos2, Rect, TextureOptions, Widget}; use egui::{Layout, Pos2, Rect, TextureOptions, Widget};
use grin_wallet_libwallet::SlatepackAddress;
use image::{DynamicImage, EncodableLayout, ImageFormat}; use image::{DynamicImage, EncodableLayout, ImageFormat};
use crate::gui::Colors; use crate::gui::Colors;
use crate::gui::icons::CAMERA_ROTATE; use crate::gui::icons::CAMERA_ROTATE;
use crate::gui::platform::PlatformCallbacks; use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::types::QrScanState; use crate::gui::views::types::{QrScanResult, QrScanState};
use crate::gui::views::View; use crate::gui::views::View;
/// Camera scanner content. /// Camera QR code scanner.
pub struct CameraContent { pub struct CameraContent {
// QR code scanning progress and result. /// QR code scanning progress and result.
qr_scan_state: Arc<RwLock<QrScanState>> qr_scan_state: Arc<RwLock<QrScanState>>
} }
@ -126,7 +128,7 @@ impl CameraContent {
/// Check if image is processing to find QR code. /// Check if image is processing to find QR code.
fn image_processing(&self) -> bool { fn image_processing(&self) -> bool {
let mut r_scan = self.qr_scan_state.read().unwrap(); let r_scan = self.qr_scan_state.read().unwrap();
r_scan.image_processing r_scan.image_processing
} }
@ -142,39 +144,60 @@ impl CameraContent {
w_scan.image_processing = true; w_scan.image_processing = true;
} }
// Launch scanner at separate thread. // Launch scanner at separate thread.
tokio::runtime::Builder::new_multi_thread() let data = data.clone();
.enable_all() let qr_scan_state = self.qr_scan_state.clone();
.build() thread::spawn(move || {
.unwrap() tokio::runtime::Builder::new_multi_thread()
.block_on(async { .enable_all()
// Prepare image data. .build()
let img = data.to_luma8(); .unwrap()
let mut img: rqrr::PreparedImage<image::GrayImage> .block_on(async {
= rqrr::PreparedImage::prepare(img); // Prepare image data.
// Scan and save results. let img = data.to_luma8();
let grids = img.detect_grids(); let mut img: rqrr::PreparedImage<image::GrayImage>
for g in grids { = rqrr::PreparedImage::prepare(img);
if let Ok((meta, text)) = g.decode() { // Scan and save results.
println!("12345 ecc: {}, text: {}", meta.ecc_level, text.clone()); let grids = img.detect_grids();
if !text.trim().is_empty() { for g in grids {
let mut w_scan = self.qr_scan_state.write().unwrap(); if let Ok((_, text)) = g.decode() {
w_scan.qr_scan_result = Some(text); let text = text.trim();
if !text.is_empty() {
let result = Self::parse_scan_result(text);
let mut w_scan = qr_scan_state.write().unwrap();
w_scan.qr_scan_result = Some(result);
}
} }
} }
} // Setup scanning flag.
// Setup scanning flag. {
{ let mut w_scan = qr_scan_state.write().unwrap();
let mut w_scan = self.qr_scan_state.write().unwrap(); w_scan.image_processing = false;
w_scan.image_processing = false; }
} });
}); });
}
fn parse_scan_result(text: &str) -> QrScanResult {
// Check if string starts with Grin address prefix.
if text.starts_with("tgrin") || text.starts_with("grin") {
if SlatepackAddress::try_from(text).is_ok() {
return QrScanResult::Address(text.to_string())
}
}
// Check if string contains Slatepack message prefix and postfix.
if text.starts_with("BEGINSLATEPACK.") && text.ends_with("ENDSLATEPACK.") {
return QrScanResult::Slatepack(text.to_string())
}
QrScanResult::Text(text.to_string())
} }
/// Get QR code scan result. /// Get QR code scan result.
pub fn qr_scan_result(&self) -> Option<String> { pub fn qr_scan_result(&self) -> Option<QrScanResult> {
let r_scan = self.qr_scan_state.read().unwrap(); let r_scan = self.qr_scan_state.read().unwrap();
if r_scan.qr_scan_result.is_some() { if r_scan.qr_scan_result.is_some() {
return Some(r_scan.qr_scan_result.as_ref().unwrap().clone()); return Some(r_scan.qr_scan_result.clone().unwrap());
} }
None None
} }

View file

@ -34,3 +34,6 @@ pub use wallets::*;
mod camera; mod camera;
pub use camera::*; pub use camera::*;
mod qr;
pub use qr::*;

132
src/gui/views/qr.rs Normal file
View file

@ -0,0 +1,132 @@
// 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 std::sync::{Arc, RwLock};
use std::thread;
use egui::{SizeHint, TextureHandle, TextureOptions};
use egui::load::SizedTexture;
use egui_extras::image::{load_svg_bytes, load_svg_bytes_with_size};
use crate::gui::views::types::QrCreationState;
use crate::gui::views::View;
/// QR code image from text.
pub struct QrCodeContent {
/// Text to create QR code.
pub(crate) text: String,
/// Texture handle to show image when created.
texture_handle: Option<TextureHandle>,
/// QR code image creation progress and result.
qr_creation_state: Arc<RwLock<QrCreationState>>
}
impl QrCodeContent {
pub fn new(text: String) -> Self {
Self {
text,
texture_handle: None,
qr_creation_state: Arc::new(RwLock::new(QrCreationState::default())),
}
}
/// Draw QR code.
pub fn ui(&mut self, ui: &mut egui::Ui, text: String) {
// Get saved QR code image or load new one.
if !self.has_image() {
ui.add_space(38.0);
View::small_loading_spinner(ui);
ui.add_space(38.0);
// Create image from text if not loading.
self.create_image(text);
} else {
// Create image from SVG data.
let r_create = self.qr_creation_state.read().unwrap();
let svg = r_create.svg.as_ref().unwrap();
let size = SizeHint::Size(ui.available_width() as u32, ui.available_width() as u32);
let color_img = load_svg_bytes_with_size(svg, Some(size)).unwrap();
// Create image texture.
let texture_handle = ui.ctx().load_texture("qr_code",
color_img.clone(),
TextureOptions::default());
self.texture_handle = Some(texture_handle.clone());
let img_size = egui::emath::vec2(color_img.width() as f32,
color_img.height() as f32);
let sized_img = SizedTexture::new(texture_handle.id(), img_size);
// Add image to content.
ui.add(egui::Image::from_texture(sized_img)
.max_height(ui.available_width())
.fit_to_original_size(1.0));
}
}
/// Check if image is creating.
fn creating(&self) -> bool {
let r_create = self.qr_creation_state.read().unwrap();
r_create.creating
}
/// Check if image was created.
fn has_image(&self) -> bool {
let r_create = self.qr_creation_state.read().unwrap();
r_create.svg.is_some()
}
/// Create QR code image at separate thread.
fn create_image(&self, text: String) {
let qr_creation_state = self.qr_creation_state.clone();
if !self.creating() {
thread::spawn(move || {
let qr = qrcodegen::QrCode::encode_text(text.as_str(),
qrcodegen::QrCodeEcc::Medium).unwrap();
let svg = Self::qr_to_svg(qr, 0);
let mut w_create = qr_creation_state.write().unwrap();
w_create.creating = false;
w_create.svg = Some(svg.into_bytes());
});
}
}
/// Convert QR code to SVG string.
fn qr_to_svg(qr: qrcodegen::QrCode, border: i32) -> String {
let mut result = String::new();
let dimension = qr.size().checked_add(border.checked_mul(2).unwrap()).unwrap();
result += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
result += "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n";
result += &format!(
"<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 {0} {0}\" stroke=\"none\">\n", dimension);
result += "\t<rect width=\"100%\" height=\"100%\" fill=\"#FFFFFF\"/>\n";
result += "\t<path d=\"";
for y in 0 .. qr.size() {
for x in 0 .. qr.size() {
if qr.get_module(x, y) {
if x != 0 || y != 0 {
result += " ";
}
result += &format!("M{},{}h1v1h-1z", x + border, y + border);
}
}
}
result += "\" fill=\"#000000\"/>\n";
result += "</svg>\n";
result
}
/// Reset QR code image content state to default.
pub fn clear_state(&mut self) {
let mut w_create = self.qr_creation_state.write().unwrap();
*w_create = QrCreationState::default();
}
}

View file

@ -167,3 +167,20 @@ impl Default for QrScanState {
} }
} }
} }
/// QR code image creation state.
pub struct QrCreationState {
// Flag to check if QR code image is creating.
pub creating: bool,
// Found QR code content.
pub svg: Option<Vec<u8>>
}
impl Default for QrCreationState {
fn default() -> Self {
Self {
creating: false,
svg: None,
}
}
}

View file

@ -22,7 +22,7 @@ use crate::gui::Colors;
use crate::gui::icons::{BRIDGE, CHAT_CIRCLE_TEXT, CHECK, CHECK_FAT, FOLDER_USER, GEAR_FINE, GRAPH, PACKAGE, PATH, POWER, REPEAT, SCAN, USERS_THREE}; use crate::gui::icons::{BRIDGE, CHAT_CIRCLE_TEXT, CHECK, CHECK_FAT, FOLDER_USER, GEAR_FINE, GRAPH, PACKAGE, PATH, POWER, REPEAT, SCAN, USERS_THREE};
use crate::gui::platform::PlatformCallbacks; use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{CameraContent, Modal, Root, View}; use crate::gui::views::{CameraContent, Modal, Root, View};
use crate::gui::views::types::{ModalPosition, TextEditOptions}; use crate::gui::views::types::{ModalPosition, QrScanResult, TextEditOptions};
use crate::gui::views::wallets::{WalletTransactions, WalletMessages, WalletTransport, WalletSettings}; use crate::gui::views::wallets::{WalletTransactions, WalletMessages, WalletTransport, WalletSettings};
use crate::gui::views::wallets::types::{GRIN, WalletTab, WalletTabType}; use crate::gui::views::wallets::types::{GRIN, WalletTab, WalletTabType};
use crate::node::Node; use crate::node::Node;
@ -44,7 +44,7 @@ pub struct WalletContent {
/// Camera content for QR scan [`Modal`]. /// Camera content for QR scan [`Modal`].
camera_content: CameraContent, camera_content: CameraContent,
/// QR code scan result /// QR code scan result
qr_scan_result: Option<String>, qr_scan_result: Option<QrScanResult>,
/// Current tab content to show. /// Current tab content to show.
pub current_tab: Box<dyn WalletTab> pub current_tab: Box<dyn WalletTab>
@ -367,15 +367,37 @@ impl WalletContent {
cb: &dyn PlatformCallbacks) { cb: &dyn PlatformCallbacks) {
ui.add_space(6.0); ui.add_space(6.0);
if let Some(result) = &self.qr_scan_result { if let Some(result) = &self.qr_scan_result {
let result_text = match result {
QrScanResult::Slatepack(t) => t,
QrScanResult::Address(t) => t,
QrScanResult::Text(t) => t
};
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
ui.label(RichText::new(result).size(16.0).color(Colors::INACTIVE_TEXT)); ui.label(RichText::new(result_text).size(16.0).color(Colors::INACTIVE_TEXT));
}); });
} else if let Some(result) = self.camera_content.qr_scan_result() { } else if let Some(result) = self.camera_content.qr_scan_result() {
///TODO: parse result and show
cb.stop_camera(); cb.stop_camera();
self.camera_content.clear_state(); self.camera_content.clear_state();
println!("result: {}", result); match &result {
self.qr_scan_result = Some(result); QrScanResult::Slatepack(message) => {
let mut messages =
WalletMessages::new(wallet.can_use_dandelion(), Some(message.clone()));
messages.parse_message(wallet);
self.current_tab = Box::new(messages);
modal.close();
}
QrScanResult::Address(receiver) => {
if wallet.get_data().unwrap().info.amount_currently_spendable > 0 {
let addr = wallet.slatepack_address().unwrap();
let mut transport = WalletTransport::new(addr.clone());
transport.show_send_tor_modal(cb, Some(receiver.clone()));
self.current_tab = Box::new(transport);
}
}
QrScanResult::Text(_) => {
self.qr_scan_result = Some(result);
}
}
} else { } else {
self.camera_content.ui(ui, cb); self.camera_content.ui(ui, cb);
} }
@ -409,13 +431,14 @@ impl WalletContent {
let is_messages = current_type == WalletTabType::Messages; let is_messages = current_type == WalletTabType::Messages;
View::tab_button(ui, CHAT_CIRCLE_TEXT, is_messages, || { View::tab_button(ui, CHAT_CIRCLE_TEXT, is_messages, || {
self.current_tab = Box::new( self.current_tab = Box::new(
WalletMessages::new(wallet.can_use_dandelion()) WalletMessages::new(wallet.can_use_dandelion(), None)
); );
}); });
}); });
columns[2].vertical_centered_justified(|ui| { columns[2].vertical_centered_justified(|ui| {
View::tab_button(ui, BRIDGE, current_type == WalletTabType::Transport, || { View::tab_button(ui, BRIDGE, current_type == WalletTabType::Transport, || {
self.current_tab = Box::new(WalletTransport::default()); let addr = wallet.slatepack_address().unwrap();
self.current_tab = Box::new(WalletTransport::new(addr));
}); });
}); });
columns[3].vertical_centered_justified(|ui| { columns[3].vertical_centered_justified(|ui| {

View file

@ -77,22 +77,6 @@ pub struct WalletMessages {
/// Identifier for amount input [`Modal`]. /// Identifier for amount input [`Modal`].
const AMOUNT_MODAL: &'static str = "amount_modal"; const AMOUNT_MODAL: &'static str = "amount_modal";
impl WalletMessages {
pub fn new(dandelion: bool) -> Self {
Self {
send_request: false,
message_edit: "".to_string(),
message_slate: None,
message_error: None,
response_edit: "".to_string(),
dandelion,
amount_edit: "".to_string(),
request_edit: "".to_string(),
request_error: None,
}
}
}
impl WalletTab for WalletMessages { impl WalletTab for WalletMessages {
fn get_type(&self) -> WalletTabType { fn get_type(&self) -> WalletTabType {
WalletTabType::Messages WalletTabType::Messages
@ -140,6 +124,21 @@ impl WalletTab for WalletMessages {
} }
impl WalletMessages { impl WalletMessages {
/// Create new content instance, put message into input if provided.
pub fn new(dandelion: bool, message: Option<String>) -> Self {
Self {
send_request: false,
message_edit: message.unwrap_or("".to_string()),
message_slate: None,
message_error: None,
response_edit: "".to_string(),
dandelion,
amount_edit: "".to_string(),
request_edit: "".to_string(),
request_error: None,
}
}
/// Draw manual wallet transaction interaction content. /// Draw manual wallet transaction interaction content.
pub fn ui(&mut self, pub fn ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
@ -452,7 +451,7 @@ impl WalletMessages {
} }
/// Parse message input into [`Slate`] updating slate and response input. /// Parse message input into [`Slate`] updating slate and response input.
fn parse_message(&mut self, wallet: &mut Wallet) { pub fn parse_message(&mut self, wallet: &mut Wallet) {
self.message_error = None; self.message_error = None;
if self.message_edit.is_empty() { if self.message_edit.is_empty() {
return; return;

View file

@ -22,7 +22,7 @@ use grin_wallet_libwallet::SlatepackAddress;
use crate::gui::Colors; use crate::gui::Colors;
use crate::gui::icons::{CHECK_CIRCLE, COMPUTER_TOWER, COPY, DOTS_THREE_CIRCLE, EXPORT, GEAR_SIX, GLOBE_SIMPLE, POWER, QR_CODE, WARNING_CIRCLE, X_CIRCLE}; use crate::gui::icons::{CHECK_CIRCLE, COMPUTER_TOWER, COPY, DOTS_THREE_CIRCLE, EXPORT, GEAR_SIX, GLOBE_SIMPLE, POWER, QR_CODE, WARNING_CIRCLE, X_CIRCLE};
use crate::gui::platform::PlatformCallbacks; use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, Root, View}; use crate::gui::views::{Modal, QrCodeContent, Root, View};
use crate::gui::views::types::{ModalPosition, TextEditOptions}; use crate::gui::views::types::{ModalPosition, TextEditOptions};
use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType}; use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType};
use crate::gui::views::wallets::wallet::WalletContent; use crate::gui::views::wallets::wallet::WalletContent;
@ -46,20 +46,9 @@ pub struct WalletTransport {
address_error: bool, address_error: bool,
/// Flag to check if [`Modal`] was just opened to focus on first field. /// Flag to check if [`Modal`] was just opened to focus on first field.
modal_just_opened: bool, modal_just_opened: bool,
}
impl Default for WalletTransport { /// QR code address image [`Modal`] content.
fn default() -> Self { qr_code_content: QrCodeContent,
Self {
tor_sending: Arc::new(RwLock::new(false)),
tor_send_error: Arc::new(RwLock::new(false)),
tor_success: Arc::new(RwLock::new(false)),
amount_edit: "".to_string(),
address_edit: "".to_string(),
address_error: false,
modal_just_opened: false,
}
}
} }
impl WalletTab for WalletTransport { impl WalletTab for WalletTransport {
@ -114,7 +103,24 @@ const SEND_TOR_MODAL: &'static str = "send_tor_modal";
/// Identifier for [`Modal`] to setup Tor service. /// Identifier for [`Modal`] to setup Tor service.
const TOR_SETTINGS_MODAL: &'static str = "tor_settings_modal"; const TOR_SETTINGS_MODAL: &'static str = "tor_settings_modal";
/// Identifier for [`Modal`] to show QR code address image.
const QR_ADDRESS_MODAL: &'static str = "qr_address_modal";
impl WalletTransport { impl WalletTransport {
/// Create new content instance from provided Slatepack address text.
pub fn new(addr: String) -> Self {
Self {
tor_sending: Arc::new(RwLock::new(false)),
tor_send_error: Arc::new(RwLock::new(false)),
tor_success: Arc::new(RwLock::new(false)),
amount_edit: "".to_string(),
address_edit: "".to_string(),
address_error: false,
modal_just_opened: false,
qr_code_content: QrCodeContent::new(addr),
}
}
/// Draw wallet transport content. /// Draw wallet transport content.
pub fn ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) { pub fn ui(&mut self, ui: &mut egui::Ui, wallet: &mut Wallet, cb: &dyn PlatformCallbacks) {
ui.add_space(3.0); ui.add_space(3.0);
@ -146,6 +152,11 @@ impl WalletTransport {
self.tor_settings_modal_ui(ui, wallet, modal); self.tor_settings_modal_ui(ui, wallet, modal);
}); });
} }
QR_ADDRESS_MODAL => {
Modal::ui(ui.ctx(), |ui, modal| {
self.qr_address_modal_ui(ui, modal);
});
}
_ => {} _ => {}
} }
} }
@ -186,7 +197,7 @@ impl WalletTransport {
// Draw button to setup Tor transport. // Draw button to setup Tor transport.
let button_rounding = View::item_rounding(0, 2, true); let button_rounding = View::item_rounding(0, 2, true);
View::item_button(ui, button_rounding, GEAR_SIX, None, || { View::item_button(ui, button_rounding, GEAR_SIX, None, || {
// Show modal. // Show Tor settings modal.
Modal::new(TOR_SETTINGS_MODAL) Modal::new(TOR_SETTINGS_MODAL)
.position(ModalPosition::CenterTop) .position(ModalPosition::CenterTop)
.title(t!("transport.tor_settings")) .title(t!("transport.tor_settings"))
@ -273,7 +284,7 @@ impl WalletTransport {
} }
/// Draw Tor send content. /// Draw Tor send content.
fn tor_receive_ui(&self, fn tor_receive_ui(&mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
wallet: &Wallet, wallet: &Wallet,
data: &WalletData, data: &WalletData,
@ -304,7 +315,12 @@ impl WalletTransport {
View::item_rounding(1, 2, true) View::item_rounding(1, 2, true)
}; };
View::item_button(ui, button_rounding, QR_CODE, None, || { View::item_button(ui, button_rounding, QR_CODE, None, || {
//TODO: qr for address // Show QR code image address modal.
self.qr_code_content.clear_state();
Modal::new(QR_ADDRESS_MODAL)
.position(ModalPosition::CenterTop)
.title(t!("network_mining.address"))
.show();
}); });
// Show button to enable/disable Tor listener for current wallet. // Show button to enable/disable Tor listener for current wallet.
@ -338,6 +354,28 @@ impl WalletTransport {
}); });
} }
/// Draw QR code image address [`Modal`] content.
fn qr_address_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal) {
ui.add_space(6.0);
// Draw QR code content.
let text = self.qr_code_content.text.clone();
self.qr_code_content.ui(ui, text.clone());
ui.add_space(6.0);
// Show address.
View::ellipsize_text(ui, text, 15.0, Colors::TEXT);
ui.add_space(6.0);
ui.vertical_centered_justified(|ui| {
View::button(ui, t!("close"), Colors::WHITE, || {
self.qr_code_content.clear_state();
modal.close();
});
ui.add_space(6.0);
});
}
/// Draw Tor receive content. /// Draw Tor receive content.
fn tor_send_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { fn tor_send_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
// Setup layout size. // Setup layout size.
@ -355,14 +393,14 @@ impl WalletTransport {
// Draw button to open sending modal. // Draw button to open sending modal.
let send_text = format!("{} {}", EXPORT, t!("wallets.send")); let send_text = format!("{} {}", EXPORT, t!("wallets.send"));
View::button(ui, send_text, Colors::WHITE, || { View::button(ui, send_text, Colors::WHITE, || {
self.show_send_tor_modal(cb); self.show_send_tor_modal(cb, None);
}); });
}); });
}); });
} }
/// Show [`Modal`] to send over Tor. /// Show [`Modal`] to send over Tor.
fn show_send_tor_modal(&mut self, cb: &dyn PlatformCallbacks) { pub fn show_send_tor_modal(&mut self, cb: &dyn PlatformCallbacks, address: Option<String>) {
// Setup modal values. // Setup modal values.
let mut w_send_err = self.tor_send_error.write().unwrap(); let mut w_send_err = self.tor_send_error.write().unwrap();
*w_send_err = false; *w_send_err = false;
@ -372,7 +410,7 @@ impl WalletTransport {
*w_success = false; *w_success = false;
self.modal_just_opened = true; self.modal_just_opened = true;
self.amount_edit = "".to_string(); self.amount_edit = "".to_string();
self.address_edit = "".to_string(); self.address_edit = address.unwrap_or("".to_string());
self.address_error = false; self.address_error = false;
// Show modal. // Show modal.
Modal::new(SEND_TOR_MODAL) Modal::new(SEND_TOR_MODAL)