ur: scanning and creating qr codes for slatepack messages
This commit is contained in:
parent
4b46e5a997
commit
3f03d145e8
10 changed files with 373 additions and 110 deletions
73
Cargo.lock
generated
73
Cargo.lock
generated
|
@ -1059,6 +1059,21 @@ version = "0.10.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
|
||||
|
||||
[[package]]
|
||||
name = "bitcoin-private"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57"
|
||||
|
||||
[[package]]
|
||||
name = "bitcoin_hashes"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d7066118b13d4b20b23645932dfb3a81ce7e29f95726c2036fa33cd7b092501"
|
||||
dependencies = [
|
||||
"bitcoin-private",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "0.9.1"
|
||||
|
@ -1800,6 +1815,21 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
|
||||
dependencies = [
|
||||
"crc-catalog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc-catalog"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.0"
|
||||
|
@ -3705,6 +3735,7 @@ dependencies = [
|
|||
"tor-keymgr",
|
||||
"tor-llcrypto",
|
||||
"tor-rtcompat",
|
||||
"ur",
|
||||
"url",
|
||||
"wgpu",
|
||||
"winit",
|
||||
|
@ -5492,6 +5523,26 @@ dependencies = [
|
|||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minicbor"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7005aaf257a59ff4de471a9d5538ec868a21586534fff7f85dd97d4043a6139"
|
||||
dependencies = [
|
||||
"minicbor-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minicbor-derive"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1154809406efdb7982841adb6311b3d095b46f78342dd646736122fe6b19e267"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.81",
|
||||
"quote 1.0.36",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
|
@ -7102,6 +7153,15 @@ dependencies = [
|
|||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xoshiro"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "range-alloc"
|
||||
version = "0.1.3"
|
||||
|
@ -10258,6 +10318,19 @@ version = "0.9.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "ur"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "010f24a953db5d22d0010969ca3bbf40b3857b89f47c0f7be0da4c2d7ded0760"
|
||||
dependencies = [
|
||||
"bitcoin_hashes",
|
||||
"crc",
|
||||
"minicbor",
|
||||
"phf",
|
||||
"rand_xoshiro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.0"
|
||||
|
|
|
@ -57,6 +57,7 @@ tokio = { version = "1.37.0", features = ["full"] }
|
|||
image = "0.25.1"
|
||||
rqrr = "0.7.1"
|
||||
qrcodegen = "1.8.0"
|
||||
ur = "0.4.1"
|
||||
|
||||
## tor
|
||||
arti = { version = "1.2.0", features = ["pt-client", "static"] }
|
||||
|
|
|
@ -35,18 +35,26 @@ use crate::wallet::WalletUtils;
|
|||
/// Camera QR code scanner.
|
||||
pub struct CameraContent {
|
||||
/// QR code scanning progress and result.
|
||||
qr_scan_state: Arc<RwLock<QrScanState>>
|
||||
qr_scan_state: Arc<RwLock<QrScanState>>,
|
||||
|
||||
/// Uniform Resources URIs collected from QR code scanning.
|
||||
ur_data: Arc<RwLock<Option<Vec<String>>>>,
|
||||
|
||||
start: i64,
|
||||
}
|
||||
|
||||
impl Default for CameraContent {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
qr_scan_state: Arc::new(RwLock::new(QrScanState::default())),
|
||||
ur_data: Arc::new(RwLock::new(None)),
|
||||
start: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CameraContent {
|
||||
/// Draw camera content.
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
// Draw last image from camera or loader.
|
||||
if let Some(img_data) = cb.camera_image() {
|
||||
|
@ -138,8 +146,13 @@ impl CameraContent {
|
|||
r_scan.image_processing
|
||||
}
|
||||
|
||||
/// Get UR scanning progress in percents.
|
||||
fn ur_progress(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
/// Parse QR code from provided image data.
|
||||
fn scan_qr(&self, data: &DynamicImage) {
|
||||
fn scan_qr(&self, image_data: &DynamicImage) {
|
||||
// Do not scan when another image is processing.
|
||||
if self.image_processing() {
|
||||
return;
|
||||
|
@ -149,55 +162,117 @@ impl CameraContent {
|
|||
let mut w_scan = self.qr_scan_state.write();
|
||||
w_scan.image_processing = true;
|
||||
}
|
||||
// Launch scanner at separate thread.
|
||||
let data = data.clone();
|
||||
|
||||
let image_data = image_data.clone();
|
||||
let qr_scan_state = self.qr_scan_state.clone();
|
||||
let ur_data = self.ur_data.clone();
|
||||
|
||||
let on_scan = async move {
|
||||
// Prepare image data.
|
||||
let img = image_data.to_luma8();
|
||||
let mut img: rqrr::PreparedImage<image::GrayImage>
|
||||
= rqrr::PreparedImage::prepare(img);
|
||||
// Scan and save results.
|
||||
let grids = img.detect_grids();
|
||||
if let Some(g) = grids.get(0) {
|
||||
let mut qr_data = vec![];
|
||||
if let Ok(_) = g.decode_to(&mut qr_data) {
|
||||
// Setup scanned data into text.
|
||||
let text = String::from_utf8(qr_data.clone()).unwrap_or("".to_string());
|
||||
// Setup current text.
|
||||
let cur_text = {
|
||||
let r_scan = qr_scan_state.read();
|
||||
let text = if let Some(res) = r_scan.qr_scan_result.clone() {
|
||||
res.text()
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
text
|
||||
};
|
||||
// Parse non-empty data if parsed text is different from saved.
|
||||
if !qr_data.is_empty() && (cur_text.is_empty() || text != cur_text) {
|
||||
let res = Self::parse_qr_code(qr_data);
|
||||
match res {
|
||||
QrScanResult::URPart(uri, index, total) => {
|
||||
// Setup current UR data.
|
||||
let mut cur_data = {
|
||||
let r_data = ur_data.read();
|
||||
let mut cur_data = vec!["".to_string(); total];
|
||||
if let Some(d) = r_data.clone() {
|
||||
cur_data = d;
|
||||
}
|
||||
cur_data
|
||||
};
|
||||
if !cur_data.contains(&uri) {
|
||||
// Save part of UR data.
|
||||
{
|
||||
cur_data.insert(index, uri);
|
||||
let mut w_data = ur_data.write();
|
||||
*w_data = Some(cur_data.clone());
|
||||
}
|
||||
// Setup UR decoder.
|
||||
let mut decoder = ur::Decoder::default();
|
||||
for m in cur_data {
|
||||
if !m.is_empty() {
|
||||
if let Ok(_) = decoder.receive(m.as_str()) {
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check if UR data is complete.
|
||||
if decoder.complete() {
|
||||
if let Ok(data) = decoder.message() {
|
||||
// Parse complete data.
|
||||
let res = Self::parse_qr_code(data.unwrap_or(vec![]));
|
||||
// Clean UR data.
|
||||
let mut w_data = ur_data.write();
|
||||
*w_data = None;
|
||||
// Save scan result.
|
||||
let mut w_scan = qr_scan_state.write();
|
||||
w_scan.qr_scan_result = Some(res);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Clean UR data.
|
||||
let mut w_data = ur_data.write();
|
||||
*w_data = None;
|
||||
// Save scan result.
|
||||
let mut w_scan = qr_scan_state.write();
|
||||
w_scan.qr_scan_result = Some(res);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reset scanning flag to process again.
|
||||
{
|
||||
let mut w_scan = qr_scan_state.write();
|
||||
w_scan.image_processing = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Launch scanner at separate thread.
|
||||
thread::spawn(move || {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async {
|
||||
// Prepare image data.
|
||||
let img = data.to_luma8();
|
||||
let mut img: rqrr::PreparedImage<image::GrayImage>
|
||||
= rqrr::PreparedImage::prepare(img);
|
||||
// Scan and save results.
|
||||
let grids = img.detect_grids();
|
||||
if let Some(g) = grids.get(0) {
|
||||
let mut data = vec![];
|
||||
if let Ok(_) = g.decode_to(&mut data) {
|
||||
let cur_text = {
|
||||
let r_scan = qr_scan_state.read();
|
||||
let text = if let Some(res) = r_scan.qr_scan_result.clone() {
|
||||
res.value()
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
text
|
||||
};
|
||||
let text = String::from_utf8(data.clone()).unwrap_or("".to_string());
|
||||
if !data.is_empty() && (cur_text.is_empty() || text != cur_text) {
|
||||
let result = Self::parse_scan_result(&data);
|
||||
let mut w_scan = qr_scan_state.write();
|
||||
w_scan.qr_scan_result = Some(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reset scanning flag to process again.
|
||||
{
|
||||
let mut w_scan = qr_scan_state.write();
|
||||
w_scan.image_processing = false;
|
||||
}
|
||||
});
|
||||
.block_on(on_scan);
|
||||
});
|
||||
}
|
||||
|
||||
fn parse_scan_result(data: &Vec<u8>) -> QrScanResult {
|
||||
/// Parse QR code scan result.
|
||||
fn parse_qr_code(data: Vec<u8>) -> QrScanResult {
|
||||
// Check if string starts with Grin address prefix.
|
||||
let text_string = String::from_utf8(data.clone()).unwrap_or("".to_string());
|
||||
let text = text_string.as_str();
|
||||
println!("data: {}", text_string);
|
||||
let text = text_string.trim();
|
||||
if text.starts_with("tgrin") || text.starts_with("grin") {
|
||||
if SlatepackAddress::try_from(text).is_ok() {
|
||||
return QrScanResult::Address(ZeroingString::from(text));
|
||||
|
@ -209,7 +284,25 @@ impl CameraContent {
|
|||
return QrScanResult::Slatepack(ZeroingString::from(text));
|
||||
}
|
||||
|
||||
// Check Compact SeedQR format (https://github.com/SeedSigner/seedsigner/blob/dev/docs/seed_qr/README.md#compactseedqr-specification).
|
||||
// Check Uniform Resource data.
|
||||
// https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-005-ur.md
|
||||
if text.starts_with("ur:bytes/") {
|
||||
let split = text.split("/").collect::<Vec<_>>();
|
||||
if let Some(index_total) = split.get(1) {
|
||||
if let Some((index, total)) = index_total.split_once("-") {
|
||||
let index = index.parse::<usize>();
|
||||
let total = total.parse::<usize>();
|
||||
if index.is_ok() && total.is_ok() {
|
||||
let index = index.unwrap() - 1;
|
||||
let total = total.unwrap();
|
||||
return QrScanResult::URPart(text_string, index, total);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check Compact SeedQR format.
|
||||
// https://github.com/SeedSigner/seedsigner/blob/dev/docs/seed_qr/README.md#compactseedqr-specification
|
||||
if data.len() <= 32 && 16 <= data.len() && data.len() % 4 == 0 {
|
||||
// Setup words amount.
|
||||
let total_bits = data.len() * 8;
|
||||
|
@ -259,7 +352,8 @@ impl CameraContent {
|
|||
}
|
||||
}
|
||||
|
||||
// Check Standard SeedQR format (https://github.com/SeedSigner/seedsigner/blob/dev/docs/seed_qr/README.md#standard-seedqr-specification).
|
||||
// Check Standard SeedQR format.
|
||||
// https://github.com/SeedSigner/seedsigner/blob/dev/docs/seed_qr/README.md#standard-seedqr-specification
|
||||
let only_numbers = || {
|
||||
for c in text.chars() {
|
||||
if !c.is_numeric() {
|
||||
|
@ -316,7 +410,11 @@ impl CameraContent {
|
|||
|
||||
/// Reset camera content state to default.
|
||||
pub fn clear_state(&mut self) {
|
||||
// Clear QR code scanning state.
|
||||
let mut w_scan = self.qr_scan_state.write();
|
||||
*w_scan = QrScanState::default();
|
||||
// Clear UR data.
|
||||
let mut w_data = self.ur_data.write();
|
||||
*w_data = None;
|
||||
}
|
||||
}
|
|
@ -28,16 +28,27 @@ pub struct QrCodeContent {
|
|||
/// Text to create QR code.
|
||||
pub(crate) text: String,
|
||||
|
||||
/// Flag to draw animated QR with Uniform Resources
|
||||
/// https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-005-ur.md
|
||||
animated: bool,
|
||||
/// Index of current image at animation.
|
||||
animated_index: Option<usize>,
|
||||
/// Time of last image draw.
|
||||
animation_time: Option<i64>,
|
||||
|
||||
/// Texture handle to show image when created.
|
||||
texture_handle: Option<TextureHandle>,
|
||||
/// QR code image creation progress and result.
|
||||
qr_creation_state: Arc<RwLock<QrCreationState>>
|
||||
qr_creation_state: Arc<RwLock<QrCreationState>>,
|
||||
}
|
||||
|
||||
impl QrCodeContent {
|
||||
pub fn new(text: String) -> Self {
|
||||
pub fn new(text: String, animated: bool) -> Self {
|
||||
Self {
|
||||
text,
|
||||
animated,
|
||||
animated_index: None,
|
||||
animation_time: None,
|
||||
texture_handle: None,
|
||||
qr_creation_state: Arc::new(RwLock::new(QrCreationState::default())),
|
||||
}
|
||||
|
@ -45,67 +56,141 @@ impl QrCodeContent {
|
|||
|
||||
/// 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() {
|
||||
let space = (ui.available_width() - View::BIG_SPINNER_SIZE) / 2.0;
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add_space(space);
|
||||
View::big_loading_spinner(ui);
|
||||
ui.add_space(space);
|
||||
});
|
||||
if self.animated {
|
||||
// Create animated QR code image if not created.
|
||||
if !self.has_image() {
|
||||
let space = (ui.available_width() - View::BIG_SPINNER_SIZE) / 2.0;
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add_space(space);
|
||||
View::big_loading_spinner(ui);
|
||||
ui.add_space(space);
|
||||
});
|
||||
|
||||
// Create image from text if not loading.
|
||||
self.create_image(text);
|
||||
// Create multiple vector images from text if not creating.
|
||||
if !self.creating() {
|
||||
self.create_svg_list(text);
|
||||
}
|
||||
} else {
|
||||
let svg_list = {
|
||||
let r_create = self.qr_creation_state.read();
|
||||
r_create.svg_list.clone().unwrap()
|
||||
};
|
||||
|
||||
// Setup animated index.
|
||||
let now = chrono::Utc::now().timestamp_millis();
|
||||
if now - *self.animation_time.get_or_insert(now) > 100 {
|
||||
if let Some(i) = self.animated_index {
|
||||
self.animated_index = Some(i + 1);
|
||||
}
|
||||
if *self.animated_index.get_or_insert(0) == svg_list.len() {
|
||||
self.animated_index = Some(0);
|
||||
}
|
||||
self.animation_time = Some(now);
|
||||
}
|
||||
|
||||
let svg = svg_list[self.animated_index.unwrap_or(0)].clone();
|
||||
|
||||
// Create images from SVG data.
|
||||
let size = SizeHint::Size(ui.available_width() as u32, ui.available_width() as u32);
|
||||
let color_img = load_svg_bytes_with_size(svg.as_slice(), 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));
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
} else {
|
||||
// Create image from SVG data.
|
||||
let r_create = self.qr_creation_state.read();
|
||||
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));
|
||||
// Create vector QR code image if not created.
|
||||
if !self.has_image() {
|
||||
let space = (ui.available_width() - View::BIG_SPINNER_SIZE) / 2.0;
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add_space(space);
|
||||
View::big_loading_spinner(ui);
|
||||
ui.add_space(space);
|
||||
});
|
||||
|
||||
// Create vector image from text if not creating.
|
||||
if !self.creating() {
|
||||
self.create_svg(text);
|
||||
}
|
||||
} else {
|
||||
// Create image from SVG data.
|
||||
let r_create = self.qr_creation_state.read();
|
||||
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.
|
||||
/// Check if QR code is creating.
|
||||
fn creating(&self) -> bool {
|
||||
let r_create = self.qr_creation_state.read();
|
||||
r_create.creating
|
||||
}
|
||||
|
||||
/// Create multiple vector QR code images at separate thread.
|
||||
fn create_svg_list(&self, text: String) {
|
||||
let qr_creation_state = self.qr_creation_state.clone();
|
||||
thread::spawn(move || {
|
||||
let mut encoder = ur::Encoder::bytes(text.as_bytes(), 100).unwrap();
|
||||
let mut data = Vec::with_capacity(encoder.fragment_count());
|
||||
for _ in 0..encoder.fragment_count() {
|
||||
let ur = encoder.next_part().unwrap();
|
||||
if let Ok(qr) = QrCode::encode_text(ur.as_str(), qrcodegen::QrCodeEcc::Low) {
|
||||
let svg = Self::qr_to_svg(qr, 0);
|
||||
data.push(svg.into_bytes());
|
||||
}
|
||||
}
|
||||
let mut w_create = qr_creation_state.write();
|
||||
if !data.is_empty() {
|
||||
w_create.svg_list = Some(data);
|
||||
}
|
||||
w_create.creating = false;
|
||||
});
|
||||
}
|
||||
|
||||
/// Check if image was created.
|
||||
fn has_image(&self) -> bool {
|
||||
let r_create = self.qr_creation_state.read();
|
||||
r_create.svg.is_some()
|
||||
r_create.svg.is_some() || r_create.svg_list.is_some()
|
||||
}
|
||||
|
||||
/// Create QR code image at separate thread.
|
||||
fn create_image(&self, text: String) {
|
||||
/// Create vector QR code image at separate thread.
|
||||
fn create_svg(&self, text: String) {
|
||||
let qr_creation_state = self.qr_creation_state.clone();
|
||||
if !self.creating() {
|
||||
thread::spawn(move || {
|
||||
if let Ok(qr) = QrCode::encode_text(text.as_str(), qrcodegen::QrCodeEcc::Medium) {
|
||||
let svg = Self::qr_to_svg(qr, 0);
|
||||
let mut w_create = qr_creation_state.write();
|
||||
w_create.creating = false;
|
||||
w_create.svg = Some(svg.into_bytes());
|
||||
}
|
||||
});
|
||||
}
|
||||
thread::spawn(move || {
|
||||
if let Ok(qr) = QrCode::encode_text(text.as_str(), qrcodegen::QrCodeEcc::Low) {
|
||||
let svg = Self::qr_to_svg(qr, 0);
|
||||
let mut w_create = qr_creation_state.write();
|
||||
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 {
|
||||
fn qr_to_svg(qr: 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";
|
||||
|
|
|
@ -154,19 +154,24 @@ pub enum QrScanResult {
|
|||
Address(ZeroingString),
|
||||
/// Parsed text.
|
||||
Text(ZeroingString),
|
||||
/// Parsed SeedQR https://github.com/SeedSigner/seedsigner/blob/dev/docs/seed_qr/README.md.
|
||||
SeedQR(ZeroingString)
|
||||
/// Recovery phrase in standard or compact SeedQR format.
|
||||
/// https://github.com/SeedSigner/seedsigner/blob/dev/docs/seed_qr/README.md
|
||||
SeedQR(ZeroingString),
|
||||
/// Part of Uniform Resources as URI with current index and total messages amount.
|
||||
/// https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-005-ur.md
|
||||
URPart(String, usize, usize),
|
||||
}
|
||||
|
||||
impl QrScanResult {
|
||||
/// Get scan result value.
|
||||
pub fn value(&self) -> String {
|
||||
/// Get text scanning result.
|
||||
pub fn text(&self) -> String {
|
||||
match self {
|
||||
QrScanResult::Slatepack(text) => text,
|
||||
QrScanResult::Address(text) => text,
|
||||
QrScanResult::Text(text) => text,
|
||||
QrScanResult::SeedQR(text) => text
|
||||
}.to_string()
|
||||
QrScanResult::Slatepack(text) => text.to_string(),
|
||||
QrScanResult::Address(text) => text.to_string(),
|
||||
QrScanResult::Text(text) => text.to_string(),
|
||||
QrScanResult::SeedQR(text) => text.to_string(),
|
||||
QrScanResult::URPart(uri, _, _) => uri.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,8 +196,10 @@ impl Default for QrScanState {
|
|||
pub struct QrCreationState {
|
||||
// Flag to check if QR code image is creating.
|
||||
pub creating: bool,
|
||||
// Found QR code content.
|
||||
pub svg: Option<Vec<u8>>
|
||||
// Vector image data.
|
||||
pub svg: Option<Vec<u8>>,
|
||||
// Multiple vector image data.
|
||||
pub svg_list: Option<Vec<Vec<u8>>>
|
||||
}
|
||||
|
||||
impl Default for QrCreationState {
|
||||
|
@ -200,6 +207,7 @@ impl Default for QrCreationState {
|
|||
Self {
|
||||
creating: false,
|
||||
svg: None,
|
||||
svg_list: None,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -371,7 +371,7 @@ impl WalletContent {
|
|||
cb: &dyn PlatformCallbacks) {
|
||||
// Show scan result if exists or show camera content while scanning.
|
||||
if let Some(result) = &self.qr_scan_result {
|
||||
let mut result_text = result.value();
|
||||
let mut result_text = result.text();
|
||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||
ui.add_space(3.0);
|
||||
ScrollArea::vertical()
|
||||
|
|
|
@ -173,13 +173,13 @@ impl WalletMessages {
|
|||
request_edit: "".to_string(),
|
||||
request_error: None,
|
||||
request_qr: false,
|
||||
request_qr_content: QrCodeContent::new("".to_string()),
|
||||
request_qr_content: QrCodeContent::new("".to_string(), true),
|
||||
request_loading: false,
|
||||
request_result: Arc::new(RwLock::new(None)),
|
||||
message_camera_content: CameraContent::default(),
|
||||
message_scan_error: false,
|
||||
qr_message_text: None,
|
||||
qr_message_content: QrCodeContent::new("".to_string()),
|
||||
qr_message_content: QrCodeContent::new("".to_string(), true),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -472,7 +472,7 @@ impl WalletMessages {
|
|||
} else {
|
||||
t!("wallets.send_request_desc","amount" => amount_format)
|
||||
};
|
||||
ui.label(RichText::new(desc_text).size(16.0).color(Colors::INACTIVE_TEXT));
|
||||
ui.label(RichText::new(desc_text).size(16.0).color(Colors::GRAY));
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
|
||||
|
|
|
@ -145,7 +145,7 @@ impl WalletTransport {
|
|||
show_address_scan: false,
|
||||
address_scan_content: CameraContent::default(),
|
||||
modal_just_opened: false,
|
||||
qr_address_content: QrCodeContent::new(addr),
|
||||
qr_address_content: QrCodeContent::new(addr, false),
|
||||
tor_settings_changed: false,
|
||||
bridge_bin_path_edit: bin_path,
|
||||
bridge_conn_line_edit: conn_line,
|
||||
|
@ -327,7 +327,7 @@ impl WalletTransport {
|
|||
};
|
||||
|
||||
if let Some(result) = self.bridge_qr_scan_content.qr_scan_result() {
|
||||
self.bridge_conn_line_edit = result.value();
|
||||
self.bridge_conn_line_edit = result.text();
|
||||
on_stop(&mut self.bridge_qr_scan_content);
|
||||
cb.show_keyboard();
|
||||
} else {
|
||||
|
@ -688,7 +688,7 @@ impl WalletTransport {
|
|||
};
|
||||
|
||||
if let Some(result) = self.address_scan_content.qr_scan_result() {
|
||||
self.address_edit = result.value();
|
||||
self.address_edit = result.text();
|
||||
self.modal_just_opened = true;
|
||||
on_stop(&mut self.address_scan_content);
|
||||
cb.show_keyboard();
|
||||
|
|
|
@ -80,7 +80,7 @@ impl Default for WalletTransactions {
|
|||
tx_info_finalizing: false,
|
||||
tx_info_final_result: Arc::new(RwLock::new(None)),
|
||||
tx_info_show_qr: false,
|
||||
tx_info_qr_code_content: QrCodeContent::new("".to_string()),
|
||||
tx_info_qr_code_content: QrCodeContent::new("".to_string(), true),
|
||||
tx_info_show_scanner: false,
|
||||
tx_info_scanner_content: CameraContent::default(),
|
||||
confirm_cancel_tx_id: None,
|
||||
|
@ -708,7 +708,7 @@ impl WalletTransactions {
|
|||
self.tx_info_scanner_content.clear_state();
|
||||
|
||||
// Setup value to finalization input field.
|
||||
self.tx_info_finalize_edit = result.value();
|
||||
self.tx_info_finalize_edit = result.text();
|
||||
self.on_finalization_input_change(tx, wallet, modal, cb);
|
||||
|
||||
modal.enable_closing();
|
||||
|
@ -737,7 +737,7 @@ impl WalletTransactions {
|
|||
let desc_color = if self.tx_info_finalize_error {
|
||||
Colors::RED
|
||||
} else {
|
||||
Colors::INACTIVE_TEXT
|
||||
Colors::GRAY
|
||||
};
|
||||
ui.label(RichText::new(desc_text).size(16.0).color(desc_color));
|
||||
} else {
|
||||
|
@ -754,7 +754,7 @@ impl WalletTransactions {
|
|||
t!("wallets.parse_s1_slatepack_desc", "amount" => amount)
|
||||
}
|
||||
};
|
||||
ui.label(RichText::new(desc_text).size(16.0).color(Colors::INACTIVE_TEXT));
|
||||
ui.label(RichText::new(desc_text).size(16.0).color(Colors::GRAY));
|
||||
}
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
|
|
|
@ -23,8 +23,6 @@ impl WalletUtils {
|
|||
let mut hasher = Sha256::new();
|
||||
hasher.update(data.clone());
|
||||
let checksum = hasher.finalize();
|
||||
println!("BEFORE data: {}, checksum: {}", data.len(), checksum.len());
|
||||
data.extend(checksum);
|
||||
println!("AFTER data: {}", data.len());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue