diff --git a/.env b/.env index f031dd9d65..53c6aeaf6f 100644 --- a/.env +++ b/.env @@ -32,3 +32,4 @@ COMMANDS_ENABLED=1 TWO_MINUTES_SYNCING=1 SWAP_ENABLED=1 STICKERS_TEST_ENABLED=1 +LOCAL_PAIRING_ENABLED=1 diff --git a/.env.e2e b/.env.e2e index 2b021818f4..6a0fa064dd 100644 --- a/.env.e2e +++ b/.env.e2e @@ -32,3 +32,4 @@ COMMUNITIES_MANAGEMENT_ENABLED=1 DELETE_MESSAGE_ENABLED=1 TWO_MINUTES_SYNCING=1 STICKERS_TEST_ENABLED=1 +LOCAL_PAIRING_ENABLED=1 diff --git a/.env.jenkins b/.env.jenkins index 394d66e07c..ec06cda656 100644 --- a/.env.jenkins +++ b/.env.jenkins @@ -34,3 +34,4 @@ DELETE_MESSAGE_ENABLED=1 TWO_MINUTES_SYNCING=1 ENABLE_QUO_PREVIEW=1 STICKERS_TEST_ENABLED=1 +LOCAL_PAIRING_ENABLED=1 diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java index db44555429..a4baf2d207 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java @@ -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"); diff --git a/modules/react-native-status/ios/RCTStatus/RCTStatus.m b/modules/react-native-status/ios/RCTStatus/RCTStatus.m index 017b4967de..1db0e5af7b 100644 --- a/modules/react-native-status/ios/RCTStatus/RCTStatus.m +++ b/modules/react-native-status/ios/RCTStatus/RCTStatus.m @@ -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]); } diff --git a/resources/images/ui/sync-new-device-cover-background@2x.png b/resources/images/ui/sync-new-device-cover-background@2x.png new file mode 100644 index 0000000000..9b274e3cfb Binary files /dev/null and b/resources/images/ui/sync-new-device-cover-background@2x.png differ diff --git a/resources/images/ui/sync-new-device-cover-background@3x.png b/resources/images/ui/sync-new-device-cover-background@3x.png new file mode 100644 index 0000000000..aa4be40064 Binary files /dev/null and b/resources/images/ui/sync-new-device-cover-background@3x.png differ diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index eaac0b62be..20a5723d42 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -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") + diff --git a/src/status_im/native_module/core.cljs b/src/status_im/native_module/core.cljs index 3507012ede..0a2db99c23 100644 --- a/src/status_im/native_module/core.cljs +++ b/src/status_im/native_module/core.cljs @@ -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] diff --git a/src/status_im/qr_scanner/core.cljs b/src/status_im/qr_scanner/core.cljs index 790a78beb2..80cf90329c 100644 --- a/src/status_im/qr_scanner/core.cljs +++ b/src/status_im/qr_scanner/core.cljs @@ -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]} diff --git a/src/status_im/router/core.cljs b/src/status_im/router/core.cljs index ea8ad9f09a..e584b3b497 100644 --- a/src/status_im/router/core.cljs +++ b/src/status_im/router/core.cljs @@ -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})))) diff --git a/src/status_im/signals/core.cljs b/src/status_im/signals/core.cljs index 9ff9ad29e7..0b773dd56d 100644 --- a/src/status_im/signals/core.cljs +++ b/src/status_im/signals/core.cljs @@ -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")))) diff --git a/src/status_im/ui/screens/advanced_settings/views.cljs b/src/status_im/ui/screens/advanced_settings/views.cljs index c595ad2ec9..f8f54f40ab 100644 --- a/src/status_im/ui/screens/advanced_settings/views.cljs +++ b/src/status_im/ui/screens/advanced_settings/views.cljs @@ -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])) diff --git a/src/status_im/ui/screens/multiaccounts/recover/views.cljs b/src/status_im/ui/screens/multiaccounts/recover/views.cljs index 1589c7d11a..1735deb6a3 100644 --- a/src/status_im/ui/screens/multiaccounts/recover/views.cljs +++ b/src/status_im/ui/screens/multiaccounts/recover/views.cljs @@ -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}) diff --git a/src/status_im/ui/screens/profile/user/views.cljs b/src/status_im/ui/screens/profile/user/views.cljs index 24fa0034a6..b31f83e5f2 100644 --- a/src/status_im/ui/screens/profile/user/views.cljs +++ b/src/status_im/ui/screens/profile/user/views.cljs @@ -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 diff --git a/src/status_im2/contexts/syncing/events.cljs b/src/status_im2/contexts/syncing/events.cljs new file mode 100644 index 0000000000..e9a7735436 --- /dev/null +++ b/src/status_im2/contexts/syncing/events.cljs @@ -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])}]))))) diff --git a/src/status_im2/contexts/syncing/sheets/enter_password/view.cljs b/src/status_im2/contexts/syncing/sheets/enter_password/view.cljs new file mode 100644 index 0000000000..eaead6ebed --- /dev/null +++ b/src/status_im2/contexts/syncing/sheets/enter_password/view.cljs @@ -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)]]]]])) diff --git a/src/status_im2/contexts/syncing/sheets/sync_device_notice/styles.cljs b/src/status_im2/contexts/syncing/sheets/sync_device_notice/styles.cljs new file mode 100644 index 0000000000..e283649157 --- /dev/null +++ b/src/status_im2/contexts/syncing/sheets/sync_device_notice/styles.cljs @@ -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}) diff --git a/src/status_im2/contexts/syncing/sheets/sync_device_notice/view.cljs b/src/status_im2/contexts/syncing/sheets/sync_device_notice/view.cljs new file mode 100644 index 0000000000..68f7e3f51d --- /dev/null +++ b/src/status_im2/contexts/syncing/sheets/sync_device_notice/view.cljs @@ -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)]]]) diff --git a/src/status_im2/contexts/syncing/styles.cljs b/src/status_im2/contexts/syncing/styles.cljs new file mode 100644 index 0000000000..35b9068c3f --- /dev/null +++ b/src/status_im2/contexts/syncing/styles.cljs @@ -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}) diff --git a/src/status_im2/contexts/syncing/view.cljs b/src/status_im2/contexts/syncing/view.cljs new file mode 100644 index 0000000000..1e6ddcba04 --- /dev/null +++ b/src/status_im2/contexts/syncing/view.cljs @@ -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)]]]]) diff --git a/src/status_im2/navigation/screens.cljs b/src/status_im2/navigation/screens.cljs index c1d1415b74..bac7127bbf 100644 --- a/src/status_im2/navigation/screens.cljs +++ b/src/status_im2/navigation/screens.cljs @@ -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) diff --git a/src/status_im2/setup/config.cljs b/src/status_im2/setup/config.cljs index 8d2f21042a..cccad4ff2b 100644 --- a/src/status_im2/setup/config.cljs +++ b/src/status_im2/setup/config.cljs @@ -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) diff --git a/src/status_im2/setup/core.cljs b/src/status_im2/setup/core.cljs index 55dd87fbf5..1c88f35447 100644 --- a/src/status_im2/setup/core.cljs +++ b/src/status_im2/setup/core.cljs @@ -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 diff --git a/translations/en.json b/translations/en.json index 9e733311e2..0a794ed9ff 100644 --- a/translations/en.json +++ b/translations/en.json @@ -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" }