fixes and improvements of QR scanning

pull/32/head
Niko PLP 4 months ago
parent 577fc40bb2
commit 5711cadfb8
  1. 2
      nextgraph/src/local_broker.rs
  2. 2
      ng-app/src/lib/Login.svelte
  3. 1
      ng-app/src/lib/components/PasswordInput.svelte
  4. 31
      ng-app/src/locales/en.json
  5. 5
      ng-app/src/routes/ScanQR.svelte
  6. 276
      ng-app/src/routes/WalletInfo.svelte
  7. 7
      ng-app/src/routes/WalletLogin.svelte
  8. 173
      ng-app/src/routes/WalletLoginQr.svelte
  9. 26
      ng-app/src/store.ts
  10. 197
      ng-app/src/styles.css
  11. 1
      ng-net/src/actors/ext/mod.rs

@ -44,7 +44,7 @@ use ng_verifier::types::*;
use ng_verifier::verifier::Verifier;
use ng_wallet::bip39::encode_mnemonic;
use ng_wallet::emojis::{display_pazzle, display_pazzle_one, encode_pazzle};
use ng_wallet::emojis::{display_pazzle, encode_pazzle};
use ng_wallet::{
create_wallet_first_step_v0, create_wallet_second_step_v0, display_mnemonic, types::*,
};

