UI for mobile to mobile local pairing - updated (#14514)

* ui for local pairing

lint-fix

removed un-necessary +

addressing some of the feedback on PR

more feedback + removing feature toggle from ui

getting rid of comments/log messages over here

tidy up logs

fix typos and more i18n stuff

swap % with a named parameter

getting rid of global state + lint-fix

get rid of un-used function

icon guidelines and more kebab case stuff :>

moving stuff to events and utils namespace

:main-icons -> :i :)

address feedback and adhere to guidelines etc

fixed the :t/ qualification

moree feedback :-D

referring status-im.utils.security for now

adding "cs" to constants

make tests pass

re-frame to rf

addressing feedback

moving icons to icons2 & renaming stuff

trying to make this file the way it was before

missed out on updating these references

getting rid of the icons moved to icons2

This reverts commit be8552c0d3daaf7a7333cfeaf304d97c86d50d3e.

fixing mistakes

getting rid of the s

* this rename makes sense to me

* adding an alias to the view

* fixed broken up namespaces
This commit is contained in:
Siddarth Kumar 2022-12-16 18:40:56 +05:30 committed by GitHub
parent 621e41e6ab
commit fc07fbf787
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 437 additions and 16 deletions

1
.env
View File

@ -32,3 +32,4 @@ COMMANDS_ENABLED=1
TWO_MINUTES_SYNCING=1
SWAP_ENABLED=1
STICKERS_TEST_ENABLED=1
LOCAL_PAIRING_ENABLED=1

View File

@ -32,3 +32,4 @@ COMMUNITIES_MANAGEMENT_ENABLED=1
DELETE_MESSAGE_ENABLED=1
TWO_MINUTES_SYNCING=1
STICKERS_TEST_ENABLED=1
LOCAL_PAIRING_ENABLED=1

View File

@ -34,3 +34,4 @@ DELETE_MESSAGE_ENABLED=1
TWO_MINUTES_SYNCING=1
ENABLE_QUO_PREVIEW=1
STICKERS_TEST_ENABLED=1
LOCAL_PAIRING_ENABLED=1

View File

