chore: add generate code to new sync ui (#15584)

This commit is contained in:
Jamie Caprani 2023-04-20 13:34:30 +01:00 committed by GitHub
parent 7c54537cb3
commit fa21c22fab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 330 additions and 227 deletions

View File

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 539 KiB

After

Width:  |  Height:  |  Size: 539 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 738 KiB

View File

@ -132,8 +132,9 @@
:text text}])]]))))
(defn- password-input
[_]
(let [password-shown? (reagent/atom false)]
[{:keys [default-shown?]
:or {default-shown? false}}]
(let [password-shown? (reagent/atom default-shown?)]
(fn [props]
[base-input
(assoc props
@ -161,6 +162,7 @@
- :label - A string to set as label for this input.
- :char-limit - A number to set a maximum char limit for this input.
- :on-char-limit-reach - Function executed each time char limit is reached or exceeded.
- :default-shown? - boolean to show password input initially
and supports the usual React Native's TextInput properties to control its behaviour:
- :value
- :default-value

View File

@ -30,7 +30,8 @@
{:no-color true})))
(defn left-section-view
[{:keys [on-press icon accessibility-label type icon-background-color] :or {type :grey}}
[{:keys [on-press icon accessibility-label type icon-background-color icon-override-theme]
:or {type :grey}}
put-middle-section-on-left?]
[rn/view {:style (when put-middle-section-on-left? {:margin-right 5})}
[button/button
@ -39,6 +40,7 @@
:type type
:size 32
:accessibility-label accessibility-label
:override-theme icon-override-theme
:override-background-color icon-background-color}
icon]])
@ -150,7 +152,7 @@
:justify-content :flex-end)}
(let [last-icon-index (-> right-section-buttons count dec)]
(map-indexed (fn [index
{:keys [icon on-press type style icon-override-theme accessibility-label]
{:keys [icon on-press type style icon-override-theme accessibility-label label]
:or {type :grey}}]
^{:key index}
[rn/view
@ -161,11 +163,12 @@
:accessible true))
[button/button
{:on-press on-press
:icon true
:icon (not label)
:type type
:before (when label icon)
:size 32
:override-theme icon-override-theme}
icon]])
(if label label icon)]])
right-section-buttons))])
(defn page-nav

View File

@ -1,4 +1,5 @@
(ns quo2.components.share.qr-code.style)
(ns quo2.components.share.qr-code.style
(:require [quo2.foundations.colors :as colors]))
(def container
{:flex-direction :row
@ -8,5 +9,6 @@
[width height]
{:width width
:height height
:background-color colors/white-opa-70
:border-radius 12
:aspect-ratio 1})

View File

@ -1,5 +1,7 @@
(ns react-native.hooks
(:require ["@react-native-community/hooks" :as hooks]))
(:require ["@react-native-community/hooks" :as hooks]
[react-native.core :as rn]
[oops.core :as oops]))
(defn use-keyboard
[]
@ -10,3 +12,21 @@
(defn use-back-handler
[handler]
(.useBackHandler hooks handler))
(defn use-interval
[cb cleanup-cb delay]
(let [saved-callback (rn/use-ref)]
(rn/use-effect
(fn []
(oops/oset! saved-callback "current" cb))
[cb])
(rn/use-effect
(fn []
(let [tick (oops/oget saved-callback "current")]
(when delay
(let [id (js/setInterval tick delay)]
(fn []
(cleanup-cb)
(js/clearInterval id))))))
[delay])))

View File

@ -8,4 +8,4 @@
(def clippath (reagent/adapt-react-class Svg/ClipPath))
(def defs (reagent/adapt-react-class Svg/Defs))
(def circle (reagent/adapt-react-class Svg/Circle))
(def svgxml (reagent/adapt-react-class Svg/SvgXml))

View File

@ -1,33 +0,0 @@
(ns status-im.ui.components.qr-code-viewer.views
(:require ["qrcode" :as qr-code-js]
["react-native-svg" :refer (SvgXml)]
[cljs-bean.core :as bean]
[reagent.core :as reagent]
[status-im.ui.components.qr-code-viewer.styles :as styles]
[status-im.ui.components.react :as react]))
(def svgxml (reagent/adapt-react-class SvgXml))
(defn qr-code
[{:keys [size value]}]
(let [uri (reagent/atom nil)]
(.toString
qr-code-js
value
(bean/->js {:margin 0 :width size})
#(reset! uri %2))
(fn []
(when @uri
[svgxml {:xml @uri :width size :height size}]))))
(defn qr-code-view
"Qr Code view including the frame.
Note: `size` includes frame with `styles/qr-code-padding.`"
[size value]
(when (and size value)
[react/view
{:style (styles/qr-code-container size)
:accessibility-label :qr-code-image}
[qr-code
{:value value
:size (- size (* styles/qr-code-padding 2))}]]))

View File

@ -11,7 +11,7 @@
[status-im.ui.components.copyable-text :as copyable-text]
[status-im.ui.components.list-selection :as list-selection]
[status-im.ui.components.profile-header.view :as profile-header]
[status-im.ui.components.qr-code-viewer.views :as qr-code-viewer]
[status-im2.common.qr-code-viewer.view :as qr-code-viewer]
[status-im.ui.components.react :as react]
[status-im.ui.screens.profile.user.edit-picture :as edit]
[status-im.ui.screens.profile.user.styles :as styles]

View File

@ -6,7 +6,7 @@
[status-im.ethereum.eip681 :as eip681]
[utils.i18n :as i18n]
[status-im.ui.components.copyable-text :as copyable-text]
[status-im.ui.components.qr-code-viewer.views :as qr-code-viewer]
[status-im2.common.qr-code-viewer.view :as qr-code-viewer]
[status-im.ui.components.react :as react])
(:require-macros [status-im.utils.views :as views]))

View File

@ -1,4 +1,4 @@
(ns status-im.ui.components.qr-code-viewer.styles
(ns status-im2.common.qr-code-viewer.style
(:require [quo.design-system.colors :as colors]))
(def qr-code-padding 16)

View File

@ -0,0 +1,31 @@
(ns status-im2.common.qr-code-viewer.view
(:require ["qrcode" :as qr-code-js]
[cljs-bean.core :as bean]
[reagent.core :as reagent]
[status-im2.common.qr-code-viewer.style :as style]
[react-native.core :as rn]
[react-native.svg :as svg]))
(defn qr-code
[{:keys [size value]}]
(let [uri (reagent/atom nil)]
(.toString
qr-code-js
value
(bean/->js {:margin 0 :width size})
#(reset! uri %2))
(fn []
(when @uri
[svg/svgxml {:xml @uri :width size :height size}]))))
(defn qr-code-view
"Qr Code view including the frame.
Note: `size` includes frame with `style/qr-code-padding.`"
[size value]
(when (and size value)
[rn/view
{:style (style/qr-code-container size)
:accessibility-label :qr-code-image}
[qr-code
{:value value
:size (- size (* style/qr-code-padding 2))}]]))

View File

@ -5,11 +5,12 @@
:lifestyle (js/require "../resources/images/ui2/lifestyle.png")
:music (js/require "../resources/images/ui2/music.png")
:podcasts (js/require "../resources/images/ui2/podcasts.png")
:sync-device (js/require "../resources/images/ui2/sync-new-device-cover-background.png")
:generate-keys (js/require "../resources/images/ui2/generate_keys.png")
:ethereum-address (js/require "../resources/images/ui2/ethereum_address.png")
:generate-keys (js/require "../resources/images/ui2/generate-keys.png")
:ethereum-address (js/require "../resources/images/ui2/ethereum-address.png")
:use-keycard (js/require "../resources/images/ui2/keycard.png")
:onboarding-illustration (js/require "../resources/images/ui2/onboarding_illustration.png")})
:onboarding-illustration (js/require "../resources/images/ui2/onboarding_illustration.png")
:qr-code (js/require "../resources/images/ui2/qr-code.png")
})
(def mock-images
{:coinbase (js/require "../resources/images/mock2/coinbase.png")

View File

@ -1,6 +1,5 @@
(ns status-im2.contexts.syncing.events
(:require [status-im.native-module.core :as status]
[status-im2.contexts.syncing.sheets.enter-password.view :as sheet]
[taoensso.timbre :as log]
[utils.re-frame :as rf]
[utils.security.core :as security]
@ -62,15 +61,12 @@
(rf/defn preparations-for-connection-string
{:events [:syncing/get-connection-string-for-bootstrapping-another-device]}
[{:keys [db]} entered-password]
[{:keys [db]} entered-password set-code]
(let [valid-password? (>= (count entered-password) constants/min-password-length)
show-sheet (fn [connection-string]
(rf/dispatch
[:show-bottom-sheet
{:content (fn []
[sheet/qr-code-view-with-connection-string
connection-string])}])
(rf/dispatch [:syncing/update-role constants/local-pairing-role-sender]))]
(set-code connection-string)
(rf/dispatch [:syncing/update-role constants/local-pairing-role-sender])
(rf/dispatch [:bottom-sheet/hide]))]
(if valid-password?
(let [sha3-pwd (status/sha3 (str (security/safe-unmask-data entered-password)))
key-uid (get-in db [:multiaccount :key-uid])

View File

@ -0,0 +1,50 @@
(ns status-im2.contexts.syncing.setup-syncing.style
(:require [quo2.foundations.colors :as colors]))
(defn container-main
[top]
{:background-color colors/neutral-95
:flex 1
:padding-top top})
(def page-container
{:margin-horizontal 20})
(def title-container
{:flex-direction :row
:align-items :center
:justify-content :space-between})
(def navigation-bar
{:height 56})
(def sync-code
{:margin-top 36})
(defn qr-container
[valid-code?]
(merge {:margin-top 12
:background-color colors/white-opa-5
:border-radius 20
:padding 12}
(if valid-code?
{:flex 1}
{:aspect-ratio 1})))
(def sub-text-container
{:margin-bottom 8
:justify-content :space-between
:align-items :center
:flex-direction :row})
(def valid-cs-container
{:flex 1
:margin 12})
(def generate-button
{:position :absolute
:top "50%"
:bottom 0
:left 0
:right 0
:margin-horizontal 60})

View File

@ -0,0 +1,148 @@
(ns status-im2.contexts.syncing.setup-syncing.view
(:require [utils.i18n :as i18n]
[clojure.string :as string]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[react-native.core :as rn]
[utils.datetime :as datetime]
[status-im2.contexts.syncing.setup-syncing.style :as style]
[utils.re-frame :as rf]
[status-im2.constants :as constants]
[react-native.clipboard :as clipboard]
[status-im2.contexts.syncing.sheets.enter-password.view :as enter-password]
[status-im2.common.qr-code-viewer.view :as qr-code-viewer]
[reagent.core :as reagent]
[status-im2.common.resources :as resources]
[react-native.hooks :as hooks]
[react-native.safe-area :as safe-area]))
(def code-valid-for-ms 120000)
(def one-min-ms 60000)
(defn navigation-bar
[]
[rn/view {:style style/navigation-bar}
[quo/page-nav
{:align-mid? true
:mid-section {:type :text-only :main-text ""}
:left-section {:type :grey
:icon :i/close
:icon-override-theme :dark
:on-press #(rf/dispatch [:navigate-back])}
:right-section-buttons [{:type :grey
:label (i18n/label :t/how-to-scan)
:icon :i/info
:icon-override-theme :dark
:on-press #(js/alert "to be implemented")}]}]])
(defn valid-cs?
[connection-string]
(when connection-string
(string/starts-with?
connection-string
constants/local-pairing-connection-string-identifier)))
(defn view
[]
(let [valid-for-ms (reagent/atom code-valid-for-ms)
code (reagent/atom nil)
delay (reagent/atom nil)
timestamp (reagent/atom nil)
set-code (fn [connection-string]
(when (valid-cs? connection-string)
(reset! timestamp (* 1000 (js/Math.ceil (/ (datetime/timestamp) 1000))))
(reset! delay 1000)
(reset! code connection-string)))
clock (fn []
(if (pos? (- code-valid-for-ms
(- (* 1000 (js/Math.ceil (/ (datetime/timestamp) 1000)))
@timestamp)))
(swap! valid-for-ms (fn [_]
(- code-valid-for-ms
(- (* 1000
(js/Math.ceil (/ (datetime/timestamp) 1000)))
@timestamp))))
(reset! delay nil)))
cleanup-clock (fn []
(reset! code nil)
(reset! timestamp nil)
(reset! valid-for-ms code-valid-for-ms))]
[:f>
(fn []
(hooks/use-interval clock
cleanup-clock
@delay)
[safe-area/consumer
(fn [{:keys [top]}]
[rn/view {:style (style/container-main top)}
[rn/scroll-view {}
[navigation-bar]
[rn/view {:style style/page-container}
[rn/view {:style style/title-container}
[quo/text
{:size :heading-1
:weight :semi-bold
:style {:color colors/white}}
(i18n/label :t/setup-syncing)]]
[rn/view {:style (style/qr-container (valid-cs? @code))}
(if (valid-cs? @code)
[qr-code-viewer/qr-code-view 331 @code]
[quo/qr-code
{:source (resources/get-image :qr-code)
:height 220
:width "100%"}])
(when-not (valid-cs? @code)
[quo/button
{:on-press (fn []
;TODO https://github.com/status-im/status-mobile/issues/15570
;remove old bottom sheet when Authentication process design is created.
(rf/dispatch [:bottom-sheet/hide-old])
(rf/dispatch [:bottom-sheet/show-sheet-old
{:content (fn []
[enter-password/sheet set-code])}]))
:size 40
:style style/generate-button
:before :i/reveal} (i18n/label :t/reveal-sync-code)])
(when (valid-cs? @code)
[rn/view
{:style style/valid-cs-container}
[rn/view
{:style style/sub-text-container}
[quo/text
{:size :paragraph-2
:style {:color colors/white-opa-40}}
(i18n/label :t/sync-code)]
[quo/text
{:size :paragraph-2
:style {:color (if (< @valid-for-ms one-min-ms)
colors/danger-60
colors/white-opa-40)}}
(i18n/label :t/valid-for-time {:valid-for (datetime/ms-to-duration @valid-for-ms)})]]
[quo/input
{:default-value @code
:type :password
:override-theme :dark
:default-shown? true
:editable false}]
[quo/button
{:on-press (fn []
(clipboard/set-string @code)
(rf/dispatch [:toasts/upsert
{:icon :correct
:icon-color colors/success-50
:text (i18n/label
:t/sharing-copied-to-clipboard)}]))
:override-theme :dark
:type :grey
:style {:margin-top 12}
:before :i/copy}
(i18n/label :t/copy-qr)]])]]
[rn/view {:style style/sync-code}
[quo/divider-label
{:label (i18n/label :t/have-a-sync-code?)
:increase-padding-top? true}]
[quo/action-drawer
[[{:icon :i/scan
:override-theme :dark
:on-press #(js/alert "to be implemented")
:label (i18n/label :t/Scan-or-enter-sync-code)}]]]]]])])]))

View File

@ -1,45 +1,14 @@
(ns status-im2.contexts.syncing.sheets.enter-password.view
(:require [clojure.string :as string]
[utils.i18n :as i18n]
(:require [utils.i18n :as i18n]
[quo.core :as quo-old]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[react-native.core :as rn]
[status-im2.constants :as constants]
[status-im.ui.components.qr-code-viewer.views :as qr-code-viewer]
[utils.re-frame :as rf]))
(defn qr-code-view-with-connection-string
[connection-string]
(let [window-width (rf/sub [:dimensions/window-width])
eighty-percent-screen-width (* window-width 0.8)
valid-cs? (string/starts-with?
connection-string
constants/local-pairing-connection-string-identifier)]
[:<>
(if valid-cs?
[rn/view {:margin 20}
[quo/text
{:accessibility-label :sync-code-generated
:weight :bold
:size :heading-1
:style {:color colors/neutral-100
:margin 20}}
(i18n/label :t/sync-code-generated)]
[qr-code-viewer/qr-code-view eighty-percent-screen-width connection-string]
[quo/information-box
{:type :informative
:closable? false
:icon :i/placeholder
:style {:margin-top 20}} (i18n/label :t/instruction-after-qr-generated)]]
[rn/view {:margin 20}
[rn/view {:padding-horizontal 8}
[quo/button
{:on-press #(rf/dispatch [:preparations-for-connection-string])}
(i18n/label :t/try-your-luck-again)]]])]))
;;TODO : this file is temporary and will be removed for new design auth method
(defn sheet
[]
[set-code]
(let [entered-password (atom "")]
[:<>
[rn/view {:margin 20}
@ -53,7 +22,7 @@
(i18n/label :t/enter-your-password)]
[rn/view {:flex-direction :row :align-items :center}
[rn/view {:flex 1}
[quo-old/text-input ;;TODO : migrate text-input from quo to quo2 namespace
[quo-old/text-input
{:placeholder (i18n/label :t/enter-your-password)
:auto-focus true
:accessibility-label :password-input
@ -64,6 +33,10 @@
{:padding-horizontal 18
:margin-top 20}
[quo/button
{:on-press #(rf/dispatch [:syncing/get-connection-string-for-bootstrapping-another-device
@entered-password])}
{:on-press (fn []
;TODO https://github.com/status-im/status-mobile/issues/15570
;remove old bottom sheet when Authentication process design is created.
(rf/dispatch [:bottom-sheet/hide-old])
(rf/dispatch [:syncing/get-connection-string-for-bootstrapping-another-device
@entered-password set-code]))}
(i18n/label :t/generate-scan-sync-code)]]]]]))

View File

@ -1,34 +0,0 @@
(ns status-im2.contexts.syncing.sheets.sync-device-notice.styles
(:require [quo2.foundations.colors :as colors]))
(def sync-devices-header
{:width "100%"})
(def sync-devices-header-image
{:width "100%"
:height 192})
(def sync-devices-body-container
{:margin-bottom 20
:border-radius 20
:z-index 2
:margin-top -16
:background-color colors/white
:padding 20})
(def header-text
{:color colors/neutral-100})
(def instructions-text
{:color colors/neutral-100
:margin-top 8})
(def list-item-text
{:color colors/neutral-100
:margin-top 18})
(def setup-syncing-button
{:margin-top 21})
(def secondary-body-container
{:margin-top 21})

View File

@ -1,62 +0,0 @@
(ns status-im2.contexts.syncing.sheets.sync-device-notice.view
(:require [quo2.core :as quo]
[react-native.core :as rn]
[status-im2.contexts.syncing.sheets.enter-password.view :as enter-password]
[status-im2.contexts.syncing.sheets.sync-device-notice.styles :as styles]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[status-im2.common.resources :as resources]))
(defn sheet
[]
[:<>
[rn/view {:style styles/sync-devices-header}
[rn/image
{:source (resources/get-image :sync-device)
:style styles/sync-devices-header-image}]]
[rn/view {:style styles/sync-devices-body-container}
[quo/text
{:accessibility-label :privacy-policy
:weight :bold
:size :heading-1
:style styles/header-text}
(i18n/label :t/sync-new-device)]
[quo/text
{:accessibility-label :privacy-policy
:weight :regular
:size :paragraph-1
:style styles/instructions-text}
(i18n/label :t/sync-instructions-text)]
[quo/text
{:accessibility-label :privacy-policy
:weight :regular
:size :paragraph-2
:style styles/list-item-text}
(i18n/label :t/sync-instruction-step-1)]
[quo/text
{:accessibility-label :privacy-policy
:weight :regular
:size :paragraph-2
:style styles/list-item-text}
(i18n/label :t/sync-instruction-step-2)]
[quo/text
{:accessibility-label :privacy-policy
:weight :regular
:size :paragraph-2
:style styles/list-item-text}
(i18n/label :t/sync-instruction-step-3)]
[quo/button
{:type :secondary
:size 40
:style styles/setup-syncing-button
:before :i/face-id20
:on-press #(rf/dispatch [:show-bottom-sheet
;; this should be a modal screen
{:content (fn []
[enter-password/sheet])}])}
(i18n/label :t/setup-syncing)]]])

View File

@ -1,4 +1,4 @@
(ns status-im2.contexts.syncing.style
(ns status-im2.contexts.syncing.syncing-devices-list.style
(:require [quo2.foundations.colors :as colors]))
(def container-main

View File

@ -1,10 +1,9 @@
(ns status-im2.contexts.syncing.view
(ns status-im2.contexts.syncing.syncing-devices-list.view
(:require [utils.i18n :as i18n]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[react-native.core :as rn]
[status-im2.contexts.syncing.sheets.sync-device-notice.view :as sync-device-notice]
[status-im2.contexts.syncing.style :as style]
[status-im2.contexts.syncing.syncing-devices-list.style :as style]
[status-im2.common.not-implemented :as not-implemented]
[utils.re-frame :as rf]))
@ -15,7 +14,7 @@
[quo/page-nav
{:align-mid? true
:mid-section {:type :text-only :main-text ""}
:left-section {:type :blur-bg
:left-section {:type :grey
:icon :i/arrow-left
:icon-override-theme :dark
:on-press #(rf/dispatch [:navigate-back])}}]])
@ -64,10 +63,7 @@
[quo/button
{:size 32
:icon true
:on-press #(rf/dispatch [:show-bottom-sheet
{:show-handle? false
:content (fn []
[sync-device-notice/sheet])}])}
:on-press #(rf/dispatch [:navigate-to :settings-setup-syncing])}
:i/add]]
[rn/view {:style style/devices-container}
[render-device

View File

@ -30,22 +30,26 @@
:backgroundColor (colors/theme-colors colors/white colors/neutral-100)}}))
(defn navbar
[]
{:navigationBar {:backgroundColor (colors/theme-colors colors/white colors/neutral-100)}})
([dark?]
{:navigationBar {:backgroundColor (if (or dark? colors/dark?) colors/neutral-100 colors/white)}})
([] (navbar nil)))
(defn statusbar
[]
([dark?]
(let [style (if (or dark? colors/dark?) :light :dark)]
(if platform/android?
{:statusBar {:translucent true
:backgroundColor :transparent
:drawBehind true
:style (if (colors/dark?) :light :dark)}}
{:statusBar {:style (if (colors/dark?) :light :dark)}}))
:style style}}
{:statusBar {:style style}})))
([] (statusbar nil)))
(defn statusbar-and-navbar
[]
(merge (navbar) (statusbar)))
([dark?]
(merge (navbar dark?) (statusbar dark?)))
([] (statusbar-and-navbar nil)))
(defn topbar-options
[]

View File

@ -24,12 +24,12 @@
[status-im2.contexts.onboarding.profiles.view :as profiles]
[status-im2.contexts.quo-preview.main :as quo.preview]
[status-im2.contexts.shell.view :as shell]
[status-im2.contexts.syncing.view :as settings-syncing]
[status-im2.contexts.syncing.syncing-devices-list.view :as settings-syncing]
[status-im2.navigation.options :as options]
[status-im2.contexts.chat.group-details.view :as group-details]
[status-im.ui.screens.screens :as old-screens]
[status-im2.contexts.communities.menus.request-to-join.view :as join-menu]))
[status-im2.contexts.communities.menus.request-to-join.view :as join-menu]
[status-im2.contexts.syncing.setup-syncing.view :as settings-setup-syncing]))
(defn screens
[]
@ -81,10 +81,15 @@
:component communities.overview/overview}
{:name :settings-syncing
:options {:statusBar {:style :light}
:insets {:top false}}
:options (options/statusbar true)
:component settings-syncing/view}
{:name :settings-setup-syncing
:options (options/statusbar true)
:component settings-setup-syncing/view}
;; Onboarding
{:name :profiles
:component profiles/views}

View File

@ -255,7 +255,7 @@
(* 1000 sec))
(defn ms-to-duration
"milisecods to mm:ss format"
"miliseconds to mm:ss format"
[ms]
(let [sec (quot ms 1000)]
(gstring/format "%02d:%02d" (quot sec 60) (mod sec 60))))

View File

@ -659,6 +659,7 @@
"group-info": "Group info",
"gwei": "Gwei",
"hash": "Hash",
"have-a-sync-code?": "Have a sync code?",
"help": "help",
"help-capitalized": "Help",
"help-center": "Help Center",
@ -669,6 +670,7 @@
"hold-card": "Hold card to the back\n of your phone",
"home": "Home",
"hooks": "Hooks",
"how-to-scan": "How to scan",
"identifier": "Identifier",
"if-you-cancel": "If you cancel, you can request to join this community at any point.",
"image-remove-current": "Remove current photo",
@ -1196,12 +1198,14 @@
"reset-card": "Reset card",
"reset-card-description": "This operation will reset card to initial state. It will erase all card data including private keys. Operation is not reversible.",
"retry": "Retry",
"reveal-sync-code": "Reveal sync code",
"revoke-access": "Revoke access",
"rpc-url": "RPC URL",
"save": "Save",
"save-password": "Save password",
"save-password-unavailable": "Set device passcode to save password",
"save-password-unavailable-android": "Save password is unavailable: your device may be rooted or lacks necessary security features.",
"Scan-or-enter-sync-code": "Scan or enter sync code",
"scan-qr": "Scan QR code",
"scan-qr-code": "Scan a QR code with a wallet address",
"search": "Search",
@ -1411,6 +1415,7 @@
"usd-currency": "USD",
"use-the-multichain-wallet": "Use the leading multi-chain self-custodial wallet",
"use-valid-contact-code": "Please enter or scan a valid chat key or username",
"valid-for-time": "Valid for {{valid-for}}",
"validation-amount-invalid-number": "Amount is not a valid number",
"validation-amount-is-too-precise": "Amount is too precise. Max number of decimals is {{decimals}}.",
"version": "App version",
@ -1993,12 +1998,8 @@
"local-pairing-experimental-mode": "Local Pairing Mode (alpha)",
"syncing": "Syncing",
"synced-devices": "Synced Devices",
"sync-new-device": "Sync a new device",
"sync-instructions-text": "You own your data. Synchronize it among all your devices.",
"sync-instruction-step-1": "1. Verify login with password",
"sync-instruction-step-2": "2. Reveal a temporary QR and Sync Code",
"sync-instruction-step-3": "3. Share that info with your new device",
"setup-syncing": "Setup Syncing",
"sync-code": "Sync Code",
"sync-code-generated": "Sync code generated",
"generate-scan-sync-code": "Generate Scan Sync Code",
"try-your-luck-again": "Try your luck again!",