@ -89,7 +89,7 @@
unlockWith = "pazzle";
scrollToTop();
}
function start_with_mnemonic() {
async function start_with_mnemonic() {
loaded = false;
step = "mnemonic";
unlockWith = "mnemonic";

@ -46,6 +46,7 @@
{placeholder}
{id}
{type}
autofocus
on:input={handleInput}
class={`${className} pr-12 text-md block`}
autocomplete={auto_complete}

@ -28,18 +28,21 @@
"remove_wallet_modal.title": "Remove wallet?",
"remove_wallet_modal.confirm": "Are you sure you want to remove this wallet from your device?",
"create_text_code": "Generate TextCode to export",
"scan_qr.title": "Export by scanning QR-Code",
"scan_qr.notes": "Scan the QR-Code on the device that you want to transfer your wallet to.",
"scan_qr.scan_btn": "Scan QR Code ",
"scan_qr.title": "Export by scanning a QR-Code",
"scan_qr.no_camera": "If to the contrary, the other device does not have a camera, ",
"scan_qr.other_has_camera": "If the other device where you want to import the Wallet, has a camera, then you can just click on the Back button and select <span class=\"path\">Generate QR to export</span>",
"scan_qr.notes": "You will now scan the QR-Code that appears on the screen of the other device (the one you want to transfer your wallet to).",
"scan_qr.scan_btn": "Scan QR-Code ",
"scan_qr.scanner.title": "Scan your QR Code",
"scan_qr.scanner.loading": "Loading scanner",
"scan_qr.syncing": "Synchronizing wallet",
"scan_qr.scan_successful": "Success!<br />Your wallet has been transferred to the new device.",
"gen_qr.title": "Export with generated QR-Code",
"gen_qr.notes": "Use the following QR-Code to scan with the device that you want to transfer your wallet to.",
"gen_qr.notes": "In order to transfer your wallet to another device, you will now display a QR-Code here on this device, and you will then scan it with the other device where you want to transfer your wallet to.",
"gen_qr.no_camera": "If the device where you want to import the Wallet, does not have a camera, then you will have to choose another method.",
"gen_qr.img_title": "Your Export QR Code to Scan",
"gen_qr.img_alt": "Your Export QR Code to Scan",
"gen_qr.gen_button": "Show QR Code",
"gen_qr.gen_button": "Display QR-Code",
"gen_text_code.title": "Export with TextCode",
"gen_text_code.gen_btn": "Generate TextCode",
"gen_text_code.label": "Your TextCode:"
@ -257,9 +260,11 @@
"scan.description": "To import your wallet from another device, generate a wallet QR-Code there. On the other device, go to<br /><span class=\"path\">User Panel > Wallet > Generate QR</span> to export.",
"scan.button": "Scan QR-Code",
"scan.modal.title": "Scan Wallet QR-Code",
"gen.button": "Generate",
"gen.description": "To import your wallet from another device, you have to generate a QR-Code here on this device, and then scan it with your other device. On the other device, go to<br /><span class=\"path\">User Panel > Wallet > Scan QR</span> to export.",
"gen.generated": "Scan this QR-Code from the the other device.",
"gen.button": "Generate QR-Code",
"gen.description": "To import your wallet from another device, you have to generate a QR-Code here on this device, and then scan it with your other device (the one where your wallet is located for now).<br/>If your other device does not have a camera, then you have to use another method for importing your wallet here.",
"offline_advice": "If you do not have internet on this device, you can use the \"Import a Wallet file\" method instead.",
"gen.letsgo": "Ready? On your other device, you first have to be logged-in (wallet is opened) and then you go to<br /><span class=\"path\">User Panel > Wallet > Scan QR to export</span>.<br />Then on this present device, click below on the<br/><span class=\"path\">Generate QR-Code</span> button.",
"gen.generated": "Scan this QR-Code from the other device.<br/>You have 5 minutes to do so.",
"success_btn": "Continue to Login"
},
"wallet_login_textcode": {
@ -372,11 +377,13 @@
"donate_nextgraph": "Donate to NextGraph"
},
"wallet_sync": {
"offline_warning": "You cannot transfer your wallet when offline.<br />Please make sure, you are connected first.",
"textcode.usage_warning": "You have to exchange this TextCode with the other device by any means available to you (email, other messenger apps, etc...). It is highly recommended to use a tool that is end-to-end encrypted. If you can, you should use the \"Import with QRCode\" option instead, as it is safer. If your devices are not connected to the internet, then you can use the \"Import a Wallet File\" option. In this case, you will transfer the wallet file with a USB key, from one device to the other, or for mobile, by connecting your mobile with the USB cable, to the computer, and then transferring the file with the File Transfer utility on Android, or AirDrop/Finder/iTunes on an iPhone/mac/PC. We do not recommend uploading your wallet file to any cloud service.",
"server_transfer_notice": "Both devices need to be online.<br />During this wallet import, your wallet will be temporarily and securely stored on our servers for up to 5 minutes, using two levels of encryption.",
"offline_warning": "You cannot transfer your wallet when offline.<br />Please make sure you are connected to the internet first.",
"textcode.usage_warning": "You have to exchange this TextCode with the other device by any means available to you (email, other messenger apps, etc...). It is highly recommended to use a tool that is end-to-end encrypted. If you can, you should use the \"Import with QRCode\" option instead, as it is safer and simpler. If your devices are not connected to the internet, then you can use the \"Import a Wallet File\" option. In this case, you will transfer the wallet file with a USB key, from one device to the other, or for mobile, by connecting your mobile with the USB cable, to the computer, and then transferring the file with the File Transfer utility on Android, or AirDrop/Finder/iTunes on an iPhone/mac/PC. We do not recommend uploading your wallet file to any cloud service.",
"server_transfer_notice": "Both devices need to be online.<br />During this wallet transfer, your wallet will be temporarily and securely stored on our servers for up to 5 minutes, using two levels of encryption.<br/> We at NextGraph will never be able to read your wallet, your PIN, your pazzle nor your mnemonic.",
"importing": "Importing wallet",
"error": "An error occurred while synchronizing your wallet:<br />{error}"
"error": "An error occurred while synchronizing your wallet:<br />{error}",
"no_camera": "Unfortunately, your device does not have a camera.<br /> You cannot scan any QR-Code.<br /> In order to export your wallet to another device, you will have to use another method.",
"no_camera_alternatives": "You have 2 other options: \"Import a Wallet file\" (transfer it using a USB key by example, useful if you are offline) or \"Import a TextCode\" (which is a text you will have to transfer with another messaging app)."
},
"emojis": {
"category": {

@ -59,9 +59,12 @@
// Add scanner to Screen.
webScanner.render((decoded_text, decoded_result) => {
console.log(decoded_result);
// Handle scan result
on_qr_scanned(decoded_text);
}, undefined);
}, (error) => {
console.error(error);
});
// Auto-Request camera permissions (there's no native way, unfortunately...)
setTimeout(() => {

@ -41,6 +41,7 @@
display_error,
online,
scanned_qr_code,
check_has_camera,
} from "../store";
import { default as ng } from "../api";
@ -63,6 +64,8 @@
let scanner_state: "before_start" | "scanned" | "success" = "before_start";
let has_camera = false;
async function scrollToTop() {
await tick();
container.scrollIntoView();
@ -78,6 +81,7 @@
scanned_qr_code.set("");
}
await scrollToTop();
has_camera = await check_has_camera();
});
function open_scan_menu() {
@ -98,7 +102,7 @@
generation_state = "loading";
generated_qr = await ng.wallet_export_get_qrcode(
$active_session.session_id,
Math.ceil(container.clientWidth * 0.9)
container.clientWidth
);
generation_state = "generated";
}
@ -184,13 +188,13 @@
</script>
<CenteredLayout>
<div class="container3" bind:this={container}>
<div class="row mb-10">
<Sidebar {nonActiveClass}>
<SidebarWrapper
divClass="bg-gray-60 overflow-y-auto py-4 px-3 rounded dark:bg-gray-800"
>
{#if sub_menu === null}
<div class="container3 mb-20" bind:this={container}>
{#if sub_menu === null}
<Sidebar {nonActiveClass}>
<SidebarWrapper
divClass="bg-gray-60 overflow-y-auto py-4 px-3 rounded dark:bg-gray-800"
>
<SidebarGroup ulClass="space-y-2" role="menu">
<li>
<h2 class="text-xl mb-6">{$t("pages.wallet_info.title")}</h2>
@ -211,7 +215,7 @@
<span class="ml-3">{$t("buttons.back")}</span>
</li>
<!-- Scan QR Code to log in with another device -->
<!-- Scan QR Code to export wallet to another device -->
<li
tabindex="0"
role="menuitem"
@ -228,7 +232,7 @@
<span class="ml-3">{$t("pages.wallet_info.scan_qr")}</span>
</li>
<!-- Generate QR Code to log in with another device -->
<!-- Generate QR Code export wallet to another device -->
<li
tabindex="0"
role="menuitem"
@ -390,8 +394,14 @@
</div>
</Modal>
</SidebarGroup>
{:else if sub_menu === "scan_qr"}
<SidebarGroup ulClass="space-y-2" role="menu">
</SidebarWrapper>
</Sidebar>
{:else if sub_menu === "scan_qr"}
<Sidebar {nonActiveClass}>
<SidebarWrapper
divClass="bg-gray-60 overflow-y-auto py-4 px-3 rounded dark:bg-gray-800"
>
<SidebarGroup ulClass="space-y-6" role="menu">
<li>
<h2 class="text-xl mb-6">
{$t("pages.wallet_info.scan_qr.title")}
@ -411,109 +421,159 @@
/>
<span class="ml-3">{$t("buttons.back")}</span>
</li>
{#if scanner_state === "before_start"}
<!-- NOTES ABOUT QR-->
{#if !has_camera}
<li class="text-left">
{@html $t("pages.wallet_info.scan_qr.notes")}
<br />
{@html $t("wallet_sync.server_transfer_notice")}
<Alert color="red">
{@html $t("wallet_sync.no_camera")}
</Alert>
<Alert color="blue" class="mt-4">
{@html $t("pages.wallet_info.scan_qr.other_has_camera")}
</Alert>
<Alert color="blue" class="mt-4">
{@html $t("pages.wallet_info.scan_qr.no_camera")}
{@html $t("wallet_sync.no_camera_alternatives")}
</Alert>
</li>
<!-- Warning if offline -->
{#if !$online}
{:else}
{#if scanner_state === "before_start"}
<!-- NOTES ABOUT QR-->
<li class="text-left">
<Alert color="red">
{@html $t("wallet_sync.offline_warning")}
</Alert>
{@html $t("pages.wallet_info.scan_qr.notes")}
<br /><br />
{@html $t("wallet_sync.server_transfer_notice")}
</li>
{/if}
<Button
on:click={open_scanner}
disabled={false || !$online}
class="w-full text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
{$t("pages.wallet_info.scan_qr.scan_btn")}
</Button>
{:else if scanner_state === "scanned"}
<li class="">
<Spinner class="mt-4 mb-2" />
<div>
{@html $t("pages.wallet_info.scan_qr.syncing")}...
<br />
<br />
{scanned_qr_code}
</div>
</li>
{:else if scanner_state === "success"}
<li class="text-green-800 flex flex-col items-center">
<div class="mt-4">
<CheckBadge color="green" size="3em" />
</div>
<div class="mt-4">
{@html $t("pages.wallet_info.scan_qr.scan_successful")}
</div>
</li>
<!-- Warning if offline -->
{#if !$online}
<li class="text-left">
<Alert color="red">
{@html $t("wallet_sync.offline_warning")}
</Alert>
</li>
{/if}
<li class="">
<Button
on:click={open_scanner}
disabled={false || !$online}
class="w-full text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
{$t("pages.wallet_info.scan_qr.scan_btn")}
</Button>
</li>
{:else if scanner_state === "scanned"}
<li class="">
<Spinner class="mt-4 mb-2" />
<div>
{@html $t("pages.wallet_info.scan_qr.syncing")}...
<br />
<br />
{scanned_qr_code}
</div>
</li>
{:else if scanner_state === "success"}
<li class="text-green-800 flex flex-col items-center">
<div class="mt-4">
<CheckBadge color="green" size="3em" />
</div>
<div class="mt-4">
{@html $t("pages.wallet_info.scan_qr.scan_successful")}
</div>
</li>
{/if}
{/if}
</SidebarGroup>
<!-- Generate QR-Code screen -->
{:else if sub_menu === "generate_qr"}
<SidebarGroup ulClass="space-y-2" role="menu">
<li>
<h2 class="text-xl mb-6">
{$t("pages.wallet_info.gen_qr.title")}
</h2>
</li>
</SidebarWrapper>
</Sidebar>
<!-- Generate QR-Code screen -->
{:else if sub_menu === "generate_qr"}
{#if generation_state !== "generated"}
<div
class="flex flex-col justify-center max-w-md mb-10 bg-gray-60 overflow-y-auto py-4 dark:bg-gray-800"
>
<div class="mx-6">
<h2 class="text-xl mb-6">
{$t("pages.wallet_info.gen_qr.title")}
</h2>
</div>
<!-- Go Back -->
<li
tabindex="0"
role="menuitem"
class="text-left flex items-center p-2 text-base font-normal text-gray-900 clickable rounded-lg dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700"
on:keypress={to_main_menu}
on:click={to_main_menu}
>
<ArrowLeft
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
<span class="ml-3">{$t("buttons.back")}</span>
</li>
<!-- Go Back -->
<!-- Go Back -->
<!-- Notes about generated QR -->
<div class="mx-6 text-left">
{@html $t("pages.wallet_info.gen_qr.notes")}
<br /><br />
{@html $t("pages.wallet_info.gen_qr.no_camera")}
{@html $t("wallet_sync.no_camera_alternatives")}
<br /><br />
{@html $t("wallet_sync.server_transfer_notice")}
</div>
<!-- Notes about generated QR -->
<li class="text-left">
{@html $t("pages.wallet_info.gen_qr.notes")}
<br />
{@html $t("wallet_sync.server_transfer_notice")}
</li>
<!-- Warning if offline -->
{#if !$online}
<div class="mx-6 text-left">
<Alert color="red">
{@html $t("wallet_sync.offline_warning")}
</Alert>
</div>
{/if}
<!-- Warning if offline -->
{#if !$online}
<li class="text-left">
<Alert color="red">
{@html $t("wallet_sync.offline_warning")}
</Alert>
</li>
{/if}
{#if generation_state === "before_start"}
<div class="mx-6">
<div class="mx-auto">
<div class="my-4 mx-1">
<Button
on:click={generate_qr_code}
disabled={!$online}
class="w-full text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
>
{$t("pages.wallet_info.gen_qr.gen_button")}
</Button></div></div></div>
{:else if generation_state === "loading"}
<Spinner class="mx-auto" size="6" />
{/if}
{#if generation_state === "before_start"}
<Button
on:click={generate_qr_code}
class="w-full text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
<button
on:click={to_main_menu}
class="mt-4 mx-6 text-gray-500 dark:text-gray-400 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><ArrowLeft
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>{$t("buttons.back")}</button
>
{$t("pages.wallet_info.gen_qr.gen_button")}
</Button>
{:else if generation_state === "loading"}
<Spinner class="mx-auto" size="6" />
{:else}
<!-- QR Code -->
<div class="w-full">
{@html generated_qr}
</div>
{/if}
</SidebarGroup>
{:else if sub_menu === "text_code"}
</div>
{:else}
<div
class="flex flex-col justify-center max-w-md mb-20 bg-gray-60 overflow-y-auto py-4 dark:bg-gray-800"
>
<h2 class="text-xl mb-6">
{$t("pages.wallet_info.gen_qr.title")}
</h2>
<div class="text-center mb-2 mx-6">
{@html $t("pages.wallet_login_qr.gen.generated")}
</div>
<!-- Generated QR Code -->
<div class="my-4 mx-auto">
{@html generated_qr}
</div>
<button
on:click={to_main_menu}
class="mt-8 mx-6 text-gray-500 dark:text-gray-400 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><ArrowLeft
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>{$t("buttons.back")}</button
>
</div>
{/if}
{:else if sub_menu === "text_code"}
<Sidebar {nonActiveClass}>
<SidebarWrapper
divClass="bg-gray-60 overflow-y-auto py-4 px-3 rounded dark:bg-gray-800"
>
<SidebarGroup ulClass="space-y-2" role="menu">
<li>
<h2 class="text-xl mb-6">
@ -565,16 +625,16 @@
<Spinner class="mx-auto" size="6" />
{:else}
<!-- TextCode Code -->
<label>{$t("pages.wallet_info.gen_text_code.label")}</label>
<span>{$t("pages.wallet_info.gen_text_code.label")}</span>
<div>
<CopyToClipboard rows={8} value={generated_text_code} />
</div>
{/if}
</div>
</SidebarGroup>
{/if}
</SidebarWrapper>
</Sidebar>
</SidebarWrapper>
</Sidebar>
{/if}
</div>
{#if error}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
@ -587,7 +647,7 @@
</p>
</div>
{/if}
</div>
</CenteredLayout>
<style>

@ -104,7 +104,6 @@
// TODO: Show component: "We got wallet from other device. Please log in to your wallet, to import the device."
}
// Sample textcode AABAOAAAAHNb4y7hdWADqFWDgER3J0xvD3K5D9pZ1wd7Bja4c9cWAOFNpmUIZOFRro0UIpZWr5Ah8U7PlRFe1GFZSKuIextFAA8A45zZUJmUPhfdBrcho1vYPfgda0BAgIT1qjzgEkBQAA"
});
function loggedin() {
@ -364,7 +363,7 @@
</button>
<a href="/wallet/login-qr" use:link>
<button
style="min-width: 250px;justify-content: left;"
style="justify-content: left;"
class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center justify-center dark:focus:ring-primary-100/55 mb-2"
>
<QrCode class="w-8 h-8 mr-2 -ml-1" />
@ -373,7 +372,7 @@
</a>
<a href="/wallet/login-text-code" use:link>
<button
style="min-width: 250px;justify-content: left;"
style="justify-content: left;"
class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
>
<svg
@ -434,7 +433,7 @@
cursor: pointer;
}
.wallet-box button {
min-width: 250px;
min-width: 262px;
}
.securitytxt {
z-index: 100;

@ -1,6 +1,6 @@
<script lang="ts">
import { t } from "svelte-i18n";
import { Alert, Modal, Spinner } from "flowbite-svelte";
import { Alert, Modal, Spinner, Button } from "flowbite-svelte";
import {
ArrowLeft,
Camera,
@ -10,21 +10,21 @@
import { onDestroy, onMount } from "svelte";
import { push } from "svelte-spa-router";
import CenteredLayout from "../lib/CenteredLayout.svelte";
import { wallet_from_import, scanned_qr_code, display_error } from "../store";
import { wallet_from_import, scanned_qr_code, display_error, check_has_camera } from "../store";
import ng from "../api";
// <a href="/wallet/scanqr" use:link>
let top: HTMLElement;
const tauri_platform: string | undefined = import.meta.env.TAURI_PLATFORM;
const use_native_cam =
tauri_platform === "ios" || tauri_platform === "android";
// TODO: Check connectivity to sync service.
let connected = true;
let has_camera: boolean | "checking" = "checking";
const set_online = () => { connected = true; };
const set_offline = () => { connected = false; };
let login_method: "scan" | "gen" | undefined = undefined;
let error;
let connected = true;
let scan_state: "before_start" | "importing" = "before_start";
@ -36,25 +36,7 @@
push("#/wallet/scanqr");
};
const check_has_camera = async () => {
if (!use_native_cam) {
// If there is a camera, go to scan mode, else gen mode.
try {
const devices = await navigator.mediaDevices.enumerateDevices();
has_camera =
devices.filter((device) => device.kind === "videoinput").length > 0;
} catch {
has_camera = false;
}
login_method = has_camera ? "scan" : "gen";
} else {
// TODO: There does not seem to be an API for checking, if the native device
// really supports cameras, as far as I can tell?
// https://github.com/tauri-apps/plugins-workspace/blob/v2/plugins/barcode-scanner/guest-js/index.ts
has_camera = true;
}
};
async function on_qr_scanned(code) {
login_method = "scan";
@ -73,7 +55,7 @@
gen_state = "generating";
try {
const [qr_code_el, code] = await ng.wallet_import_rendezvous(
Math.ceil(top.clientWidth * 0.9)
top.clientWidth
);
rendezvous_code = code;
qr_code_html = qr_code_el;
@ -91,18 +73,23 @@
top.scrollIntoView();
}
onMount(() => {
onMount(async () => {
connected = window.navigator.onLine;
window.addEventListener("offline", set_offline);
window.addEventListener("online", set_online);
// Handle return from QR scanner.
if ($scanned_qr_code) {
on_qr_scanned($scanned_qr_code);
scanned_qr_code.set("");
} else {
// Or check, if a camera exists and offer scanner or QR generator, respectively.
check_has_camera();
login_method = await check_has_camera() ? "scan" : "gen";
}
scrollToTop();
});
onDestroy(() => {
window.removeEventListener("offline", set_offline);
window.removeEventListener("online", set_online);
if (rendezvous_code) {
// TODO: Destroy
}
@ -112,10 +99,10 @@
<CenteredLayout>
<div class="container3" bind:this={top}>
<div
class="flex flex-col justify-center max-w-md mx-6 mb-20 bg-gray-60 overflow-y-auto py-4 dark:bg-gray-800"
class="flex flex-col justify-center max-w-md mb-20 bg-gray-60 overflow-y-auto py-4 dark:bg-gray-800"
>
<!-- Title -->
<div>
<div class="mx-6">
<h2 class="text-xl mb-6">{$t("pages.wallet_login_qr.title")}</h2>
</div>
@ -124,15 +111,16 @@
<div><Spinner /></div>
{:else if !connected}
<!-- Warning, if offline -->
<!-- TODO: just use $online from store to know if it is online -->
<!-- @Niko isnt online only true, when logged in and connected to a broker? -->
<div class="text-left">
<div class="text-left mx-6">
<Alert color="red">
{@html $t("wallet_sync.offline_warning")}
</Alert>
<Alert color="blue" class="mt-4">
{@html $t("pages.wallet_login_qr.offline_advice")}
</Alert>
</div>
{:else if error}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<div class="mx-6 max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<ExclamationTriangle class="animate-bounce mt-10 h-16 w-16 mx-auto" />
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
@ -142,28 +130,33 @@
</p>
</div>
{:else if login_method === "scan"}
{#if scan_state === "before_start"}
<!-- Scan Mode -->
<!-- Notes about QR -->
<div class="text-left text-sm">
{@html $t("pages.wallet_login_qr.scan.description")}
<br />
{@html $t("wallet_sync.server_transfer_notice")}
</div>
{:else if scan_state === "importing"}
<div class="mb-4 w-full">
{@html $t("wallet_sync.importing")}
</div>
<div class="w-full"><Spinner /></div>
{/if}
<div class="mx-6">
{#if scan_state === "before_start"}
<!-- Scan Mode -->
<!-- Notes about QR -->
<div class="text-left">
{@html $t("pages.wallet_login_qr.scan.description")}
<br />
{@html $t("wallet_sync.server_transfer_notice")}
</div>
{:else if scan_state === "importing"}
<div class="mb-4 w-full">
{@html $t("wallet_sync.importing")}
</div>
<div class="w-full"><Spinner /></div>
{/if}
</div>
{:else if login_method === "gen"}
<!-- Generate QR Code to log in with another device -->
{#if gen_state == "before_start"}
<!-- Notes about QR Generation -->
<div class="text-left text-sm">
<div class="text-left mx-6">
{@html $t("pages.wallet_login_qr.gen.description")}
<br />
{@html $t("wallet_sync.no_camera_alternatives")}
<br /><br />
{@html $t("pages.wallet_login_qr.gen.letsgo")}
<br /><br />
{@html $t("wallet_sync.server_transfer_notice")}
</div>
{:else if gen_state === "generating"}
@ -172,54 +165,56 @@
</div>
{:else if gen_state === "generated"}
<!-- Notes about generated QR -->
<div class="text-left text-sm">
<div class="text-center mb-2 mx-6">
{@html $t("pages.wallet_login_qr.gen.generated")}
</div>
<!-- Generated QR Code -->
<div class="my-4 my-auto">
<div class="my-4 mx-auto">
{@html qr_code_html}
</div>
{/if}
{/if}
<div class="mx-auto">
<div class="my-4 mx-1">
{#if login_method === "scan" && scan_state === "before_start"}
<!-- Open Scanner Button-->
<div class="mx-6">
<div class="mx-auto">
<div class="my-4 mx-1">
{#if login_method === "scan" && scan_state === "before_start"}
<!-- Open Scanner Button-->
<button
on:click={open_scanner}
class="mt-4 w-full text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<Camera
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
{$t("pages.wallet_login_qr.scan.button")}
</button>
{:else if login_method === "gen" && gen_state === "before_start"}
<!-- Generate QR Button -->
<Button
disabled={!connected}
on:click={generate_qr}
class="mt-4 w-full text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<QrCode
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
{$t("pages.wallet_login_qr.gen.button")}
</Button>
{/if}
<!-- Go Back -->
<button
on:click={open_scanner}
class="mt-4 w-full text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<Camera
on:click={() => window.history.go(-1)}
class="mt-8 w-full text-gray-500 dark:text-gray-400 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><ArrowLeft
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
{$t("pages.wallet_login_qr.scan.button")}
</button>
{:else if login_method === "gen" && gen_state === "before_start"}
<!-- Generate QR Button -->
<button
on:click={generate_qr}
class="mt-4 w-full text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
/>{$t("buttons.back")}</button
>
<QrCode
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>
{$t("pages.wallet_login_qr.gen.button")}
</button>
{/if}
<!-- Go Back -->
<button
on:click={() => window.history.go(-1)}
class="mt-8 w-full text-gray-500 dark:text-gray-400 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><ArrowLeft
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 focus:outline-none group-hover:text-gray-900 dark:group-hover:text-white"
/>{$t("buttons.back")}</button
>
</div>
</div>
</div>
</div>

@ -177,6 +177,32 @@ export const connection_status: Writable<"disconnected" | "connected" | "connect
let next_reconnect: NodeJS.Timeout | null = null;
export const check_has_camera = async () => {
const tauri_platform: string | undefined = import.meta.env.TAURI_PLATFORM;
const use_native_cam =
tauri_platform === "ios" || tauri_platform === "android";
let has_camera: boolean | "checking" = "checking";
if (!use_native_cam) {
// If there is a camera, go to scan mode, else gen mode.
try {
const devices = await navigator.mediaDevices.enumerateDevices();
has_camera =
devices.filter((device) => device.kind === "videoinput").length > 0;
} catch {
has_camera = false;
}
} else {
// TODO: There does not seem to be an API for checking, if the native device
// really supports cameras, as far as I can tell?
// https://github.com/tauri-apps/plugins-workspace/blob/v2/plugins/barcode-scanner/guest-js/index.ts
has_camera = true;
}
return has_camera;
};
const updateConnectionStatus = ($connections: Record<string, any>) => {
// Reset error state for PeerAlreadyConnected errors.
Object.entries($connections).forEach(([cnx, connection]) => {

@ -11,120 +11,119 @@
/** To format paths, like Settings > Wallet > Generate Wallet QR */
.path {
font-family: monospace;
background-color: rgba(73, 114, 165, 0.1);
font-family: monospace;
background-color: rgba(73, 114, 165, 0.1);
}
/* TODO: hide qr scanner and info button */
#scanner-div {
border: none !important;
/* #scanner-div {
border: none !important;
}
#scanner-div * {
display: none;
display: none;
}
#scanner-div__scan_region {
display: block;
display: block;
}
#scanner-div__scan_region * {
display: block;
display: block;
}
#scanner-div__dashboard_section_csr {
display: none !important;
display: none !important;
}
#html5-qrcode-anchor-scan-type-change {
display: none !important;
display: none !important;
}
[alt="Camera based scan"] {
display: none !important;
display: none !important;
}
[alt="Info icon"] {
display: none !important;
}
display: none !important;
} */
.logo {
padding: 1.5em;
will-change: filter;
transition: 0.75s;
padding-bottom: 1em;
padding: 1.5em;
will-change: filter;
transition: 0.75s;
padding-bottom: 1em;
}
@keyframes pulse-logo-color {
0%,
100% {
fill: rgb(73, 114, 165);
stroke: rgb(73, 114, 165);
}
50% {
/* Mid-transition color */
stroke: #bbb;
fill: #bbb;
}
0%,
100% {
fill: rgb(73, 114, 165);
stroke: rgb(73, 114, 165);
}
50% {
/* Mid-transition color */
stroke: #bbb;
fill: #bbb;
}
}
.logo-pulse path {
animation: pulse-logo-color 2s infinite;
animation-timing-function: cubic-bezier(0.65, 0.01, 0.59, 0.83);
animation: pulse-logo-color 2s infinite;
animation-timing-function: cubic-bezier(0.65, 0.01, 0.59, 0.83);
}
.logo-gray path {
fill: #bbb;
stroke: #bbb;
fill: #bbb;
stroke: #bbb;
}
.logo-blue path {
fill: rgb(73, 114, 165);
stroke: rgb(73, 114, 165);
fill: rgb(73, 114, 165);
stroke: rgb(73, 114, 165);
}
.container3 {
margin: 0;
min-width: 280px;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
margin: 0;
min-width: 280px;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
}
div[role="alert"] div {
display: block;
display: block;
}
.choice-button {
min-width: 320px;
min-width: 320px;
}
.row {
display: flex;
justify-content: center;
display: flex;
justify-content: center;
}
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color: #0f0f0f;
background-color: #f6f6f6;
color: #0f0f0f;
background-color: #f6f6f6;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 305px;
min-height: 100vh;
margin: 0;
display: flex;
place-items: center;
min-width: 305px;
min-height: 100vh;
}
#app {
width: 100%;
width: 100%;
}
/* #app {
/*max-width: 1280px;
@ -138,45 +137,45 @@ body {
} */
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
color: #535bf2;
}
h1 {
text-align: center;
font-size: 3.2em;
line-height: 1.1;
text-align: center;
font-size: 3.2em;
line-height: 1.1;
}
input,
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
color: #0f0f0f;
background-color: #ffffff;
transition: border-color 0.25s;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
color: #0f0f0f;
background-color: #ffffff;
transition: border-color 0.25s;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
}
button {
cursor: pointer;
cursor: pointer;
}
button:hover {
border-color: #396cd8;
border-color: #396cd8;
}
button:active {
border-color: #396cd8;
background-color: #e8e8e8;
border-color: #396cd8;
background-color: #e8e8e8;
}
/* input,
@ -186,25 +185,25 @@ button {
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: dark) {
:root {
color: #f6f6f6;
background-color: #2f2f2f;
}
a:hover {
color: #24c8db;
}
input,
button {
color: #ffffff;
background-color: #0f0f0f98;
}
button:active {
background-color: #0f0f0f69;
}
:root {
color: #f6f6f6;
background-color: #2f2f2f;
}
a:hover {
color: #24c8db;
}
input,
button {
color: #ffffff;
background-color: #0f0f0f98;
}
button:active {
background-color: #0f0f0f69;
}
}

@ -1,2 +1 @@
pub mod wallet_get_export;
pub use wallet_get_export::*;

Loading…
Cancel
Save