@ -940,6 +940,52 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
StatusThreadPoolExecutor.getInstance().execute(r);
}
@ReactMethod
public void getConnectionStringForBootstrappingAnotherDevice(final String configJSON, final Callback callback) throws JSONException {
final JSONObject jsonConfig = new JSONObject(configJSON);
final String keyUID = jsonConfig.getString("keyUID");
final String keyStorePath = this.getKeyStorePath(keyUID);
jsonConfig.put("keystorePath", keyStorePath);
if (!checkAvailability()) {
callback.invoke(false);
return;
}
Runnable runnableTask = new Runnable() {
@Override
public void run() {
String res = Statusgo.getConnectionStringForBootstrappingAnotherDevice(jsonConfig.toString());
callback.invoke(res);
}
};
StatusThreadPoolExecutor.getInstance().execute(runnableTask);
}
@ReactMethod
public void inputConnectionStringForBootstrapping(final String connectionString, final String configJSON, final Callback callback) throws JSONException {
final JSONObject jsonConfig = new JSONObject(configJSON);
final String keyStorePath = pathCombine(this.getNoBackupDirectory(), "/keystore");
jsonConfig.put("keystorePath", keyStorePath);
if (!checkAvailability()) {
callback.invoke(false);
return;
}
Runnable runnableTask = new Runnable() {
@Override
public void run() {
String res = Statusgo.inputConnectionStringForBootstrapping(connectionString,jsonConfig.toString());
callback.invoke(res);
}
};
StatusThreadPoolExecutor.getInstance().execute(runnableTask);
}
@ReactMethod
public void hashTypedData(final String data, final Callback callback) {
Log.d(TAG, "hashTypedData");

View File

@ -318,6 +318,42 @@ RCT_EXPORT_METHOD(hashMessage:(NSString *)message
callback(@[result]);
}
//////////////////////////////////////////////////////////////////// getConnectionStringForBootstrappingAnotherDevice
RCT_EXPORT_METHOD(getConnectionStringForBootstrappingAnotherDevice:(NSString *)configJSON
callback:(RCTResponseSenderBlock)callback) {
NSData *configData = [configJSON dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *configDict = [NSJSONSerialization JSONObjectWithData:configData options:NSJSONReadingMutableContainers error:nil];
NSString *keyUID = [configDict objectForKey:@"keyUID"];
NSURL *multiaccountKeystoreDir = [self getKeyStoreDir:keyUID];
NSString *keystoreDir = multiaccountKeystoreDir.path;
[configDict setValue:keystoreDir forKey:@"keystorePath"];
NSString *modifiedConfigJSON = [configDict bv_jsonStringWithPrettyPrint:NO];
NSString *result = StatusgoGetConnectionStringForBootstrappingAnotherDevice(modifiedConfigJSON);
callback(@[result]);
}
//////////////////////////////////////////////////////////////////// inputConnectionStringForBootstrapping
RCT_EXPORT_METHOD(inputConnectionStringForBootstrapping:(NSString *)cs
configJSON:(NSString *)configJSON
callback:(RCTResponseSenderBlock)callback) {
NSData *configData = [configJSON dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *configDict = [NSJSONSerialization JSONObjectWithData:configData options:NSJSONReadingMutableContainers error:nil];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *rootUrl =[[fileManager URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *multiaccountKeystoreDir = [rootUrl URLByAppendingPathComponent:@"keystore"];
NSString *keystoreDir = multiaccountKeystoreDir.path;
[configDict setValue:keystoreDir forKey:@"keystorePath"];
NSString *modifiedConfigJSON = [configDict bv_jsonStringWithPrettyPrint:NO];
NSString *result = StatusgoInputConnectionStringForBootstrapping(cs,modifiedConfigJSON);
callback(@[result]);
}
//////////////////////////////////////////////////////////////////// hashTypedData
RCT_EXPORT_METHOD(hashTypedData:(NSString *)data
callback:(RCTResponseSenderBlock)callback) {
@ -502,11 +538,11 @@ RCT_EXPORT_METHOD(saveAccountAndLoginWithKeycard:(NSString *)multiaccountData
- (NSString *) getExportDbFilePath {
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"export.db"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:filePath]) {
[fileManager removeItemAtPath:filePath error:nil];
}
return filePath;
}
@ -957,10 +993,10 @@ RCT_EXPORT_METHOD(exportUnencryptedDatabase:(NSString *)accountData
#if DEBUG
NSLog(@"exportUnencryptedDatabase() method called");
#endif
NSString *filePath = [self getExportDbFilePath];
StatusgoExportUnencryptedDatabase(accountData, password, filePath);
callback(@[filePath]);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 738 KiB

View File

@ -200,3 +200,10 @@
(def ^:const community-member-role-all 1)
(def ^:const community-member-role-manage-users 2)
(def ^:const community-member-role-moderator 3)
(def local-pairing-connection-string-identifier
"If any string begins with cs we know its a connection string.
This is useful when we read QR codes we know it is a connection string if it begins with this identifier.
An example of a connection string is -> cs2:5vd6J6:Jfc:27xMmHKEYwzRGXcvTtuiLZFfXscMx4Mz8d9wEHUxDj4p7:EG7Z13QScfWBJNJ5cprszzDQ5fBVsYMirXo8MaQFJvpF:3 "
"cs")

View File

@ -239,6 +239,23 @@
(log/debug "[native-module] hash-message")
(.hashMessage ^js (status) message callback))
(defn get-connection-string-for-bootstrapping-another-device
"Generates connection string form status-go for the purpose of local pairing on the sender end"
[config-json callback]
(log/info "[native-module] Fetching Connection String"
{:fn :get-connection-string-for-bootstrapping-another-device
:config-json config-json})
(.getConnectionStringForBootstrappingAnotherDevice ^js (status) config-json callback))
(defn input-connection-string-for-bootstrapping
"Provides connection string to status-go for the purpose of local pairing on the receiver end"
[connection-string config-json callback]
(log/info "[native-module] Sending Connection String"
{:fn :input-connection-string-for-bootstrapping
:config-json config-json
:connection-string connection-string})
(.inputConnectionStringForBootstrapping ^js (status) connection-string config-json callback))
(defn hash-typed-data
"used for keycard"
[data callback]

View File

@ -9,7 +9,8 @@
[status-im.add-new.db :as new-chat.db]
[status-im.utils.fx :as fx]
[status-im.group-chats.core :as group-chats]
[clojure.string :as string]))
[clojure.string :as string]
[taoensso.timbre :as log]))
(fx/defn scan-qr-code
{:events [::scan-code]}
@ -87,6 +88,11 @@
{:dispatch [:wallet-connect-legacy/pair data]}
{:dispatch [:wallet-connect/pair data]})))
(fx/defn handle-local-pairing
{:events [::handle-local-pairing-uri]}
[_ data]
{:dispatch [:syncing/input-connection-string-for-bootstrapping data]})
(fx/defn match-scan
{:events [::match-scanned-value]}
[cofx {:keys [type] :as data}]
@ -98,9 +104,14 @@
:browser (handle-browse cofx data)
:eip681 (handle-eip681 cofx data)
:wallet-connect (handle-wallet-connect cofx data)
{:dispatch [:navigate-back]
:utils/show-popup {:title (i18n/label :t/unable-to-read-this-code)
:on-dismiss #(re-frame/dispatch [:pop-to-root-tab :chat-stack])}}))
:localpairing (handle-local-pairing cofx data)
(do
(log/info "Unable to find matcher for scanned value"
{:type type
:event ::match-scanned-value})
{:dispatch [:navigate-back]
:utils/show-popup {:title (i18n/label :t/unable-to-read-this-code)
:on-dismiss #(re-frame/dispatch [:pop-to-root-tab :chat-stack])}})))
(fx/defn on-scan
{:events [::on-scan-success]}

View File

@ -12,6 +12,7 @@
[status-im.utils.http :as http]
[status-im.utils.security :as security]
[status-im.utils.wallet-connect :as wallet-connect]
[status-im.constants :as constants]
[taoensso.timbre :as log]))
(def ethereum-scheme "ethereum:")
@ -223,6 +224,9 @@
(wallet-connect/url? uri)
(cb {:type :wallet-connect :data uri})
(string/starts-with? uri constants/local-pairing-connection-string-identifier)
(cb {:type :localpairing :data uri})
:else
(cb {:type :undefined
:data uri}))))

View File

@ -50,6 +50,10 @@
{:db (assoc db :peer-stats peer-stats
:peers-count (count (:peers peer-stats)))}))
(defn handle-local-pairing-signals [signal-type]
(log/info "local pairing signal received"
{:signal-type signal-type}))
(fx/defn process
{:events [:signals/signal-received]}
[{:keys [db] :as cofx} event-str]
@ -77,4 +81,5 @@
"local-notifications" (local-notifications/process cofx (js->clj event-js :keywordize-keys true))
"community.found" (link.preview/cache-community-preview-data (js->clj event-js :keywordize-keys true))
"status.updates.timedout" (visibility-status-updates/handle-visibility-status-updates cofx (js->clj event-js :keywordize-keys true))
"localPairing" (handle-local-pairing-signals event-str)
(log/debug "Event " type " not handled"))))

