scan: seed_qr format support
This commit is contained in:
parent
8ccfa8f7fd
commit
479ad82d9b
3 changed files with 69 additions and 11 deletions
|
@ -22,12 +22,14 @@ use image::{DynamicImage, EncodableLayout, ImageFormat};
|
||||||
|
|
||||||
use grin_util::ZeroingString;
|
use grin_util::ZeroingString;
|
||||||
use grin_wallet_libwallet::SlatepackAddress;
|
use grin_wallet_libwallet::SlatepackAddress;
|
||||||
|
use grin_keychain::mnemonic::WORDS;
|
||||||
|
|
||||||
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::{QrScanResult, QrScanState};
|
use crate::gui::views::types::{QrScanResult, QrScanState};
|
||||||
use crate::gui::views::View;
|
use crate::gui::views::View;
|
||||||
|
use crate::wallet::types::PhraseSize;
|
||||||
|
|
||||||
/// Camera QR code scanner.
|
/// Camera QR code scanner.
|
||||||
pub struct CameraContent {
|
pub struct CameraContent {
|
||||||
|
@ -146,6 +148,11 @@ impl CameraContent {
|
||||||
let mut w_scan = self.qr_scan_state.write();
|
let mut w_scan = self.qr_scan_state.write();
|
||||||
w_scan.image_processing = true;
|
w_scan.image_processing = true;
|
||||||
}
|
}
|
||||||
|
// Clear previous scanning result.
|
||||||
|
{
|
||||||
|
let mut w_scan = self.qr_scan_state.write();
|
||||||
|
w_scan.qr_scan_result = None;
|
||||||
|
}
|
||||||
// Launch scanner at separate thread.
|
// Launch scanner at separate thread.
|
||||||
let data = data.clone();
|
let data = data.clone();
|
||||||
let qr_scan_state = self.qr_scan_state.clone();
|
let qr_scan_state = self.qr_scan_state.clone();
|
||||||
|
@ -164,7 +171,16 @@ impl CameraContent {
|
||||||
for g in grids {
|
for g in grids {
|
||||||
if let Ok((_, text)) = g.decode() {
|
if let Ok((_, text)) = g.decode() {
|
||||||
let text = text.trim();
|
let text = text.trim();
|
||||||
if !text.is_empty() {
|
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
|
||||||
|
};
|
||||||
|
if !text.is_empty() && text != cur_text {
|
||||||
let result = Self::parse_scan_result(text);
|
let result = Self::parse_scan_result(text);
|
||||||
let mut w_scan = qr_scan_state.write();
|
let mut w_scan = qr_scan_state.write();
|
||||||
w_scan.qr_scan_result = Some(result);
|
w_scan.qr_scan_result = Some(result);
|
||||||
|
@ -184,15 +200,58 @@ impl CameraContent {
|
||||||
// Check if string starts with Grin address prefix.
|
// Check if string starts with Grin address prefix.
|
||||||
if text.starts_with("tgrin") || text.starts_with("grin") {
|
if text.starts_with("tgrin") || text.starts_with("grin") {
|
||||||
if SlatepackAddress::try_from(text).is_ok() {
|
if SlatepackAddress::try_from(text).is_ok() {
|
||||||
return QrScanResult::Address(ZeroingString::from(text))
|
return QrScanResult::Address(ZeroingString::from(text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if string contains Slatepack message prefix and postfix.
|
// Check if string contains Slatepack message prefix and postfix.
|
||||||
if text.starts_with("BEGINSLATEPACK.") && text.ends_with("ENDSLATEPACK.") {
|
if text.starts_with("BEGINSLATEPACK.") && text.ends_with("ENDSLATEPACK.") {
|
||||||
return QrScanResult::Slatepack(ZeroingString::from(text))
|
return QrScanResult::Slatepack(ZeroingString::from(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check SeedQR format.
|
||||||
|
let only_numbers = || {
|
||||||
|
for c in text.chars() {
|
||||||
|
if !c.is_numeric() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
};
|
||||||
|
if only_numbers() {
|
||||||
|
if let Some(_) = PhraseSize::type_for_value(text.len() / 4) {
|
||||||
|
let chars: Vec<char> = text.trim().chars().collect();
|
||||||
|
let split = &chars.chunks(4)
|
||||||
|
.map(|chunk| chunk.iter().collect::<String>()
|
||||||
|
.trim()
|
||||||
|
.trim_start_matches("0")
|
||||||
|
.to_string()
|
||||||
|
)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let mut words = "".to_string();
|
||||||
|
for i in split {
|
||||||
|
let index = if i.is_empty() {
|
||||||
|
0usize
|
||||||
|
} else {
|
||||||
|
i.parse::<usize>().unwrap_or(WORDS.len())
|
||||||
|
};
|
||||||
|
let empty_word = "".to_string();
|
||||||
|
let word = WORDS.get(index).clone().unwrap_or(&empty_word).clone();
|
||||||
|
// Return text result when BIP39 word was not found.
|
||||||
|
if word.is_empty() {
|
||||||
|
return QrScanResult::Text(ZeroingString::from(text));
|
||||||
|
}
|
||||||
|
words = if words.is_empty() {
|
||||||
|
format!("{}", word)
|
||||||
|
} else {
|
||||||
|
format!("{} {}", words, word)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return QrScanResult::SeedQR(ZeroingString::from(words));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return default text result.
|
||||||
QrScanResult::Text(ZeroingString::from(text))
|
QrScanResult::Text(ZeroingString::from(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -153,7 +153,9 @@ pub enum QrScanResult {
|
||||||
/// Slatepack address.
|
/// Slatepack address.
|
||||||
Address(ZeroingString),
|
Address(ZeroingString),
|
||||||
/// Parsed text.
|
/// Parsed text.
|
||||||
Text(ZeroingString)
|
Text(ZeroingString),
|
||||||
|
/// Parsed SeedQR https://github.com/SeedSigner/seedsigner/blob/dev/docs/seed_qr/README.md.
|
||||||
|
SeedQR(ZeroingString)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QrScanResult {
|
impl QrScanResult {
|
||||||
|
@ -162,7 +164,8 @@ impl QrScanResult {
|
||||||
match self {
|
match self {
|
||||||
QrScanResult::Slatepack(text) => text,
|
QrScanResult::Slatepack(text) => text,
|
||||||
QrScanResult::Address(text) => text,
|
QrScanResult::Address(text) => text,
|
||||||
QrScanResult::Text(text) => text
|
QrScanResult::Text(text) => text,
|
||||||
|
QrScanResult::SeedQR(text) => text
|
||||||
}.to_string()
|
}.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -371,11 +371,7 @@ impl WalletContent {
|
||||||
cb: &dyn PlatformCallbacks) {
|
cb: &dyn PlatformCallbacks) {
|
||||||
// Show scan result if exists or show camera content while scanning.
|
// Show scan result if exists or show camera content while scanning.
|
||||||
if let Some(result) = &self.qr_scan_result {
|
if let Some(result) = &self.qr_scan_result {
|
||||||
let mut result_text = match result {
|
let mut result_text = result.value();
|
||||||
QrScanResult::Slatepack(t) => t,
|
|
||||||
QrScanResult::Address(t) => t,
|
|
||||||
QrScanResult::Text(t) => t
|
|
||||||
}.to_string();
|
|
||||||
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
View::horizontal_line(ui, Colors::ITEM_STROKE);
|
||||||
ui.add_space(3.0);
|
ui.add_space(3.0);
|
||||||
ScrollArea::vertical()
|
ScrollArea::vertical()
|
||||||
|
@ -431,7 +427,7 @@ impl WalletContent {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
QrScanResult::Text(_) => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set result and rename modal title.
|
// Set result and rename modal title.
|
||||||
|
|
Loading…
Reference in a new issue