View File

@ -2,7 +2,7 @@
(:require [re-frame.core :as re-frame]
[status-im.i18n.i18n :as i18n]
[quo.core :as quo]
[status-im.utils.config :as config]
[status-im2.setup.config :as config]
[status-im.ui.components.list.views :as list])
(:require-macros [status-im.utils.views :as views]))

View File

@ -6,10 +6,11 @@
[status-im.multiaccounts.key-storage.core :as multiaccounts.key-storage]
[status-im.keycard.recovery :as keycard]
[status-im.i18n.i18n :as i18n]
[status-im.utils.config :as config]
[status-im2.setup.config :as config]
[status-im.utils.security]
[quo.design-system.colors :as colors]
[quo.core :as quo]
[status-im.qr-scanner.core :as qr-scanner]
[status-im.react-native.resources :as resources]
[status-im.ui.components.icons.icons :as icons]))
@ -89,7 +90,17 @@
[quo/list-item {:theme :accent
:on-press #(hide-sheet-and-dispatch [:multiaccounts.login.ui/export-db-submitted])
:icon :main-icons/send
:title "Export unencrypted"}])]]))
:title "Export unencrypted"}])
(when config/local-pairing-mode-enabled?
[:<>
[quo/list-item {:theme :accent
:on-press #(hide-sheet-and-dispatch [::qr-scanner/scan-code {:handler ::qr-scanner/on-scan-success}])
:icon :i/key
:title (i18n/label :t/scan-sync-code)}]
[quo/list-item {:theme :accent
:on-press #(hide-sheet-and-dispatch [:navigate-to :multiaccounts])
:icon :i/key
:title (i18n/label :t/show-existing-keys)}]])]]))
(def bottom-sheet
{:content bottom-sheet-view})

View File

@ -11,7 +11,7 @@
[status-im.ui.components.qr-code-viewer.views :as qr-code-viewer]
[status-im.ui.components.react :as react]
[status-im.ui.screens.profile.user.styles :as styles]
[status-im.utils.config :as config]
[status-im2.setup.config :as config]
[status-im.utils.gfycat.core :as gfy]
[status-im.utils.universal-links.utils :as universal-links]
[status-im.ui.components.profile-header.view :as profile-header]
@ -67,7 +67,8 @@
@(re-frame/subscribe [:multiaccount])
active-contacts-count @(re-frame/subscribe [:contacts/active-count])
chain @(re-frame/subscribe [:chain-keyword])
registrar (stateofus/get-cached-registrar chain)]
registrar (stateofus/get-cached-registrar chain)
local-pairing-mode-enabled? config/local-pairing-mode-enabled?]
[:<>
[visibility-status/visibility-status-button
visibility-status/calculate-button-height-and-dispatch-popover]
@ -160,6 +161,13 @@
:accessibility-label :about-button
:chevron true
:on-press #(re-frame/dispatch [:navigate-to :about-app])}]
(when local-pairing-mode-enabled?
[quo/list-item
{:icon :i/mobile
:title (i18n/label :t/syncing)
:accessibility-label :syncing
:chevron true
:on-press #(re-frame/dispatch [:navigate-to :settings-syncing])}])
[react/view {:padding-vertical 24}
[quo/list-item
{:icon :main-icons/log-out

View File

@ -0,0 +1,30 @@
(ns status-im2.contexts.syncing.events
(:require [utils.re-frame :as rf]
[status-im.utils.security :as security]
[taoensso.timbre :as log]
[status-im.native-module.core :as status]
[status-im2.contexts.syncing.sheets.enter-password.view :as sheet]))
(rf/defn initiate-local-pairing-with-connection-string
{:events [:syncing/input-connection-string-for-bootstrapping]}
[{:keys [db]} {:keys [data]}]
(let [config-map (.stringify js/JSON (clj->js {:keyUID "" :keystorePath "" :password ""}))
connection-string data]
(status/input-connection-string-for-bootstrapping
connection-string
config-map
#(log/info "this is response from initiate-local-pairing-with-connection-string " %))))
(rf/defn preparations-for-connection-string
{:events [:syncing/get-connection-string-for-bootstrapping-another-device]}
[{:keys [db]} entered-password]
(let [sha3-pwd (status/sha3 (str (security/safe-unmask-data entered-password)))
key-uid (get-in db [:multiaccount :key-uid])
config-map (.stringify js/JSON (clj->js {:keyUID key-uid :keystorePath "" :password sha3-pwd}))]
(status/get-connection-string-for-bootstrapping-another-device
config-map
(fn [connection-string]
(rf/dispatch [:bottom-sheet/show-sheet
{:show-handle? false
:content (fn []
[sheet/qr-code-view-with-connection-string connection-string])}])))))

View File

@ -0,0 +1,60 @@
(ns status-im2.contexts.syncing.sheets.enter-password.view
(:require [react-native.core :as rn]
[utils.re-frame :as rf]
[quo2.core :as quo]
[quo.core :as quo-old]
[i18n.i18n :as i18n]
[clojure.string :as string]
[quo2.foundations.colors :as colors]
[status-im.constants :as constants]
[status-im.ui.components.qr-code-viewer.views :as qr-code-viewer]))
(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)]]])]))
(defn sheet []
(let [entered-password (atom "")]
[:<>
[rn/view {:margin 20}
[rn/view
[quo/text {:accessibility-label :sync-code-generated
:weight :bold
:size :heading-1
:style {:color colors/neutral-100
:margin 20}}
(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
{:placeholder (i18n/label :t/enter-your-password)
:auto-focus true
:accessibility-label :password-input
:show-cancel false
:on-change-text #(reset! entered-password %)
:secure-text-entry true}]]]
[rn/view {:padding-horizontal 18
:margin-top 20}
[quo/button
{:on-press #(rf/dispatch [:syncing/get-connection-string-for-bootstrapping-another-device @entered-password])}
(i18n/label :t/generate-scan-sync-code)]]]]]))

View File

@ -0,0 +1,34 @@
(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

@ -0,0 +1,54 @@
(ns status-im2.contexts.syncing.sheets.sync-device-notice.view
(:require
[react-native.core :as rn]
[status-im2.contexts.syncing.sheets.sync-device-notice.styles :as styles]
[status-im2.contexts.syncing.sheets.enter-password.view :as enter-password]
[utils.re-frame :as rf]
[quo2.core :as quo]
[i18n.i18n :as i18n]))
(defn sheet []
[:<>
[rn/view {:style styles/sync-devices-header}
[rn/image {:source (js/require "../resources/images/ui/sync-new-device-cover-background.png")
: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 [:bottom-sheet/show-sheet
{:show-handle? false
:content (fn []
[enter-password/sheet])}])}
(i18n/label :t/setup-syncing)]]])

View File

@ -0,0 +1,24 @@
(ns status-im2.contexts.syncing.styles
(:require [quo2.foundations.colors :as colors]))
(def container-main
{:margin 16})
(def devices-container
{:border-color colors/neutral-20
:border-radius 16
:border-width 1
:margin-top 12})
(def device-row
{:flex-direction :row
:padding-vertical 10
:padding-left 10})
(def device-column
{:flex-direction :column
:margin-left 10
:align-self :center})
(def sync-device-container
{:padding 10})

View File

@ -0,0 +1,44 @@
(ns status-im2.contexts.syncing.view
(:require [react-native.core :as rn]
[quo2.core :as quo]
[i18n.i18n :as i18n]
[utils.re-frame :as rf]
[quo2.foundations.colors :as colors]
[status-im2.contexts.syncing.styles :as styles]
[status-im2.contexts.syncing.sheets.sync-device-notice.view :as sync-device-notice]))
(defn render-device [device-name device-status]
[:<>
[rn/view {:style styles/device-row}
[quo/icon-avatar {:size :medium
:icon :i/placeholder
:color :primary
:style {:margin-vertical 10}}]
[rn/view {:style styles/device-column}
[quo/text {:accessibility-label :device-name
:weight :medium
:size :paragraph-1
:style {:color colors/neutral-100}} device-name]
[quo/text {:accessibility-label :device-status
:weight :regular
:size :paragraph-2
:style {:color colors/neutral-50}} device-status]]]])
(defn views []
[rn/view {:style styles/container-main}
[quo/text {:accessibility-label :synced-devices-title
:weight :medium
:size :paragraph-2
:style {:color colors/neutral-50}} (i18n/label :t/synced-devices)]
[rn/view {:style styles/devices-container}
[render-device "iPhone 11" (i18n/label :t/this-device)] ;; note : the device name is hardcoded for now
[rn/view {:style styles/sync-device-container}
[quo/button {:label :primary
:size 40
:before :i/placeholder
:on-press #(rf/dispatch [:bottom-sheet/show-sheet
{:show-handle? false
:content (fn []
[sync-device-notice/sheet])}])}
(i18n/label :t/sync-new-device)]]]])

View File

@ -5,7 +5,8 @@
[status-im2.contexts.shell.view :as shell]
[status-im2.contexts.quo-preview.main :as quo.preview]
[status-im2.contexts.chat.messages.view :as chat]
[status-im2.contexts.syncing.view :as settings-syncing]
[i18n.i18n :as i18n]
;; TODO remove when not used anymore
[status-im.ui.screens.screens :as old-screens]))
@ -29,7 +30,12 @@
{:name :community-overview
:options {:topBar {:visible false}}
:component communities.overview/overview}]
:component communities.overview/overview}
{:name :settings-syncing
:insets {:bottom true}
:options {:topBar {:title {:text (i18n/label :t/syncing)}}}
:component settings-syncing/views}]
(when config/quo-preview-enabled?
quo.preview/screens)

View File

@ -42,6 +42,7 @@
(def two-minutes-syncing? (enabled? (get-config :TWO_MINUTES_SYNCING "0")))
(def swap-enabled? (enabled? (get-config :SWAP_ENABLED "0")))
(def stickers-test-enabled? (enabled? (get-config :STICKERS_TEST_ENABLED "0")))
(def local-pairing-mode-enabled? (enabled? (get-config :LOCAL_PAIRING_ENABLED "1")))
;; CONFIG VALUES
(def log-level (string/upper-case (get-config :LOG_LEVEL "")))
@ -138,3 +139,7 @@
;;TODO for development only should be removed in status 2.0
(def new-ui-enabled? true)
;; TODO: Remove this (highly) temporary flag once the new Activity Center is
;; usable enough to replace the old one **in the new UI**.
(def new-activity-center-enabled? true)

View File

@ -10,6 +10,7 @@
[i18n.i18n :as i18n]
status-im2.setup.events
status-im2.contexts.syncing.events
status-im2.subs.root
status-im2.navigation.core

View File

@ -1894,5 +1894,20 @@
"unmute-group": "Unmute group",
"remove-user-from-group": "Remove {{username}} from the group",
"edit-name-image": "Edit name and image",
"owner": "Owner"
"owner": "Owner",
"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-generated": "Sync code generated",
"generate-scan-sync-code": "Generate Scan Sync Code",
"try-your-luck-again": "Try your luck again!",
"instruction-after-qr-generated": "On your other device, navigate to the Syncing screen and select “Scan sync”",
"show-existing-keys": "Show Existing Keys",
"scan-sync-code": "Scan Sync Code"
}