Compare commits

..
Author SHA1 Message Date
Omar Basem 6221b42f2a fix: android bottom padding 2023-05-16 10:44:03 +04:00
Churikova Tetiana 7ef466078a e2e: image test 2023-05-15 19:08:03 +02:00
Jamie Caprani 4b2d62ee1f chore: update ui for syncing page for onboarding and in profile (#15623) 2023-05-15 10:07:13 -07:00
flexsurfer 05073e7453 fix onboarding transition background color (#15900) 2023-05-15 16:28:08 +02:00
frank fa11c232cc update status-go-version.json (#15894) 2023-05-15 20:41:26 +08:00
Brian Sztamfater 3038ceeb82 fix: audio issue when sending app to background on Android and other fixes
Signed-off-by: Brian Sztamfater <brian@status.im>
2023-05-15 09:19:06 -03:00
frank d2e38772b9 use debounce-and-dispatch to improve mention performance (#15887)
* use debounce-and-dispatch to improve mention performance

* increase time to 400ms

* decrease time to 300ms

* update status-go-version.json

* update status-go-version.json
2023-05-15 15:51:29 +08:00
Churikova Tetiana 409c0a9e60 e2e: small fixes 2023-05-12 14:25:32 +02:00
erikseppanen 4b98f0e8de Add 'How to scan' syncing screens (#15710) 2023-05-11 20:58:57 -04:00
Omar Basem 264b7ffab9 Fix mentions animation when disappearing (#15879)
* fix: suggestions list animation
2023-05-11 22:33:25 +04:00
Alexander c71224e73e Double-tap for communities & fix for 'Recent' tab not being shown as selected if message icon is double tapped (#15844)
* Updates

* Small namespace fix
2023-05-11 18:17:50 +02:00
Parvesh Monu 3c278ed06c fix window height sub leak (#15878) 2023-05-11 19:57:37 +05:30
Jamie Caprani d714dc1590 chore: restore label on im new to status page (#15876) 2023-05-11 07:09:03 -07:00
Ajay Sivan d2840885dd fix: app locked screen accent color (#15854) 2023-05-11 05:31:41 -07:00
Omar Basem 87247af86b Composer polishing (#15875)
* composer polishing
2023-05-11 15:35:42 +04:00
Mohamed Javid 159ad02e7a [Fix] Tapable area of user avatar in chat (#15840) 2023-05-11 14:33:35 +05:30
61 changed files with 1108 additions and 725 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 KiB

@@ -173,6 +173,7 @@
reached-max-duration? (atom false)
touch-timestamp (atom nil)
disabled? (atom false)
app-state-listener (atom nil)
rec-options
(merge
audio/default-recorder-options
@@ -512,7 +513,13 @@
(on-init reset-recorder))
(when audio-file
(let [filename (last (string/split audio-file "/"))]
(reload-player filename)))))
(reload-player filename)))
(reset! app-state-listener
(.addEventListener rn/app-state
"change"
#(when (= % "background")
(reset! playing-audio? false))))
#(.remove @app-state-listener)))
[rn/view
{:style style/bar-container
:pointer-events :box-none}
+5
View File
@@ -130,6 +130,11 @@
(when (and player (.-canPlay ^js player))
(.-currentTime ^js player)))
(defn set-player-wake-lock
[player wake-lock?]
(when player
(set! (.-wakeLock player) wake-lock?)))
(defn toggle-playpause-player
[player on-play on-pause on-error]
(when (and player (.-canPlay ^js player))
+9 -10
View File
@@ -70,13 +70,12 @@
[{:keys [db] :as cofx} text-input-ref {:keys [primary-name searched-text match public-key] :as user}]
(let [chat-id (:current-chat-id db)
text (get-in db [:chat/inputs chat-id :input-text])
method "wakuext_chatMentionNewInputTextWithMention"
params [chat-id text primary-name]]
method "wakuext_chatMentionSelectMention"
params [chat-id text primary-name public-key]]
{:json-rpc/call [{:method method
:params params
:on-success #(rf/dispatch [:mention/on-new-input-text-with-mentions-success %
primary-name text-input-ref match searched-text
public-key])
:on-success #(rf/dispatch [:mention/on-select-mention-success %
primary-name match searched-text public-key])
:on-error #(rf/dispatch [:mention/on-error
{:method method
:params params} %])}]}))
@@ -250,21 +249,21 @@
[{{:keys [current-chat-id] :as db} :db :as cofx}]
(let [{:keys [input-text metadata]} (get-in db [:chat/inputs current-chat-id])
editing-message (:editing-message metadata)
method "wakuext_chatMentionCheckMentions"
method "wakuext_chatMentionReplaceWithPublicKey"
params [current-chat-id input-text]]
{:json-rpc/call [{:method method
:params params
:on-error #(rf/dispatch [:mention/on-error {:method method :params params} %])
:on-success #(rf/dispatch [:mention/on-check-mentions-success
:on-success #(rf/dispatch [:mention/on-replace-with-public-key-success
current-chat-id
editing-message
input-text
%])}]}))
(rf/defn on-check-mentions-success
{:events [:mention/on-check-mentions-success]}
(rf/defn on-replace-with-public-key-success
{:events [:mention/on-replace-with-public-key-success]}
[{:keys [db] :as cofx} current-chat-id editing-message input-text new-text]
(log/debug "[mentions] on-check-mentions-success"
(log/debug "[mentions] on-replace-with-public-key-success"
{:chat-id current-chat-id
:editing-message editing-message
:input-text input-text
+11 -76
View File
@@ -1,11 +1,7 @@
(ns status-im.chat.models.mentions
(:require [clojure.set :as set]
[quo.react :as react]
[quo.react-native :as rn]
[re-frame.core :as re-frame]
[utils.re-frame :as rf]
[taoensso.timbre :as log]
[native-module.core :as native-module]))
[taoensso.timbre :as log]))
(defn- transfer-input-segments
[segments]
@@ -126,47 +122,21 @@
(assoc-in [:chats/mentions chat-id :mentions] state)
(assoc-in [:chat/inputs-with-mentions chat-id] input-segments))}))
(rf/defn recheck-at-idxs
[{:keys [db]} public-key]
(let [chat-id (:current-chat-id db)
text (get-in db [:chat/inputs chat-id :input-text])
params [chat-id text public-key]
method "wakuext_chatMentionRecheckAtIdxs"]
{:json-rpc/call [{:method method
:params params
:on-success #(rf/dispatch [:mention/on-recheck-at-idxs-success %])
:on-error #(rf/dispatch [:mention/on-error
{:method method
:params params} %])}]}))
(rf/defn on-recheck-at-idxs-success
{:events [:mention/on-recheck-at-idxs-success]}
[{:keys [db]} result]
(log/debug "[mentions] on-recheck-at-idxs-success" {:result result})
(let [{:keys [input-segments state chat-id]} (transfer-mention-result result)]
{:db (-> db
(assoc-in [:chats/mentions chat-id :mentions] state)
(assoc-in [:chat/inputs-with-mentions chat-id] input-segments))}))
(rf/defn reset-text-input-cursor
[_ ref cursor]
{::reset-text-input-cursor [ref cursor]})
(rf/defn on-new-input-text-with-mentions-success
{:events [:mention/on-new-input-text-with-mentions-success]}
[{:keys [db] :as cofx} result primary-name text-input-ref match searched-text public-key]
(log/debug "[mentions] on-new-input-text-with-mentions-success"
(rf/defn on-select-mention-success
{:events [:mention/on-select-mention-success]}
[{:keys [db] :as cofx} result primary-name match searched-text public-key]
(log/debug "[mentions] on-select-mention-success"
{:result result
:primary-name primary-name
:match match
:searched-text searched-text
:public-key public-key})
(let [{:keys [new-text chat-id]} (transfer-mention-result result)]
(rf/merge
cofx
{:db (assoc-in db [:chats/mention-suggestions chat-id] nil)
:dispatch [:chat.ui/set-chat-input-text new-text chat-id]}
(recheck-at-idxs public-key))))
(let [{:keys [new-text chat-id state input-segments]} (transfer-mention-result result)]
{:db (-> db
(assoc-in [:chats/mentions chat-id :mentions] state)
(assoc-in [:chat/inputs-with-mentions chat-id] input-segments)
(assoc-in [:chats/mention-suggestions chat-id] nil))
:dispatch [:chat.ui/set-chat-input-text new-text chat-id]}))
(rf/defn clear-suggestions
[{:keys [db]}]
@@ -189,38 +159,3 @@
:on-error #(log/error "Error while calling wakuext_chatMentionClearMentions"
{:error %})}]}
(clear-suggestions))))
(rf/defn check-selection
{:events [:mention/on-selection-change]}
[{:keys [db]}
{:keys [start end]}]
(let [chat-id (:current-chat-id db)
text (get-in db [:chat/inputs chat-id :input-text])
params [chat-id text start end]
method "wakuext_chatMentionHandleSelectionChange"]
(when text
(log/debug "[mentions] check-selection" {:params params})
{:json-rpc/call [{:method method
:params params
:on-success #(rf/dispatch [:mention/on-handle-selection-change-success %])
:on-error #(rf/dispatch [:mention/on-error
{:method method
:params params} %])}]})))
(rf/defn on-check-selection-success
{:events [:mention/on-handle-selection-change-success]}
[{:keys [db]} result]
(log/debug "[mentions] on-check-selection-success" {:result result})
(let [{:keys [state chat-id mentionable-users input-segments]} (transfer-mention-result result)]
{:db (-> db
(assoc-in [:chats/mention-suggestions chat-id] mentionable-users)
(assoc-in [:chats/mentions chat-id :mentions] state)
(assoc-in [:chat/inputs-with-mentions chat-id] input-segments))}))
(re-frame/reg-fx
::reset-text-input-cursor
(fn [[ref cursor]]
(when ref
(native-module/reset-keyboard-input
(rn/find-node-handle (react/current-ref ref))
cursor))))
+2 -1
View File
@@ -62,7 +62,8 @@
[status-im2.common.theme.core :as theme]
[react-native.core :as rn]
[react-native.platform :as platform]
status-im2.contexts.chat.home.events))
status-im2.contexts.chat.home.events
status-im2.contexts.communities.home.events))
(re-frame/reg-fx
:dismiss-keyboard
+13 -43
View File
@@ -1,21 +1,19 @@
(ns status-im.multiaccounts.core
(:require [clojure.string :as string]
[native-module.core :as native-module]
[quo.platform :as platform]
[quo2.foundations.colors :as colors]
[quo2.theme]
[re-frame.core :as re-frame]
[status-im.bottom-sheet.events :as bottom-sheet]
[status-im.contact.db :as contact.db]
[status-im.multiaccounts.update.core :as multiaccounts.update]
[status-im.utils.gfycat.core :as gfycat]
[status-im2.common.theme.core :as theme]
[native-module.core :as native-module]
[utils.re-frame :as rf]
[quo2.foundations.colors :as colors]
[status-im2.constants :as constants]
[status-im2.contexts.shell.animation :as shell.animation]
[status-im.utils.gfycat.core :as gfycat]
[status-im2.setup.hot-reload :as hot-reload]
[status-im2.common.theme.core :as theme]
[taoensso.timbre :as log]
[utils.image-server :as image-server]
[utils.re-frame :as rf]))
[status-im2.contexts.shell.animation :as shell.animation]
[status-im.contact.db :as contact.db]))
;; validate that the given mnemonic was generated from Status Dictionary
(re-frame/reg-fx
@@ -136,13 +134,13 @@
[:dark :light colors/neutral-100]
[:light :dark colors/white])]
(theme/set-theme theme)
(rf/dispatch [:change-shell-status-bar-style
(re-frame/dispatch [:change-shell-status-bar-style
(if (shell.animation/home-stack-open?) status-bar-theme :light)])
(when reload-ui?
(rf/dispatch [:dissmiss-all-overlays])
(hot-reload/reload)
(when-not (= view-id :shell-stack)
(rf/dispatch [:change-shell-nav-bar-color nav-bar-color]))))))
(re-frame/dispatch [:change-shell-nav-bar-color nav-bar-color]))))))
(rf/defn switch-appearance
{:events [:multiaccounts.ui/appearance-switched]}
@@ -179,25 +177,6 @@
(string/replace-first path #"file://" "")
(log/warn "[native-module] Empty path was provided")))
(defn replace-multiaccount-image-uri
[multiaccount port]
(let [public-key (:public-key multiaccount)
theme (theme/get-theme)
images (:images multiaccount)
images (reduce (fn [acc current]
(let [key-uid (:keyUid current)
image-name (:type current)
uri (image-server/get-account-image-uri port
public-key
image-name
key-uid
theme)]
(conj acc (assoc current :uri uri))))
[]
images)]
(assoc multiaccount :images images)))
(rf/defn save-profile-picture
{:events [::save-profile-picture]}
[cofx path ax ay bx by]
@@ -206,16 +185,7 @@
{:json-rpc/call [{:method "multiaccounts_storeIdentityImage"
:params [key-uid (clean-path path) ax ay bx by]
;; NOTE: In case of an error we can show a toast error
:on-success (fn [pics]
(rf/dispatch [::update-local-picture pics])
;; take the same image, get the new image with ring from media server
;; save it to account db
(let [account (get-in cofx [:db :multiaccount])
port (get-in cofx [:db :mediaserver/port])
photo-url (-> account
(replace-multiaccount-image-uri port)
displayed-photo)]
(rf/dispatch [::save-profile-picture-from-url photo-url])))}]}
:on-success #(re-frame/dispatch [::update-local-picture %])}]}
(bottom-sheet/hide-bottom-sheet-old))))
(rf/defn save-profile-picture-from-url
@@ -226,11 +196,11 @@
{:json-rpc/call [{:method "multiaccounts_storeIdentityImageFromURL"
:params [key-uid url]
:on-error #(log/error "::save-profile-picture-from-url error" %)
:on-success #(rf/dispatch [::update-local-picture %])}]}
:on-success #(re-frame/dispatch [::update-local-picture %])}]}
(bottom-sheet/hide-bottom-sheet-old))))
(comment
(rf/dispatch
(re-frame/dispatch
[::save-profile-picture-from-url
"https://lh3.googleusercontent.com/XuKjNm3HydsaxbPkbpGs9YyCKhn5QQk5oDC8XF2jzmPyYXeZofxFtfUDZuQ3EVmacS_BlBKzbX2ypm37YNX3n1fDJA3WndeFcPsp7Z0=w600"]))
@@ -252,7 +222,7 @@
(let [key-uid (get-in cofx [:db :multiaccount :key-uid])]
{:json-rpc/call [{:method "multiaccounts_getIdentityImages"
:params [key-uid]
:on-success #(rf/dispatch [::update-local-picture %])}]}))
:on-success #(re-frame/dispatch [::update-local-picture %])}]}))
(rf/defn store-profile-picture
{:events [::update-local-picture]}
+2 -1
View File
@@ -739,7 +739,8 @@
(merge
{:db (update db :keycard dissoc :application-info)}
(when keycard-multiaccount? {:navigate-to :keycard-login-pin}))
(open-login (select-keys multiaccount [:key-uid :name :public-key :images])))))
(open-login (select-keys multiaccount
[:key-uid :name :public-key :images :customization-color])))))
(rf/defn hide-keycard-banner
{:events [:hide-keycard-banner]}
+14 -9
View File
@@ -1,15 +1,20 @@
(ns status-im2.common.resources)
(def ui
{:add-new-contact (js/require "../resources/images/ui2/add-contact.png")
:lifestyle (js/require "../resources/images/ui2/lifestyle.png")
:music (js/require "../resources/images/ui2/music.png")
:podcasts (js/require "../resources/images/ui2/podcasts.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")
:qr-code (js/require "../resources/images/ui2/qr-code.png")
{:add-new-contact (js/require "../resources/images/ui2/add-contact.png")
:desktop-how-to-pair-sign-in (js/require "../resources/images/ui2/desktop-how-to-pair-sign-in.png")
:desktop-how-to-pair-logged-in (js/require
"../resources/images/ui2/desktop-how-to-pair-logged-in.png")
:mobile-how-to-pair-sign-in (js/require "../resources/images/ui2/mobile-how-to-pair-sign-in.png")
:mobile-how-to-pair-logged-in (js/require "../resources/images/ui2/mobile-how-to-pair-logged-in.png")
:lifestyle (js/require "../resources/images/ui2/lifestyle.png")
:music (js/require "../resources/images/ui2/music.png")
:podcasts (js/require "../resources/images/ui2/podcasts.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")
:qr-code (js/require "../resources/images/ui2/qr-code.png")
})
(def mock-images
@@ -31,4 +31,4 @@
:left -20
:right -20
:bottom 0
:height constants/composer-default-height})
:height (- constants/composer-default-height constants/bottom-padding)})
@@ -58,9 +58,9 @@
:i/arrow-up]])
(defn send-button
[state animations window-height images?]
[{:keys [text-value] :as state} animations window-height images?]
(let [btn-opacity (reanimated/use-shared-value 0)
z-index (reagent/atom 0)]
z-index (reagent/atom (if (and (empty? @text-value) (not images?)) 0 1))]
[:f> f-send-button state animations window-height images? btn-opacity z-index]))
(defn audio-button
@@ -149,11 +149,12 @@
(defn image-button
[props animations insets]
[quo/button
{:on-press #(open-photo-selector props animations insets)
:icon true
:type :outline
:size 32
:style {:margin-right 12}}
{:on-press #(open-photo-selector props animations insets)
:accessibility-label :open-images-button
:icon true
:type :outline
:size 32
:style {:margin-right 12}}
:i/image])
(defn reaction-button
@@ -176,7 +177,7 @@
:i/format])
(defn view
[props state animations window-height insets edit? images?]
[props state animations window-height insets {:keys [edit images]}]
[rn/view {:style style/actions-container}
[rn/view
{:style {:flex-direction :row
@@ -185,6 +186,6 @@
[image-button props animations insets]
[reaction-button]
[format-button]]
[:f> send-button state animations window-height images?]
(when (and (not edit?) (not images?))
[:f> send-button state animations window-height images]
(when (and (not edit) (not images))
[audio-button props state animations])])
@@ -9,7 +9,9 @@
(def ^:const actions-container-height 56)
(def ^:const composer-default-height (+ bar-container-height input-height actions-container-height))
(def ^:const bottom-padding (if platform/android? 15 0))
(def ^:const composer-default-height (+ bar-container-height input-height actions-container-height bottom-padding))
(def ^:const line-height (:line-height typography/paragraph-1))
@@ -8,7 +8,7 @@
[status-im2.contexts.chat.composer.keyboard :as kb]
[utils.number :as utils.number]
[oops.core :as oops]
[utils.re-frame :as rf]))
[utils.debounce :as debounce]))
(defn reenter-screen-effect
[{:keys [text-value saved-cursor-position maximized?]}
@@ -91,8 +91,7 @@
(.remove ^js @keyboard-frame-listener))
(defn initialize
[props state animations {:keys [max-height] :as dimensions} chat-input images? reply?
audio]
[props state animations {:keys [max-height] :as dimensions} {:keys [chat-input images reply audio]}]
(rn/use-effect
(fn []
(maximized-effect state animations dimensions chat-input)
@@ -100,9 +99,9 @@
(layout-effect state)
(kb-default-height-effect state)
(background-effect state animations dimensions chat-input)
(images-effect props animations images?)
(images-effect props animations images)
(audio-effect state animations audio)
(empty-effect state animations images? reply? audio)
(empty-effect state animations images reply audio)
(kb/add-kb-listeners props state animations dimensions)
#(component-will-unmount props))
[max-height]))
@@ -110,7 +109,7 @@
(defn edit
[{:keys [input-ref]}
{:keys [text-value saved-cursor-position]}
edit]
{:keys [edit]}]
(rn/use-effect
(fn []
(let [edit-text (get-in edit [:content :text])]
@@ -124,7 +123,7 @@
(defn reply
[{:keys [input-ref]}
{:keys [container-opacity]}
reply]
{:keys [reply]}]
(rn/use-effect
(fn []
(when reply
@@ -134,7 +133,7 @@
[(:message-id reply)]))
(defn edit-mentions
[{:keys [input-ref]} {:keys [text-value cursor-position]} input-with-mentions]
[{:keys [input-ref]} {:keys [text-value cursor-position]} {:keys [input-with-mentions]}]
(rn/use-effect (fn []
(let [input-text (reduce (fn [acc item]
(str acc (second item)))
@@ -153,14 +152,14 @@
(defn update-input-mention
[{:keys [input-ref]}
{:keys [text-value]}
input-text]
{:keys [input-text]}]
(rn/use-effect
(fn []
(when (and input-text (not= @text-value input-text))
(when @input-ref
(.setNativeProps ^js @input-ref (clj->js {:text input-text})))
(reset! text-value input-text)
(rf/dispatch [:mention/on-change-text input-text])))
(debounce/debounce-and-dispatch [:mention/on-change-text input-text] 300)))
[input-text]))
(defn did-mount
@@ -8,7 +8,8 @@
[status-im2.contexts.chat.composer.keyboard :as kb]
[status-im2.contexts.chat.composer.utils :as utils]
[status-im2.contexts.chat.composer.selection :as selection]
[utils.re-frame :as rf]))
[utils.re-frame :as rf]
[utils.debounce :as debounce]))
(defn focus
[{:keys [input-ref] :as props}
@@ -38,8 +39,7 @@
maximized?]}
{:keys [height saved-height last-height gradient-opacity container-opacity opacity background-y]}
{:keys [content-height max-height window-height]}
images
reply]
{:keys [images reply]}]
(let [lines (utils/calc-lines (- @content-height constants/extra-content-offset))
min-height (utils/get-min-height lines)
reopen-height (utils/calc-reopen-height text-value min-height content-height saved-height)]
@@ -119,7 +119,7 @@
(@record-reset-fn)
(reset! recording? false))
(rf/dispatch [:chat.ui/set-chat-input-text text])
(rf/dispatch [:mention/on-change-text text]))
(debounce/debounce-and-dispatch [:mention/on-change-text text] 300))
(defn selection-change
[event
@@ -30,16 +30,17 @@
user])
(defn- f-view
[suggestions-atom props state animations max-height cursor-pos]
[suggestions-atom props state animations max-height cursor-pos images reply edit]
(let [suggestions (rf/sub [:chat/mention-suggestions])
opacity (reanimated/use-shared-value (if (seq suggestions) 1 0))
size (count suggestions)
data {:keyboard-height @(:kb-height state)
:insets (safe-area/get-insets)
:curr-height (reanimated/get-shared-value (:height animations))
:window-height (rf/sub [:dimensions/window-height])
:reply (rf/sub [:chats/reply-message])
:edit (rf/sub [:chats/edit-message])}
:window-height (:height (rn/get-window))
:images images
:reply reply
:edit edit}
mentions-pos (utils/calc-suggestions-position cursor-pos max-height size state data)]
(rn/use-effect
(fn []
@@ -60,6 +61,6 @@
:accessibility-label :mentions-list}]]))
(defn view
[props state animations max-height cursor-pos]
[props state animations max-height cursor-pos images reply edit]
(let [suggestions-atom (reagent/atom {})]
[:f> f-view suggestions-atom props state animations max-height cursor-pos]))
[:f> f-view suggestions-atom props state animations max-height cursor-pos images reply edit]))
@@ -4,6 +4,7 @@
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area]
[status-im2.contexts.chat.composer.style :as style]
[status-im2.contexts.chat.composer.utils :as utils]
[status-im2.contexts.chat.messages.list.view :as messages.list]
@@ -25,10 +26,16 @@
[:f> f-blur-view layout-height focused?])
(defn- f-shell-button
[insets height maximized?]
(let [translate-y (reanimated/use-shared-value (utils/calc-shell-neg-y insets maximized?))]
(rn/use-effect #(reanimated/animate translate-y (utils/calc-shell-neg-y insets maximized?))
[@maximized?])
[{:keys [maximized?]} {:keys [height]} {:keys [images reply edit]}]
(let [insets (safe-area/get-insets)
extra-height (utils/calc-extra-content-height images reply edit)
translate-y (reanimated/use-shared-value
(utils/calc-shell-neg-y insets maximized? extra-height))]
(rn/use-effect (fn []
(let [extra-height (utils/calc-extra-content-height images reply edit)]
(reanimated/animate translate-y
(utils/calc-shell-neg-y insets maximized? extra-height))))
[@maximized? images reply edit])
[reanimated/view
{:style (reanimated/apply-animations-to-style
{:bottom height ; we use height of the input directly as bottom position
@@ -48,5 +55,5 @@
{}]]))
(defn shell-button
[insets {:keys [height]} {:keys [maximized?]}]
[:f> f-shell-button insets height maximized?])
[state animations subs]
[:f> f-shell-button state animations subs])
@@ -65,17 +65,22 @@
(let [lines (Math/round (/ height constants/line-height))]
(if platform/ios? lines (dec lines))))
(defn calc-extra-content-height
[images? reply? edit?]
(let [height (if images? constants/images-container-height 0)
height (if reply? (+ height constants/reply-container-height) height)
height (if edit? (+ height constants/edit-container-height) height)]
height))
(defn calc-max-height
[window-height kb-height insets images? reply? edit?]
[{:keys [images reply edit]} window-height kb-height insets]
(let [margin-top (if platform/ios? (:top insets) (+ 10 (:top insets)))
max-height (- window-height
margin-top
kb-height
constants/bar-container-height
constants/actions-container-height)
max-height (if images? (- max-height constants/images-container-height) max-height)
max-height (if reply? (- max-height constants/reply-container-height) max-height)
max-height (if edit? (- max-height constants/edit-container-height) max-height)]
max-height (- max-height (calc-extra-content-height images reply edit))]
max-height))
(defn empty-input?
@@ -108,22 +113,24 @@
(* sub-text-lines-in-view constants/line-height)))
(defn calc-shell-neg-y
[insets maximized?]
(let [neg-y (if @maximized? -50 0)]
(- (+ constants/bar-container-height constants/actions-container-height (:bottom insets) 6 neg-y))))
[insets maximized? extra-height]
(let [padding 6
neg-y (if @maximized? -50 0)]
(- (+ constants/bar-container-height
constants/actions-container-height
constants/bottom-padding
(:bottom insets)
padding
extra-height
neg-y))))
(defn calc-suggestions-position
[cursor-pos max-height size
{:keys [maximized?]}
{:keys [insets curr-height window-height keyboard-height edit reply]}]
{:keys [insets curr-height window-height keyboard-height images reply edit]}]
(let [base (+ constants/composer-default-height (:bottom insets) 8)
base (+ base (- curr-height constants/input-height))
base (if edit
(+ base constants/edit-container-height)
base)
base (if reply
(+ base constants/reply-container-height)
base)
base (+ base (calc-extra-content-height images reply edit))
view-height (- window-height keyboard-height (:top insets))
container-height (bounded-val
(* (/ constants/mentions-max-height 4) size)
@@ -170,8 +177,21 @@
:recording? (reagent/atom false)
:first-level? (reagent/atom true)
:menu-items (reagent/atom selection/first-level-menu-items)})
(defn init-subs
[]
(let [chat-input (rf/sub [:chats/current-chat-input])]
{:images (seq (rf/sub [:chats/sending-image]))
:audio (rf/sub [:chats/sending-audio])
:reply (rf/sub [:chats/reply-message])
:edit (rf/sub [:chats/edit-message])
:input-with-mentions (rf/sub [:chat/input-with-mentions])
:input-text (:input-text chat-input)
:input-content-height (:input-content-height chat-input)}))
(defn init-animations
[lines input-text images reply audio content-height max-height opacity background-y]
[{:keys [input-text images reply audio]}
lines content-height max-height opacity background-y]
(let [initial-height (if (> lines 1)
constants/multiline-minimized-height
constants/input-height)]
+22 -32
View File
@@ -4,10 +4,10 @@
[react-native.core :as rn]
[react-native.gesture :as gesture]
[react-native.hooks :as hooks]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[status-im2.contexts.chat.composer.style :as style]
[status-im2.contexts.chat.composer.images.view :as images]
[status-im2.contexts.chat.composer.reply.view :as reply]
@@ -25,31 +25,20 @@
(defn sheet-component
[{:keys [insets window-height blur-height opacity background-y]} props state]
(let [images (rf/sub [:chats/sending-image])
audio (rf/sub [:chats/sending-audio])
reply (rf/sub [:chats/reply-message])
edit (rf/sub [:chats/edit-message])
input-with-mentions (rf/sub [:chat/input-with-mentions])
{:keys [input-text input-content-height]
:as chat-input} (rf/sub [:chats/current-chat-input])
content-height (reagent/atom (or input-content-height
(let [subs (utils/init-subs)
content-height (reagent/atom (or (:input-content-height subs)
constants/input-height))
{:keys [keyboard-shown]} (hooks/use-keyboard)
max-height (utils/calc-max-height window-height
max-height (utils/calc-max-height subs
window-height
@(:kb-height state)
insets
(boolean (seq images))
reply
edit)
insets)
lines (utils/calc-lines (- @content-height
constants/extra-content-offset))
max-lines (utils/calc-lines max-height)
animations (utils/init-animations
subs
lines
input-text
images
reply
audio
content-height
max-height
opacity
@@ -67,17 +56,15 @@
state
animations
dimensions
chat-input
(boolean (seq images))
reply
audio)
(effects/edit props state edit)
(effects/reply props animations reply)
(effects/update-input-mention props state input-text)
(effects/edit-mentions props state input-with-mentions)
subs)
(effects/edit props state subs)
(effects/reply props animations subs)
(effects/update-input-mention props state subs)
(effects/edit-mentions props state subs)
[:<>
[sub-view/shell-button insets animations state]
[mentions/view props state animations max-height cursor-pos]
[sub-view/shell-button state animations subs]
[mentions/view props state animations max-height cursor-pos (:images subs) (:reply subs)
(:edit subs)]
[gesture/gesture-detector
{:gesture (drag-gesture/drag-gesture props state animations dimensions keyboard-shown)}
[reanimated/view
@@ -99,12 +86,12 @@
{:ref #(reset! (:input-ref props) %)
:default-value @(:text-value state)
:on-focus #(handler/focus props state animations dimensions)
:on-blur #(handler/blur state animations dimensions images reply)
:on-blur #(handler/blur state animations dimensions subs)
:on-content-size-change #(handler/content-size-change %
state
animations
dimensions
(or keyboard-shown edit))
(or keyboard-shown (:edit subs)))
:on-scroll #(handler/scroll % props state animations dimensions)
:on-change-text #(handler/change-text % props state)
:on-selection-change #(handler/selection-change % props state)
@@ -119,8 +106,11 @@
:accessibility-label :chat-message-input}]]
[gradients/view props state animations show-bottom-gradient?]]
[images/images-list]
[actions/view props state animations window-height insets edit
(boolean (seq images))]]]]))
[actions/view props state animations window-height insets subs]
(when (and platform/android? (not @(:focused? state)))
[rn/view {:style {:height constants/bottom-padding
:background-color :transparent}}])
]]]))
(defn composer
[insets]
+3 -1
View File
@@ -102,7 +102,8 @@
[]
(fn []
(let [pending-contact-requests (rf/sub [:activity-center/pending-contact-requests])
selected-tab (or (rf/sub [:messages-home/selected-tab]) :tab/recent)
selected-tab (or (rf/sub [:messages-home/selected-tab])
:tab/recent)
top (safe-area/get-top)]
[:<>
(if (= selected-tab :tab/contacts)
@@ -122,6 +123,7 @@
[quo/discover-card
{:title (i18n/label :t/invite-friends-to-status)
:description (i18n/label :t/share-invite-link)}]
^{:key (str "tabs-" selected-tab)}
[quo/tabs
{:style style/tabs
:size 32
@@ -9,8 +9,10 @@
contact (rf/sub [:contacts/contact-by-address public-key])
photo-path (when (seq (:images contact)) (rf/sub [:chats/photo-path public-key]))
online? (rf/sub [:visibility-status-updates/online? public-key])]
[rn/touchable-without-feedback {:on-press #(rf/dispatch [:chat.ui/show-profile public-key])}
[rn/view {:padding-top 2}
[rn/view {:style {:padding-top 2}}
[rn/touchable-opacity
{:active-opacity 1
:on-press #(rf/dispatch [:chat.ui/show-profile public-key])}
[quo/user-avatar
{:full-name display-name
:profile-picture photo-path
@@ -72,5 +72,6 @@
[:<>
(map-indexed
(fn [index item]
[image/image-message index item context #(on-long-press message context)])
[:<> {:key (:message-id item)}
[image/image-message index item context #(on-long-press message context)]])
(:album message))])))
@@ -20,7 +20,8 @@
(h/describe "audio message"
(h/before-each
#(setup-subs {:mediaserver/port 1000}))
#(setup-subs {:mediaserver/port 1000
:app-state "active"}))
(h/test "renders correctly"
(h/render [audio-message/audio-message message context])
@@ -59,7 +59,7 @@
audio-url
(fn [base64-data]
(let [player (audio/new-player
(str "data:audio/acc;base64," base64-data)
(str "data:audio/aac;base64," base64-data)
{:autoDestroy false
:continuesToPlayInBackground false}
(fn []
@@ -68,6 +68,7 @@
(when (and @progress-timer (= @current-player-key player-key))
(js/clearInterval @progress-timer)
(reset! progress-timer nil))))]
(audio/set-player-wake-lock player true)
(swap! active-players assoc player-key player)
(audio/prepare-player
player
@@ -90,7 +91,8 @@
(when-not playing?
(reset! current-player-key player-key))
(if (and player
(= (@audio-uris player-key) audio-uri))
(= (@audio-uris player-key) audio-uri)
(not= (audio/get-state player) audio/IDLE))
(audio/toggle-playpause-player
player
(fn []
@@ -121,7 +123,7 @@
(reset! seeking-audio? false)
(if (> @progress 0)
(let [seek-time (* audio-duration-ms @progress)
checked-seek-time (min audio-duration-ms seek-time)]
checked-seek-time (if (<= @progress 1) seek-time @progress)]
(seek-player
player-key
player-state
@@ -141,13 +143,20 @@
(if (or @seeking-audio? (#{:playing :seeking} @player-state))
(if (<= @progress 1) (* duration @progress) @progress)
duration)
1000)]
1000)
paused? (= (audio/get-state player) audio/PAUSED)
app-state (rf/sub [:app-state])]
(rn/use-effect (fn [] #(destroy-player player-key)))
(rn/use-effect
(fn []
(when (and (some? @current-player-key)
(not= @current-player-key player-key)
(= @player-state :playing))
(when (or
(and (some? @current-player-key)
(not= @current-player-key player-key)
(= @player-state :playing))
(and platform/ios?
(= @current-player-key player-key)
(not= app-state "active")
(= @player-state :playing)))
(play-pause-player {:player-key player-key
:player-state player-state
:progress progress
@@ -155,7 +164,7 @@
:audio-duration-ms duration
:seeking-audio? seeking-audio?
:user-interaction? false})))
[@current-player-key])
[@current-player-key app-state])
(if (= @player-state :error)
[quo/text
{:style style/error-label
@@ -177,10 +186,12 @@
:user-interaction? true})
:style (style/play-pause-container)}
[quo/icon
(case @player-state
:preparing :i/loading
:playing :i/pause-audio
:i/play-audio)
(cond
(= @player-state :preparing)
:i/loading
(and (= @player-state :playing) (not paused?))
:i/pause-audio
:else :i/play-audio)
{:size 20
:color colors/white}]]
[:f> quo/soundtrack
@@ -15,23 +15,21 @@
(defn image-message
[index {:keys [content image-width image-height message-id] :as message} context on-long-press]
(let [insets (safe-area/get-insets)
dimensions (calculate-dimensions (or image-width 1000) (or image-height 1000))]
(fn []
(let [shared-element-id (rf/sub [:shared-element-id])]
[rn/touchable-opacity
{:active-opacity 1
:key message-id
:style {:margin-top (when (pos? index) 10)}
:on-long-press on-long-press
:on-press #(rf/dispatch [:chat.ui/navigate-to-lightbox
message-id
{:messages [message]
:index 0
:insets insets}])}
(when (= index 0)
[rn/view {:style {:margin-bottom 10}} [text/text-content message context]])
[fast-image/fast-image
{:source {:uri (:image content)}
:style (merge dimensions {:border-radius 12})
:native-ID (when (= shared-element-id message-id) :shared-element)}]]))))
(let [insets (safe-area/get-insets)
dimensions (calculate-dimensions (or image-width 1000) (or image-height 1000))
shared-element-id (rf/sub [:shared-element-id])]
[rn/touchable-opacity
{:active-opacity 1
:style {:margin-top (when (pos? index) 10)}
:on-long-press on-long-press
:on-press #(rf/dispatch [:chat.ui/navigate-to-lightbox
message-id
{:messages [message]
:index 0
:insets insets}])}
(when (= index 0)
[rn/view {:style {:margin-bottom 10}} [text/text-content message context]])
[fast-image/fast-image
{:source {:uri (:image content)}
:style (merge dimensions {:border-radius 12})
:native-ID (when (= shared-element-id message-id) :shared-element)}]]))
@@ -0,0 +1,8 @@
(ns status-im2.contexts.communities.home.events
(:require
[utils.re-frame :as rf]))
(rf/defn communities-select-tab-event
{:events [:communities/select-tab]}
[{:keys [db]} tab]
{:db (assoc db :communities/selected-tab tab)})
@@ -1,7 +1,6 @@
(ns status-im2.contexts.communities.home.view
(:require [utils.i18n :as i18n]
[quo2.core :as quo]
[reagent.core :as reagent]
[react-native.core :as rn]
[status-im2.common.home.view :as common.home]
[status-im2.contexts.communities.actions.community-options.view :as options]
@@ -35,40 +34,42 @@
(defn home
[]
(let [selected-tab (reagent/atom :joined)]
(fn []
(let [{:keys [joined pending opened]} (rf/sub [:communities/grouped-by-status])
selected-items (case @selected-tab
:joined joined
:pending pending
:opened opened)
top (safe-area/get-top)]
[:<>
[rn/flat-list
{:key-fn :id
:content-inset-adjustment-behavior :never
:header [rn/view {:height (+ 245 top)}]
:render-fn item-render
:data selected-items}]
[rn/view
{:style (style/blur-container top)}
[blur/view
{:blur-amount (if platform/ios? 20 10)
:blur-type (if (colors/dark?) :dark (if platform/ios? :light :xlight))
:style style/blur}]
[common.home/top-nav]
[common.home/title-column
{:label (i18n/label :t/communities)
:handler #(rf/dispatch [:show-bottom-sheet {:content actions.home-plus/view}])
:accessibility-label :new-chat-button}]
[quo/discover-card
{:on-press #(rf/dispatch [:navigate-to :discover-communities])
:title (i18n/label :t/discover)
:description (i18n/label :t/whats-trending)
:accessibility-label :communities-home-discover-card}]
[quo/tabs
{:size 32
:style style/tabs
:on-change #(reset! selected-tab %)
:default-active @selected-tab
:data tabs-data}]]]))))
(fn []
(let [selected-tab (or (rf/sub [:communities/selected-tab]) :joined)
{:keys [joined pending opened]} (rf/sub [:communities/grouped-by-status])
selected-items (case selected-tab
:joined joined
:pending pending
:opened opened)
top (safe-area/get-top)]
[:<>
[rn/flat-list
{:key-fn :id
:content-inset-adjustment-behavior :never
:header [rn/view {:height (+ 245 top)}]
:render-fn item-render
:data selected-items}]
[rn/view
{:style (style/blur-container top)}
[blur/view
{:blur-amount (if platform/ios? 20 10)
:blur-type (if (colors/dark?) :dark (if platform/ios? :light :xlight))
:style style/blur}]
[common.home/top-nav]
[common.home/title-column
{:label (i18n/label :t/communities)
:handler #(rf/dispatch [:show-bottom-sheet {:content actions.home-plus/view}])
:accessibility-label :new-chat-button}]
[quo/discover-card
{:on-press #(rf/dispatch [:navigate-to :discover-communities])
:title (i18n/label :t/discover)
:description (i18n/label :t/whats-trending)
:accessibility-label :communities-home-discover-card}]
^{:key (str "tabs-" selected-tab)}
[quo/tabs
{:size 32
:style style/tabs
:on-change (fn [tab]
(rf/dispatch [:communities/select-tab tab]))
:default-active selected-tab
:data tabs-data}]]])))
@@ -34,7 +34,7 @@
(defn store-screen-height
[evt]
(let [window-height (rf/sub [:dimensions/window-height])
(let [window-height (:height (rn/get-window))
height (or (oget evt "nativeEvent" "layout" "height") 0)
width (or (oget evt "nativeEvent" "layout" "width") 0)]
;; Layout height calculation
+13 -25
View File
@@ -1,20 +1,17 @@
(ns status-im2.contexts.onboarding.events
(:require
[clojure.string :as string]
[native-module.core :as native-module]
[quo2.theme :as theme]
[re-frame.core :as re-frame]
[status-im.ethereum.core :as ethereum]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.utils.types :as types]
[status-im2.config :as config]
[status-im2.constants :as constants]
[status-im2.contexts.onboarding.profiles.view :as profiles.view]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.image-server :as image-server]
[utils.re-frame :as rf]
[utils.security.core :as security]))
[utils.re-frame :as rf]
[taoensso.timbre :as log]
[re-frame.core :as re-frame]
[status-im.utils.types :as types]
[status-im2.config :as config]
[clojure.string :as string]
[utils.i18n :as i18n]
[utils.security.core :as security]
[native-module.core :as native-module]
[status-im.ethereum.core :as ethereum]
[status-im2.constants :as constants]
[status-im2.contexts.onboarding.profiles.view :as profiles.view]))
(re-frame/reg-fx
:multiaccount/create-account-and-login
@@ -160,14 +157,6 @@
{:db (dissoc db :onboarding-2/profile)
:dispatch [:navigate-to :create-profile]})
(rf/defn save-profile-picture-to-account-db
{:events [:onboarding-2/save-profile-picture-to-account-db]}
[{:keys [db]}]
(let [key-uid (get-in db [:multiaccount :public-key])
media-server-port (get db :mediaserver/port)
identicon (image-server/get-identicons-uri media-server-port key-uid (theme/get-theme))]
{:dispatch [::multiaccounts/save-profile-picture-from-url identicon]}))
(rf/defn onboarding-new-account-finalize-setup
{:events [:onboarding-2/finalize-setup]}
[{:keys [db]}]
@@ -177,8 +166,7 @@
constants/auth-method-biometric
(get-in db [:onboarding-2/profile :auth-method]))]
(cond-> {:dispatch-n [[:navigate-to :identifiers]
[:onboarding-2/save-profile-picture-to-account-db]]}
(cond-> {:dispatch [:navigate-to :identifiers]}
biometric-enabled?
(assoc :biometric/enable-and-save-password
{:key-uid key-uid
@@ -19,13 +19,12 @@
:margin-bottom 20})
(def subtitle-container
{:height 42
:padding-top 16
:padding-bottom 8})
{:height 20
:margin-top 16
:margin-bottom 4})
(def subtitle
{:color colors/white-opa-70
:margin-bottom 20})
{:color colors/white-opa-70})
(def suboptions
{:padding-top 4
@@ -228,7 +228,7 @@
[quo/button
{:size 40
:type :primary
:customization-color (or :primary customization-color)
:customization-color (or customization-color :primary)
:accessibility-label :login-button
:override-theme :dark
:before :i/unlocked
@@ -1,241 +1,11 @@
(ns status-im2.contexts.onboarding.sign-in.view
(:require [clojure.string :as string]
[oops.core :as oops]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[react-native.blur :as blur]
[react-native.camera-kit :as camera-kit]
[react-native.core :as rn]
[react-native.hole-view :as hole-view]
[react-native.permissions :as permissions]
[react-native.platform :as platform]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im2.constants :as constants]
(:require [utils.i18n :as i18n]
[status-im2.contexts.onboarding.common.background.view :as background]
[status-im2.contexts.onboarding.sign-in.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.transforms :as transforms]))
(defonce camera-permission-granted? (reagent/atom false))
(defn- header
[active-tab read-qr-once?]
[:<>
[rn/view {:style style/header-container}
[quo/button
{:icon true
:type :blur-bg
:size 32
:accessibility-label :close-sign-in-by-syncing
:override-theme :dark
:on-press #(rf/dispatch [:navigate-back])}
:i/close]
[quo/button
{:before :i/info
:type :blur-bg
:size 32
:accessibility-label :find-sync-code
:override-theme :dark
:on-press #(js/alert "Yet to be implemented")}
(i18n/label :t/find-sync-code)]]
[quo/text
{:size :heading-1
:weight :semi-bold
:style style/header-text}
(i18n/label :t/sign-in-by-syncing)]
[quo/text
{:size :paragraph-1
:weight :regular
:style style/header-sub-text}
(i18n/label :t/synchronise-your-data-across-your-devices)]
[rn/view {:style style/tabs-container}
[quo/segmented-control
{:size 32
:override-theme :dark
:blur? true
:default-active @active-tab
:data [{:id 1 :label (i18n/label :t/scan-sync-qr-code)}
{:id 2 :label (i18n/label :t/enter-sync-code)}]
:on-change (fn [id]
(reset! active-tab id)
(reset! read-qr-once? false))}]]])
(defn- camera-permission-view
[request-camera-permission]
[rn/view {:style style/camera-permission-container}
[quo/text
{:size :paragraph-1
:weight :medium
:style style/enable-camera-access-header}
(i18n/label :t/enable-access-to-camera)]
[quo/text
{:size :paragraph-2
:weight :regular
:style style/enable-camera-access-sub-text}
(i18n/label :t/to-scan-a-qr-enable-your-camera)]
[quo/button
{:before :i/camera
:type :primary
:size 32
:accessibility-label :request-camera-permission
:override-theme :dark
:on-press request-camera-permission}
(i18n/label :t/enable-camera)]])
(defn- qr-scan-hole-area
[qr-view-finder]
[rn/view
{:style style/qr-view-finder
:on-layout (fn [event]
(let [layout (transforms/js->clj (oops/oget event "nativeEvent.layout"))
width (:width layout)
y (if platform/android?
(+ (safe-area/get-top) (:y layout))
(:y layout))
view-finder (-> layout
(assoc :height width)
(assoc :y y))]
(reset! qr-view-finder view-finder)))}])
(defn- border
[border1 border2 corner]
[rn/view
(assoc {:border-color colors/white :width 80 :height 80} border1 2 border2 2 corner 16)])
(defn- viewfinder
[qr-view-finder]
(let [size (:width qr-view-finder)]
[rn/view {:style (style/viewfinder-container qr-view-finder)}
[rn/view {:width size :height size :justify-content :space-between}
[rn/view {:flex-direction :row :justify-content :space-between}
[border :border-top-width :border-left-width :border-top-left-radius]
[border :border-top-width :border-right-width :border-top-right-radius]]
[rn/view {:flex-direction :row :justify-content :space-between}
[border :border-bottom-width :border-left-width :border-bottom-left-radius]
[border :border-bottom-width :border-right-width :border-bottom-right-radius]]]
[quo/text
{:size :paragraph-2
:weight :regular
:style style/viewfinder-text}
(i18n/label :t/ensure-qr-code-is-in-focus-to-scan)]]))
(defn- scan-qr-code-tab
[qr-view-finder request-camera-permission]
[:<>
[rn/view {:style style/scan-qr-code-container}]
(when (empty? @qr-view-finder)
[qr-scan-hole-area qr-view-finder])
(if (and @camera-permission-granted? (boolean (not-empty @qr-view-finder)))
[viewfinder @qr-view-finder]
[camera-permission-view request-camera-permission])])
(defn- enter-sync-code-tab
[]
[rn/view {:style style/enter-sync-code-container}
[quo/text
{:size :paragraph-1
:weight :medium
:style {:color colors/white}}
"Yet to be implemented"]])
(defn- bottom-view
[insets]
[rn/touchable-without-feedback
{:on-press #(js/alert "Yet to be implemented")}
[rn/view
{:style (style/bottom-container (:bottom insets))}
[quo/text
{:size :paragraph-2
:weight :regular
:style style/bottom-text}
(i18n/label :t/i-dont-have-status-on-another-device)]]])
(defn- check-qr-code-data
[event]
(let [connection-string (string/trim (oops/oget event "nativeEvent.codeStringValue"))
valid-connection-string? (string/starts-with?
connection-string
constants/local-pairing-connection-string-identifier)]
(if valid-connection-string?
(rf/dispatch [:syncing/input-connection-string-for-bootstrapping connection-string])
(rf/dispatch [:toasts/upsert
{:icon :i/info
:icon-color colors/danger-50
:override-theme :light
:text (i18n/label :t/error-this-is-not-a-sync-qr-code)}]))))
(defn f-view
[]
(let [camera-ref (atom nil)
read-qr-once? (atom false)
insets (safe-area/get-insets)
active-tab (reagent/atom 1)
qr-view-finder (reagent/atom {})
{:keys [height width]} (rf/sub [:dimensions/window])
request-camera-permission (fn []
(rf/dispatch
[:request-permissions
{:permissions [:camera]
:on-allowed #(reset! camera-permission-granted? true)
:on-denied #(rf/dispatch
[:toasts/upsert
{:icon :i/info
:icon-color colors/danger-50
:override-theme :light
:text (i18n/label
:t/camera-permission-denied)}])}]))]
(fn []
(let [holes (merge @qr-view-finder {:borderRadius 16})
scan-qr-code-tab? (= @active-tab 1)
show-camera? (and scan-qr-code-tab? @camera-permission-granted?)
show-holes? (and show-camera?
(boolean (not-empty @qr-view-finder)))
on-read-code (fn [data]
(when-not @read-qr-once?
(reset! read-qr-once? true)
(js/setTimeout (fn []
(reset! read-qr-once? false))
3000)
(check-qr-code-data data)))
hole-view-wrapper (if show-camera?
[hole-view/hole-view
{:style style/absolute-fill
:holes (if show-holes?
[holes]
[])}]
[:<>])]
(rn/use-effect
(fn []
(when-not @camera-permission-granted?
(permissions/permission-granted? :camera
#(reset! camera-permission-granted? %)
#(reset! camera-permission-granted? false)))))
[rn/view {:style (style/root-container (:top insets))}
(if show-camera?
[camera-kit/camera
{:ref #(reset! camera-ref %)
:style (merge style/absolute-fill {:height height :width width})
:camera-options {:zoomMode :off}
:scan-barcode true
:on-read-code on-read-code}]
[background/view true])
(conj hole-view-wrapper
[blur/view
{:style style/absolute-fill
:overlay-color colors/neutral-80-opa-80
:blur-type :dark
:blur-amount (if platform/ios? 15 5)}])
[header active-tab read-qr-once?]
(case @active-tab
1 [scan-qr-code-tab qr-view-finder request-camera-permission]
2 [enter-sync-code-tab]
nil)
[rn/view {:style style/flex-spacer}]
[bottom-view insets]]))))
[status-im2.contexts.syncing.scan-sync-code.view :as scan-sync-code]))
(defn view
[]
[:f> f-view])
[scan-sync-code/view
{:title (i18n/label :t/sign-in-by-syncing)
:show-bottom-view? true
:background [background/view true]}])
+19 -13
View File
@@ -31,25 +31,31 @@
(defn- f-bottom-tabs
[]
(let [notifications-data (rf/sub [:shell/bottom-tabs-notifications-data])
pass-through? (rf/sub [:shell/shell-pass-through?])
shared-values @animation/shared-values-atom
original-style (style/bottom-tabs-container pass-through?)
animated-style (reanimated/apply-animations-to-style
{:height (:bottom-tabs-height shared-values)}
original-style)
messages-double-tap-gesture (-> (gesture/gesture-tap)
(gesture/number-of-taps 2)
(gesture/on-start
(fn [_event]
(rf/dispatch [:messages-home/select-tab :tab/recent]))))]
(let [notifications-data (rf/sub [:shell/bottom-tabs-notifications-data])
pass-through? (rf/sub [:shell/shell-pass-through?])
shared-values @animation/shared-values-atom
original-style (style/bottom-tabs-container pass-through?)
animated-style (reanimated/apply-animations-to-style
{:height (:bottom-tabs-height shared-values)}
original-style)
communities-double-tab-gesture (-> (gesture/gesture-tap)
(gesture/number-of-taps 2)
(gesture/on-start
(fn [_event]
(rf/dispatch [:communities/select-tab :joined]))))
messages-double-tap-gesture (-> (gesture/gesture-tap)
(gesture/number-of-taps 2)
(gesture/on-start
(fn [_event]
(rf/dispatch [:messages-home/select-tab :tab/recent]))))]
(animation/load-stack @animation/selected-stack-id)
(reanimated/set-shared-value (:pass-through? shared-values) pass-through?)
[reanimated/view {:style animated-style}
(when pass-through?
[blur/view (blur-overlay-params style/bottom-tabs-blur-overlay)])
[rn/view {:style (style/bottom-tabs)}
[bottom-tab :i/communities :communities-stack shared-values notifications-data]
[gesture/gesture-detector {:gesture communities-double-tab-gesture}
[bottom-tab :i/communities :communities-stack shared-values notifications-data]]
[gesture/gesture-detector {:gesture messages-double-tap-gesture}
[bottom-tab :i/messages :chats-stack shared-values notifications-data]]
[bottom-tab :i/wallet :wallet-stack shared-values notifications-data]
@@ -0,0 +1,72 @@
(ns status-im2.contexts.syncing.how-to-pair.style
(:require [quo2.foundations.colors :as colors]
[react-native.platform :as platform]))
(def container-outer
{:flex (if platform/ios? 4.5 5)
:padding-top 20
:padding-bottom 8
:padding-horizontal 16
:background-color colors/neutral-95})
(def heading
{:margin-bottom 12
:color colors/white})
(def tabs-container
{:margin-top 8
:margin-bottom 12})
(def paragraph
{:margin-vertical 8
:color colors/white})
(def hr
{:margin-top 12
:margin-bottom 6
:border-bottom-color colors/white-opa-10
:border-bottom-width 1})
(def image
{:border-radius 16})
(def container-image
{:flex-direction :row
:aspect-ratio 1
:justify-content :flex-end
:align-items :flex-end
:overflow :hidden
:background-color colors/white-opa-5
:border-radius 16})
(def list-text
{:margin-horizontal 2
:color colors/white})
(def list-icon
{:height 18
:width 18
:border-radius 6
:border-width 1
:border-color colors/white-opa-10
:align-items :center
:justify-content :center})
(def list-icon-text
{:color colors/white})
(def button-grey
{:margin-horizontal 2})
(def button-grey-placeholder
{:margin-horizontal 2})
(def button-primary
{:margin-horizontal 2})
(def numbered-list
{:margin-top 12})
(def numbered-list-item
{:flex-direction :row
:align-items :center})
@@ -0,0 +1,160 @@
(ns status-im2.contexts.syncing.how-to-pair.view
(:require
[react-native.gesture :as gesture]
[status-im2.contexts.syncing.how-to-pair.style :as style]
[status-im2.common.resources :as resources]
[utils.i18n :as i18n]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[react-native.core :as rn]
[reagent.core :as reagent]))
(defn render-element
[[type value]]
(case type
:text
[quo/text
{:size :paragraph-2
:weight :regular
:style style/list-text} (i18n/label value)]
:button-primary
[quo/button
{:type :primary
:size 24
:style style/button-primary} (i18n/label value)]
:button-grey
[quo/button
{:type :grey
:override-theme :dark
:size 24
:style style/button-grey}
(i18n/label value)]
:button-grey-placeholder
[quo/button
{:type :grey
:override-theme :dark
:size 24
:before :i/placeholder
:style style/button-grey-placeholder}
(i18n/label value)]
:context-tag
[quo/context-tag
{:override-theme :dark
:text-style {:color colors/white}}
(resources/get-mock-image (:source value))
(i18n/label (:label value))]))
(defn render-item
[i item]
[rn/view
{:margin-vertical 6
:style style/numbered-list-item}
[rn/view
{:margin-right 6
:style style/list-icon}
[quo/text
{:size :label
:weight :medium
:style style/list-icon-text} i]]
(map #(render-element %) item)])
(defn render-list
[{:keys [title image list]}]
[rn/view
[quo/text
{:size :paragraph-1
:weight :semi-bold
:style style/paragraph} (i18n/label title)]
(case (:type image)
:container-image [rn/view {:style style/container-image}
[rn/image
{:source (resources/get-image
(:source image))}]]
:image [rn/image
{:source (resources/get-image (:source image))
:style style/image}])
[rn/view {:style style/numbered-list}
(map-indexed (fn [i item] (render-item (inc i) item)) list)]])
(def platforms
{:mobile
[gesture/scroll-view
(render-list {:title :t/signing-in-from-another-device
:image {:source :mobile-how-to-pair-sign-in
:type :image}
:list [[[:text :t/open-status-on-your-other-device]]
[[:text :t/tap-on]
[:button-grey :t/im-new]]
[[:button-primary :t/enable-camera]
[:text :t/or-tap]
[:button-grey :t/enter-sync-code]]
[[:text :t/scan-or-enter-sync-code-seen-on-this-device]]]})
[rn/view {:style style/hr}]
(render-list {:title :t/already-logged-in-on-the-other-device
:image {:source :mobile-how-to-pair-logged-in
:type :image}
:list [[[:text :t/tap-on]
[:context-tag
{:label :t/profile
:source :user-picture-male5}]
[:text :t/and]
[:button-grey-placeholder :t/syncing]]
[[:text :t/press]
[:button-grey :t/scan-or-enter-sync-code]]
[[:button-primary :t/enable-camera]
[:text :t/or-tap]
[:button-grey :t/enter-sync-code]]
[[:text :t/scan-or-enter-sync-code-seen-on-this-device]]]})]
:desktop
[gesture/scroll-view
(render-list {:title :t/signing-in-from-another-device
:image {:source :desktop-how-to-pair-sign-in
:type :image}
:list [[[:text :t/open-status-on-your-other-device]]
[[:text :t/open]
[:button-grey :t/settings]
[:text :t/and-go-to]
[:button-grey :t/syncing]]
[[:text :t/tap]
[:button-grey :t/scan-or-enter-a-sync-code]]
[[:text :t/scan-the-qr-code-or-copy-the-sync-code]]]})
[rn/view {:style style/hr}]
(render-list {:title :t/already-logged-in-on-the-other-device
:image {:source :desktop-how-to-pair-logged-in
:type :container-image}
:list [[[:text :t/open-status-on-your-other-device]]
[[:text :t/tap-on]
[:button-grey :t/im-new]]
[[:button-primary :t/enable-camera]
[:text :t/or-tap]
[:button-grey :t/enter-sync-code]]
[[:text :t/scan-or-enter-sync-code-seen-on-this-device]]]})]})
(defn instructions
[]
(let [platform (reagent/atom :mobile)
platform-data (map (fn [platform]
{:id platform
:label (i18n/label
(keyword (str "t/" (name platform))))})
(keys platforms))]
(fn []
[rn/view {:style style/container-outer}
[quo/text
{:size :heading-1
:weight :semi-bold
:style style/heading} (i18n/label :t/how-to-pair)]
[rn/view {:style style/tabs-container}
[quo/segmented-control
{:size 28
:override-theme :dark
:blur? true
:default-active :mobile
:data platform-data
:on-change #(reset! platform %)}]]
(@platform platforms)])))
@@ -0,0 +1,113 @@
(ns status-im2.contexts.syncing.scan-sync-code.style
(:require [quo2.foundations.colors :as colors]
[status-im.utils.platform :as platform]))
(def screen-padding 20)
(def flex-spacer {:flex 1})
(def absolute-fill
{:position :absolute
:top 0
:bottom 0
:left 0
:right 0})
(def hole
(merge absolute-fill
{:z-index 2 :opacity 0.95}))
(defn root-container
[padding-top]
{:z-index 5
:flex 1
:padding-top padding-top})
(def header-container
{:flex-direction :row
:justify-content :space-between
:padding-horizontal screen-padding
:margin-vertical 12})
(def header-text
{:padding-horizontal screen-padding
:padding-top 12
:padding-bottom 8
:color colors/white})
(def header-sub-text
{:padding-horizontal screen-padding
:color colors/white})
(def tabs-container
{:padding-horizontal screen-padding
:margin-top 20})
(def scan-qr-code-container
{:margin-top 19})
(def qr-view-finder
{:margin-horizontal screen-padding
:height 1
:display :flex})
(defn viewfinder-container
[viewfinder]
{:position :absolute
:left (:x viewfinder)
:top (:y viewfinder)
:overflow :hidden})
(def viewfinder-text
{:color colors/white-opa-70
:text-align :center
:padding-top 16})
(def camera-permission-container
{:height 335
:margin-horizontal screen-padding
:background-color colors/white-opa-5
:border-color colors/white-opa-10
:border-radius 12
:align-items :center
:justify-content :center})
(def enable-camera-access-header
{:color colors/white})
(def enable-camera-access-sub-text
{:color colors/white-opa-70
:margin-bottom 16})
(def enter-sync-code-container
{:margin-top 20
:justify-content :center
:align-items :center})
(defn bottom-container
[padding-bottom]
{:z-index 6
:padding-top 12
:padding-bottom padding-bottom
:background-color colors/white-opa-5
:border-top-left-radius 20
:border-top-right-radius 20
:align-items :center
:justify-content :center})
(def bottom-text
{:color colors/white
:padding-bottom 12})
(def camera-style
{:height "100%"
:borderRadius 16
:background-color :transparent})
(def camera-container
{:position :absolute
:top (if platform/android? 40 0)
:left 0
:right 0
:bottom 0
:border-radius 16})
@@ -0,0 +1,239 @@
(ns status-im2.contexts.syncing.scan-sync-code.view
(:require [clojure.string :as string]
[oops.core :as oops]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[react-native.camera-kit :as camera-kit]
[react-native.core :as rn]
[react-native.blur :as blur]
[react-native.hole-view :as hole-view]
[react-native.permissions :as permissions]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im2.contexts.syncing.scan-sync-code.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[status-im2.contexts.syncing.utils :as sync-utils]))
(defonce camera-permission-granted? (reagent/atom false))
(defn- header
[active-tab read-qr-once? title]
[:<>
[rn/view {:style style/header-container}
[quo/button
{:icon true
:type :blur-bg
:size 32
:accessibility-label :close-sign-in-by-syncing
:override-theme :dark
:on-press #(rf/dispatch [:navigate-back])}
:i/close]
[quo/button
{:before :i/info
:type :blur-bg
:size 32
:accessibility-label :find-sync-code
:override-theme :dark
:on-press #(js/alert "Yet to be implemented")}
(i18n/label :t/find-sync-code)]]
[quo/text
{:size :heading-1
:weight :semi-bold
:style style/header-text}
title]
[quo/text
{:size :paragraph-1
:weight :regular
:style style/header-sub-text}
(i18n/label :t/synchronise-your-data-across-your-devices)]
[rn/view {:style style/tabs-container}
[quo/segmented-control
{:size 32
:override-theme :dark
:blur? true
:default-active @active-tab
:data [{:id 1 :label (i18n/label :t/scan-sync-qr-code)}
{:id 2 :label (i18n/label :t/enter-sync-code)}]
:on-change (fn [id]
(reset! active-tab id)
(reset! read-qr-once? false))}]]])
(defn- camera-permission-view
[request-camera-permission]
[rn/view {:style style/camera-permission-container}
[quo/text
{:size :paragraph-1
:weight :medium
:style style/enable-camera-access-header}
(i18n/label :t/enable-access-to-camera)]
[quo/text
{:size :paragraph-2
:weight :regular
:style style/enable-camera-access-sub-text}
(i18n/label :t/to-scan-a-qr-enable-your-camera)]
[quo/button
{:before :i/camera
:type :primary
:size 32
:accessibility-label :request-camera-permission
:override-theme :dark
:on-press request-camera-permission}
(i18n/label :t/enable-camera)]])
(defn- qr-scan-hole-area
[qr-view-finder]
[rn/view
{:style style/qr-view-finder
:on-layout (fn [event]
(let [layout (js->clj (oops/oget event "nativeEvent.layout")
:keywordize-keys
true)
view-finder (assoc layout :height (:width layout))]
(reset! qr-view-finder view-finder)))}])
(defn- border
[border1 border2 corner]
[rn/view
(assoc {:border-color colors/white :width 80 :height 80} border1 2 border2 2 corner 16)])
(defn- viewfinder
[qr-view-finder]
(let [size (:width qr-view-finder)]
[:<>
[rn/view {:style (style/viewfinder-container qr-view-finder)}
[rn/view {:width size :height size :justify-content :space-between}
[rn/view {:flex-direction :row :justify-content :space-between}
[border :border-top-width :border-left-width :border-top-left-radius]
[border :border-top-width :border-right-width :border-top-right-radius]]
[rn/view {:flex-direction :row :justify-content :space-between}
[border :border-bottom-width :border-left-width :border-bottom-left-radius]
[border :border-bottom-width :border-right-width :border-bottom-right-radius]]]
[quo/text
{:size :paragraph-2
:weight :regular
:style style/viewfinder-text}
(i18n/label :t/ensure-qr-code-is-in-focus-to-scan)]]]))
(defn- scan-qr-code-tab
[qr-view-finder request-camera-permission]
[:<>
[rn/view {:style style/scan-qr-code-container}]
(when (empty? @qr-view-finder)
[qr-scan-hole-area qr-view-finder])
(if (and @camera-permission-granted? (boolean (not-empty @qr-view-finder)))
[viewfinder @qr-view-finder]
[camera-permission-view request-camera-permission])])
(defn- enter-sync-code-tab
[]
[rn/view {:style style/enter-sync-code-container}
[quo/text
{:size :paragraph-1
:weight :medium
:style {:color colors/white}}
"Yet to be implemented"]])
(defn- bottom-view
[insets]
[rn/touchable-without-feedback
{:on-press #(js/alert "Yet to be implemented")}
[rn/view
{:style (style/bottom-container (:bottom insets))}
[quo/text
{:size :paragraph-2
:weight :regular
:style style/bottom-text}
(i18n/label :t/i-dont-have-status-on-another-device)]]])
(defn- check-qr-code-data
[event]
(let [connection-string (string/trim (oops/oget event "nativeEvent.codeStringValue"))
valid-connection-string? (sync-utils/valid-connection-string? connection-string)]
(if valid-connection-string?
(rf/dispatch [:syncing/input-connection-string-for-bootstrapping connection-string])
(rf/dispatch [:toasts/upsert
{:icon :i/info
:icon-color colors/danger-50
:override-theme :light
:text (i18n/label :t/error-this-is-not-a-sync-qr-code)}]))))
(defn render-camera
[show-camera? qr-view-finder camera-ref on-read-code show-holes?]
(when (and show-camera? (:x qr-view-finder))
[:<>
[rn/view {:style style/camera-container}
[camera-kit/camera
{:ref #(reset! camera-ref %)
:style style/camera-style
:camera-options {:zoomMode :off}
:scan-barcode true
:on-read-code on-read-code}]]
[hole-view/hole-view
{:style style/hole
:holes (if show-holes?
[(merge qr-view-finder
{:borderRadius 16})]
[])}
[blur/view
{:style style/absolute-fill
:blur-amount 10
:blur-type :transparent
:overlay-color colors/neutral-80-opa-80
:background-color colors/neutral-80-opa-80}]
]]))
(defn f-view
[{:keys [title show-bottom-view? background]}]
(let [insets (safe-area/get-insets)
active-tab (reagent/atom 1)
qr-view-finder (reagent/atom {})
request-camera-permission (fn []
(rf/dispatch
[:request-permissions
{:permissions [:camera]
:on-allowed #(reset! camera-permission-granted? true)
:on-denied #(rf/dispatch
[:toasts/upsert
{:icon :i/info
:icon-color colors/danger-50
:override-theme :light
:text (i18n/label
:t/camera-permission-denied)}])}]))]
(fn []
(let [camera-ref (atom nil)
read-qr-once? (atom false)
on-read-code (fn [data]
(when-not @read-qr-once?
(reset! read-qr-once? true)
(js/setTimeout (fn []
(reset! read-qr-once? false))
3000)
(check-qr-code-data data)))
scan-qr-code-tab? (= @active-tab 1)
show-camera? (and scan-qr-code-tab? @camera-permission-granted?)
show-holes? (and show-camera?
(boolean (not-empty @qr-view-finder)))]
(rn/use-effect
(fn []
(when-not @camera-permission-granted?
(permissions/permission-granted? :camera
#(reset! camera-permission-granted? %)
#(reset! camera-permission-granted? false)))))
[:<>
background
[render-camera show-camera? @qr-view-finder camera-ref on-read-code show-holes?]
[rn/view {:style (style/root-container (:top insets))}
[header active-tab read-qr-once? title]
(case @active-tab
1 [scan-qr-code-tab qr-view-finder request-camera-permission]
2 [enter-sync-code-tab]
nil)
[rn/view {:style style/flex-spacer}]
(when show-bottom-view? [bottom-view insets])
]]))))
(defn view
[props]
[:f> f-view props])
@@ -0,0 +1,10 @@
(ns status-im2.contexts.syncing.scan-sync-code-page.style
(:require [quo2.foundations.colors :as colors]))
(def background
{:position :absolute
:top 0
:bottom 0
:left 0
:right 0
:background-color colors/neutral-95})
@@ -0,0 +1,12 @@
(ns status-im2.contexts.syncing.scan-sync-code-page.view
(:require [react-native.core :as rn]
[status-im2.contexts.syncing.scan-sync-code-page.style :as style]
[status-im2.contexts.syncing.scan-sync-code.view :as scan-sync-code]
[utils.i18n :as i18n]))
(defn view
[]
[scan-sync-code/view
{:title (i18n/label :t/scan-sync-code)
:background [rn/view
{:style style/background} true]}])
@@ -1,20 +1,19 @@
(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]))
[react-native.safe-area :as safe-area]
[status-im2.contexts.syncing.utils :as sync-utils]))
(def code-valid-for-ms 120000)
(def one-min-ms 60000)
@@ -33,14 +32,7 @@
: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)))
:on-press #(rf/dispatch [:open-modal :how-to-pair])}]}]])
(defn f-use-interval
[clock cleanup-clock delay]
@@ -53,7 +45,7 @@
delay (reagent/atom nil)
timestamp (reagent/atom nil)
set-code (fn [connection-string]
(when (valid-cs? connection-string)
(when (sync-utils/valid-connection-string? connection-string)
(reset! timestamp (* 1000 (js/Math.ceil (/ (datetime/timestamp) 1000))))
(reset! delay 1000)
(reset! code connection-string)))
@@ -84,14 +76,14 @@
: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)
[rn/view {:style (style/qr-container (sync-utils/valid-connection-string? @code))}
(if (sync-utils/valid-connection-string? @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)
(when-not (sync-utils/valid-connection-string? @code)
[quo/button
{:on-press (fn []
;TODO https://github.com/status-im/status-mobile/issues/15570
@@ -103,7 +95,7 @@
:size 40
:style style/generate-button
:before :i/reveal} (i18n/label :t/reveal-sync-code)])
(when (valid-cs? @code)
(when (sync-utils/valid-connection-string? @code)
[rn/view
{:style style/valid-cs-container}
[rn/view
@@ -144,5 +136,5 @@
[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)}]]]]]])))
:on-press #(rf/dispatch [:navigate-to :scan-sync-code-page])
:label (i18n/label :t/scan-or-enter-sync-code)}]]]]]])))
@@ -0,0 +1,10 @@
(ns status-im2.contexts.syncing.utils
(:require [clojure.string :as string]
[status-im2.constants :as constants]))
(defn valid-connection-string?
[connection-string]
(when connection-string
(string/starts-with?
connection-string
constants/local-pairing-connection-string-identifier)))
+2 -1
View File
@@ -43,7 +43,8 @@
(rf/merge cofx
(multiaccounts.login/open-login (select-keys
multiaccount
[:key-uid :name :public-key :images]))
[:key-uid :name :public-key :images
:customization-color]))
(keychain/get-auth-method (:key-uid multiaccount))))
(navigation/init-root cofx :intro))))
+1 -3
View File
@@ -75,9 +75,7 @@
(get roots/themes root-id)
root-id])
(reset! state/root-id (or (get-in root [:root :stack :id]) root-id))
(if root
(navigation/set-root root)
(println "root" root-id root)))))
(navigation/set-root root))))
;; NAVIGATE-TO
(defn navigate
+11 -3
View File
@@ -22,12 +22,20 @@
{:statusBar {:style (or status-bar-theme :light)}}))
(defn default-root
[& [status-bar-theme]]
[& [status-bar-theme background-color]]
(merge (statusbar-and-navbar-root status-bar-theme)
{:topBar {:visible false}
:layout {:componentBackgroundColor (colors/theme-colors colors/white colors/neutral-100)
:layout {:componentBackgroundColor (or background-color
(colors/theme-colors colors/white colors/neutral-100))
:orientation :portrait
:backgroundColor (colors/theme-colors colors/white colors/neutral-100)}}))
:backgroundColor (or background-color
(colors/theme-colors colors/white
colors/neutral-100))}}))
(def onboarding-layout
{:componentBackgroundColor colors/neutral-80-opa-80-blur
:orientation :portrait
:backgroundColor colors/neutral-80-opa-80-blur})
(defn navbar
([dark?]
+1 -1
View File
@@ -96,7 +96,7 @@
{:stack {:id :intro
:children [{:component {:name :intro
:id :intro
:options (options/default-root)}}]}}}
:options (options/default-root nil colors/neutral-100)}}]}}}
:shell-stack
{:root
{:stack {:id :shell-stack
+28 -8
View File
@@ -24,7 +24,9 @@
[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.scan-sync-code-page.view :as scan-sync-code-page]
[status-im2.contexts.syncing.syncing-devices-list.view :as settings-syncing]
[status-im2.contexts.syncing.how-to-pair.view :as how-to-pair]
[status-im2.navigation.options :as options]
[status-im2.contexts.chat.group-details.view :as group-details]
[status-im.ui.screens.screens :as old-screens]
@@ -36,10 +38,7 @@
(concat
(old-screens/screens)
[{:name :intro
:component intro/view}
{:name :activity-center
[{:name :activity-center
:options options/transparent-screen-options
:component activity-center/view}
@@ -74,6 +73,10 @@
:options {:sheet? true}
:component add-new-contact/new-contact}
{:name :how-to-pair
:options {:sheet? true}
:component how-to-pair/instructions}
{:name :discover-communities
:component communities.discover/discover}
@@ -90,50 +93,67 @@
:component settings-setup-syncing/view}
;; Onboarding
{:name :intro
:component intro/view}
{:name :profiles
:options {:layout options/onboarding-layout}
:component profiles/views}
{:name :new-to-status
:options {:layout options/onboarding-layout}
:component new-to-status/new-to-status}
{:name :create-profile
:options {:layout options/onboarding-layout}
:component create-profile/create-profile}
{:name :create-profile-password
:options {:insets {:top false}}
:options {:insets {:top false}
:layout options/onboarding-layout}
:component create-password/create-password}
{:name :enable-biometrics
:options {:layout options/onboarding-layout}
:component enable-biometrics/enable-biometrics}
{:name :generating-keys
:options {:popGesture false
:options {:layout options/onboarding-layout
:popGesture false
:hardwareBackButton {:dismissModalOnPress false
:popStackOnPress false}}
:component generating-keys/generating-keys}
{:name :enter-seed-phrase
:options {:layout options/onboarding-layout}
:component enter-seed-phrase/enter-seed-phrase}
{:name :enable-notifications
:options {:popGesture false
:options {:layout options/onboarding-layout
:popGesture false
:hardwareBackButton {:dismissModalOnPress false
:popStackOnPress false}}
:component enable-notifications/enable-notifications}
{:name :identifiers
:component identifiers/view
:options {:popGesture false
:options {:layout options/onboarding-layout
:popGesture false
:hardwareBackButton {:dismissModalOnPress false
:popStackOnPress false}}}
{:name :scan-sync-code-page
:component scan-sync-code-page/view}
{:name :sign-in
:options {:layout options/onboarding-layout}
:component sign-in/view}
{:name :syncing-devices
:options {:layout options/onboarding-layout}
:component syncing-devices/syncing-devices}
{:name :welcome
:options {:layout options/onboarding-layout}
:component welcome/view}]
(when config/quo-preview-enabled?
+22 -3
View File
@@ -4,9 +4,10 @@
[re-frame.core :as re-frame]
[status-im.ethereum.core :as ethereum]
[status-im.fleet.core :as fleet]
[status-im.multiaccounts.core :as multiaccount.core]
[status-im.multiaccounts.db :as multiaccounts.db]
[utils.security.core :as security]))
[utils.image-server :as image-server]
[utils.security.core :as security]
[quo2.theme :as theme]))
(re-frame/reg-sub
:multiaccount/public-key
@@ -205,12 +206,30 @@
(fn [multiaccount]
(pos? (count (get multiaccount :images)))))
(defn- replace-multiaccount-image-uri
[multiaccount port]
(let [public-key (:public-key multiaccount)
theme (theme/get-theme)
images (:images multiaccount)
images (reduce (fn [acc current]
(let [key-uid (:keyUid current)
image-name (:type current)
uri (image-server/get-account-image-uri port
public-key
image-name
key-uid
theme)]
(conj acc (assoc current :uri uri))))
[]
images)]
(assoc multiaccount :images images)))
(re-frame/reg-sub
:profile/multiaccount
:<- [:multiaccount]
:<- [:mediaserver/port]
(fn [[multiaccount port]]
(multiaccount.core/replace-multiaccount-image-uri multiaccount port)))
(replace-multiaccount-image-uri multiaccount port)))
;; LINK PREVIEW
;; ========================================================================================================
+1
View File
@@ -249,6 +249,7 @@
(reg-root-key-sub :communities/resolve-community-info :communities/resolve-community-info)
(reg-root-key-sub :communities/my-pending-requests-to-join :communities/my-pending-requests-to-join)
(reg-root-key-sub :communities/collapsed-categories :communities/collapsed-categories)
(reg-root-key-sub :communities/selected-tab :communities/selected-tab)
(reg-root-key-sub :activity-center :activity-center)
+3 -3
View File
@@ -3,7 +3,7 @@
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
"owner": "status-im",
"repo": "status-go",
"version": "v0.150.2",
"commit-sha1": "31144ed5a38589fd129945b634f54e800b39645a",
"src-sha256": "0jh6bidhf0gs15samqh5whw4iigr7ql11cx0v6ma7achr6j4r1d2"
"version": "v0.151.5",
"commit-sha1": "31d02f5197684317ad9f14874b0980d5ca716be6",
"src-sha256": "0ih77pq3lbk6qadv018pp7gzd6vhgzcl34vm1vg2mdmv2f15zvlw"
}
@@ -256,81 +256,6 @@ class TestOneToOneChatMultipleSharedDevices(MultipleSharedDeviceTestCase):
self.errors.verify_no_errors()
@marks.testrail_id(6305)
def test_1_1_chat_image_send_save_reply(self):
self.home_1.get_app_from_background()
self.home_2.get_app_from_background()
self.home_1.home_button.click()
self.home_1.get_chat(username=self.default_username_2).click()
self.home_1.just_fyi('send image in 1-1 chat from Gallery, check options for sender')
image_description = 'description'
self.chat_1.show_images_button.click()
self.chat_1.allow_button.click_if_shown()
self.chat_1.first_image_from_gallery.click()
if not self.chat_1.cancel_send_image_button.is_element_displayed():
self.errors.append("Can't cancel sending images, expected image preview is not shown!")
self.chat_1.chat_message_input.set_value(image_description)
self.chat_1.send_message_button.click()
self.chat_1.chat_message_input.click()
for message in self.chat_1.image_message_in_chat, self.chat_1.chat_element_by_text(image_description):
if not message.is_element_displayed():
self.errors.append('Image or description is not shown in chat after sending for sender')
self.chat_1.image_message_in_chat.long_press_element()
for element in self.chat_1.reply_message_button, self.chat_1.save_image_button:
if not element.is_element_displayed():
self.errors.append('Save and reply are not available on long-press on own image messages')
if self.chat_1.view_profile_button.is_element_displayed():
self.errors.append('"View profile" is shown on long-press on own message')
self.home_2.just_fyi('check image, description and options for receiver')
self.home_2.get_chat(self.default_username_1).click()
for message in self.chat_2.image_message_in_chat, self.chat_2.chat_element_by_text(image_description):
if not message.is_element_displayed():
self.errors.append('Image or description is not shown in chat after sending for receiver')
self.home_2.just_fyi('check options on long-press image for receiver')
self.chat_2.image_message_in_chat.long_press_element()
for element in (self.chat_2.reply_message_button, self.chat_2.save_image_button):
if not element.is_element_displayed():
self.errors.append('Save and reply are not available on long-press on received image messages')
self.home_1.just_fyi('save image')
self.chat_1.save_image_button.click_until_presence_of_element(self.chat_1.show_images_button)
self.chat_1.show_images_button.click_until_presence_of_element(self.chat_1.image_from_gallery_button)
self.chat_1.image_from_gallery_button.click_until_presence_of_element(self.chat_1.recent_image_in_gallery)
if not self.chat_1.recent_image_in_gallery.is_element_displayed():
self.errors.append('Saved image is not shown in Recent')
self.home_1.click_system_back_button(2)
self.home_2.just_fyi('reply to image message')
self.chat_2.reply_message_button.click()
if self.chat_2.quote_username_in_message_input.text != "↪ Replying to %s" % self.default_username_1:
self.errors.append("Username is not displayed in reply quote snippet replying to image message")
reply_to_message_from_receiver = "image reply"
self.chat_2.send_message(reply_to_message_from_receiver)
reply_message = self.chat_2.chat_element_by_text(reply_to_message_from_receiver)
if not reply_message.image_in_reply.is_element_displayed():
self.errors.append("Image is not displayed in reply")
self.home_2.just_fyi('check share and save options on opened image')
self.chat_2.image_message_in_chat.scroll_to_element(direction='up')
self.chat_2.image_message_in_chat.click_until_presence_of_element(self.chat_2.share_image_icon_button)
self.chat_2.share_image_icon_button.click()
self.chat_2.share_via_messenger()
if not self.chat_2.image_in_android_messenger.is_element_displayed():
self.errors.append("Can't share image")
self.chat_2.click_system_back_button_until_element_is_shown(element=self.chat_2.save_image_icon_button)
self.chat_2.save_image_icon_button.click()
self.chat_2.show_images_button.click()
self.chat_2.allow_button.wait_and_click()
if not self.chat_2.first_image_from_gallery.is_element_image_similar_to_template('saved.png'):
self.errors.append("New picture was not saved!")
self.errors.verify_no_errors()
@marks.testrail_id(5387)
# think about priority of this
def test_1_1_chat_delete_via_delete_button_relogin(self):
@@ -940,6 +865,7 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
message_from_sender = "Message sender"
self.device_1.just_fyi("Sender start 1-1 chat, set emoji and check counter")
self.chat_1.send_message(message_from_sender)
self.chat_1.chat_element_by_text(message_from_sender).wait_for_sent_state(120)
self.chat_1.set_reaction(message_from_sender)
message_sender = self.chat_1.chat_element_by_text(message_from_sender)
@@ -998,8 +924,7 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
if replied_message.replied_message_text != url_message:
self.errors.append("Reply for '%s' not present in message received in public chat" % url_message)
# The "open_in_status" feature is not implemented yet
self.home_2.just_fyi(("Check 'Open in Status' option"))
self.home_2.just_fyi("Check 'Open in Status' option")
url_message = 'http://status.im'
self.chat_1.send_message(url_message)
self.chat_2.element_starts_with_text(url_message, 'button').click_inside_element_by_coordinate(0.2, 0.5)
@@ -1140,6 +1065,7 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
self.errors.verify_no_errors()
@marks.testrail_id(702813)
@marks.xfail(reason="blocked by 15859")
def test_1_1_chat_push_emoji(self):
message_no_pn, message = 'No PN', 'Text push notification'
@@ -203,6 +203,7 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
self.chats[0].send_message(self.message_before_adding)
@marks.testrail_id(702807)
@marks.xfail(reason="blocked by 15859")
def test_group_chat_join_send_text_messages_push(self):
message_to_admin = self.message_to_admin
[self.homes[i].click_system_back_button_until_element_is_shown() for i in range(3)]
@@ -331,24 +331,18 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
self.drivers[0].fail("Not navigated to channel view after reopening app")
@marks.testrail_id(702742)
@marks.xfail(reason='flaky test; sometimes can not be copied through appium')
def test_community_copy_and_paste_message_in_chat_input(self):
message_texts = ['mmmeowesage_text', 'https://status.im']
message_input = self.channel.chat_message_input
if not message_input.is_element_displayed():
if not self.channel.chat_message_input.is_element_displayed():
self.home.click_system_back_button_until_element_is_shown()
self.home.get_to_community_channel_from_home(self.community_name)
for message in message_texts:
message_input.send_keys(message)
self.channel.send_message_button.click()
self.channel.send_message(message)
self.channel.copy_message_text(message)
message_input.paste_text_from_clipboard()
if message_input.text != message:
self.errors.append('Message %s text was not copied in community channel' % message)
message_input.clear()
actual_copied_text = self.channel.driver.get_clipboard_text()
if actual_copied_text != message:
self.errors.append('Message %s text was not copied in community channel, text in clipboard %s' % actual_copied_text)
self.errors.verify_no_errors()
@@ -364,9 +358,10 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
for key in ['admin_open', 'member_open', 'admin_closed', 'member_closed']:
if not self.home.element_by_text(waku_user.communities[key]).is_element_displayed(30):
self.errors.append("%s was not restored from waku-backup!!" % key)
self.home.opened_communities_tab.click()
if not self.home.element_by_text(waku_user.communities['member_pending']).is_element_displayed(30):
self.errors.append("Pending community %s was not restored from waku-backup!" % waku_user.communities['member_pending'])
# TODO: there is a bug when pending community sometimes restored as joined; needs investigation
# self.home.opened_communities_tab.click()
# if not self.home.element_by_text(waku_user.communities['member_pending']).is_element_displayed(30):
# self.errors.append("Pending community %s was not restored from waku-backup!" % waku_user.communities['member_pending'])
self.home.just_fyi("Check contacts/blocked users")
self.home.chats_tab.click()
@@ -498,8 +493,60 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase):
self.errors.append("Deleted for me message is deleted all channel members")
self.errors.verify_no_errors()
@marks.testrail_id(702859)
def test_1_1_chat_image_send_reply(self):
self.home_1.just_fyi('Send image in 1-1 chat from Gallery')
image_description = 'description'
self.channel_1.show_images_button.click()
self.channel_1.allow_button.click_if_shown()
self.channel_1.first_image_from_gallery.click()
self.channel_1.images_confirm_selection_button.click()
self.channel_1.chat_message_input.set_value(image_description)
self.channel_1.send_message_button.click()
self.channel_1.chat_message_input.click()
self.channel_1.chat_element_by_text(image_description).image_in_message.click()
self.channel_1.click_system_back_button()
# TODO: options for image are still WIP; add case with edit description of image and after 15901 fix
# self.home_2.just_fyi('check image, description and options for receiver')
# self.channel_2.chat_element_by_text(image_description).image_in_message.click()
# self.home_1.just_fyi('save image')
# self.chat_1.save_image_button.click_until_presence_of_element(self.chat_1.show_images_button)
# self.chat_1.show_images_button.click_until_presence_of_element(self.chat_1.image_from_gallery_button)
# self.chat_1.image_from_gallery_button.click_until_presence_of_element(self.chat_1.recent_image_in_gallery)
# if not self.chat_1.recent_image_in_gallery.is_element_displayed():
# self.errors.append('Saved image is not shown in Recent')
# self.home_1.click_system_back_button(2)
# self.home_2.just_fyi('check share and save options on opened image')
# self.chat_2.image_message_in_chat.scroll_to_element(direction='up')
# self.chat_2.image_message_in_chat.click_until_presence_of_element(self.chat_2.share_image_icon_button)
# self.chat_2.share_image_icon_button.click()
# self.chat_2.share_via_messenger()
# if not self.chat_2.image_in_android_messenger.is_element_displayed():
# self.errors.append("Can't share image")
# self.chat_2.click_system_back_button_until_element_is_shown(element=self.chat_2.save_image_icon_button)
# self.chat_2.save_image_icon_button.click()
# self.chat_2.show_images_button.click()
# self.chat_2.allow_button.wait_and_click()
#
# if not self.chat_2.first_image_from_gallery.is_element_image_similar_to_template('saved.png'):
# self.errors.append("New picture was not saved!")
#
# self.channel_2.chat_element_by_text(image_description).image_in_message.save_new_screenshot_of_element('images_test.png')
self.channel_2.just_fyi("Can reply to images")
self.channel_2.quote_message(image_description)
message_text = 'reply to image'
self.channel_2.chat_message_input.send_keys(message_text)
self.channel_2.send_message_button.click()
chat_element_1 = self.channel_1.chat_element_by_text(message_text)
if not chat_element_1.is_element_displayed(sec=60) or chat_element_1.replied_message_text != 'Image':
self.errors.append('Reply message was not received by the sender')
self.errors.verify_no_errors()
@marks.testrail_id(702840)
@marks.xfail(reason='flaky test; sometimes can not be copied through appium')
def test_community_emoji_send_copy_paste_reply(self):
emoji_name = random.choice(list(emoji.EMOJI_UNICODE))
emoji_unicode = emoji.EMOJI_UNICODE[emoji_name]
@@ -511,9 +558,9 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase):
self.channel_1.just_fyi("Can copy and paste emojis")
self.channel_1.copy_message_text(emoji_unicode)
self.channel_1.chat_message_input.paste_text_from_clipboard()
if self.channel_1.chat_message_input.text != emoji_unicode:
self.errors.append('Emoji message was not copied')
actual_copied_text = self.channel_1.driver.get_clipboard_text()
if actual_copied_text != emoji_unicode:
self.errors.append('Emoji message was not copied, text in clipboard is %s' % actual_copied_text)
self.channel_1.just_fyi("Can reply to emojis")
self.channel_2.quote_message(emoji_unicode)
@@ -696,6 +743,7 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase):
self.errors.verify_no_errors()
@marks.testrail_id(702786)
@marks.xfail(reason="blocked by 15859")
def test_community_mentions_push_notification(self):
self.home_1.click_system_back_button_until_element_is_shown()
if not self.channel_2.chat_message_input.is_element_displayed():
@@ -23,7 +23,6 @@ class TestActivityCenterContactRequestMultipleDevicePR(MultipleSharedDeviceTestC
[home.chats_tab.click() for home in self.homes]
@marks.testrail_id(702850)
@marks.xfail(reason="Blocked by 15500")
def test_activity_center_contact_request_decline(self):
self.device_1.put_app_to_background()
self.device_2.just_fyi('Device2 sends a contact request to Device1 via Paste button and check user details')
@@ -32,8 +31,9 @@ class TestActivityCenterContactRequestMultipleDevicePR(MultipleSharedDeviceTestC
self.home_2.add_a_contact_chat_bottom_sheet_button.click()
self.home_2.element_by_translation_id("paste").click()
self.home_2.element_by_translation_id("user-found").wait_for_visibility_of_element(10)
if not self.home_2.element_by_text(self.username_1).is_element_displayed():
self.errors.append("Username is not shown on 'Add contact' page after entering valid public key")
# TODO: Blocked by 15500
# if not self.home_2.element_by_text(self.username_1).is_element_displayed():
# self.errors.append("Username is not shown on 'Add contact' page after entering valid public key")
chat = self.home_2.get_chat_view()
chat.view_profile_new_contact_button.click_until_presence_of_element(chat.profile_block_contact_button)
+12 -4
View File
@@ -186,7 +186,6 @@ class ChatElementByText(Text):
return BaseElement(self.driver, prefix=self.locator,
xpath="//*[@content-desc='message-sent']").is_element_displayed(wait_time)
@property
def uncollapse(self) -> bool:
class Collapse(Button):
@@ -274,6 +273,15 @@ class ChatElementByText(Text):
return EmojisNumber(self.driver, self.locator)
@property
def image_in_message(self):
try:
self.driver.info("Trying to access image inside message with text '%s'" % self.message_text)
ChatElementByText(self.driver, self.message_text).wait_for_sent_state(60)
return Button(self.driver, xpath='%s//android.view.ViewGroup/android.widget.ImageView' % self.locator)
except NoSuchElementException:
self.driver.fail("No image is found in message!")
@property
def pinned_by_label(self):
class PinnedByLabelText(Text):
@@ -728,11 +736,11 @@ class ChatView(BaseView):
self.sticker_message = Button(self.driver, accessibility_id="sticker-message")
# Images
self.show_images_button = Button(self.driver, accessibility_id="show-photo-icon")
self.show_images_button = Button(self.driver, accessibility_id="open-images-button")
self.take_photo_button = Button(self.driver, accessibility_id="take-picture")
self.image_from_gallery_button = Button(self.driver, accessibility_id="open-gallery")
self.first_image_from_gallery = Button(self.driver,
xpath="//*[@content-desc='open-gallery']/following-sibling::android.view.ViewGroup[1]")
self.first_image_from_gallery = Button(self.driver, accessibility_id="image-0")
self.images_confirm_selection_button = Button(self.driver, accessibility_id="confirm-selection")
self.images_area_in_gallery = Button(self.driver,
xpath="//*[@content-desc='open-gallery']/following-sibling::android.view.ViewGroup[1]")
self.image_message_in_chat = Button(self.driver, accessibility_id="image-message")
+16 -1
View File
@@ -1219,7 +1219,7 @@
"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-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",
@@ -2108,6 +2108,21 @@
"community-request-pending-body-text": "You requested to join",
"community-kicked-heading": "Kicked from community",
"community-kicked-body": "You were kicked from",
"signing-in-from-another-device": "Signing in from the other device",
"open-status-on-your-other-device": "Open Status on your other device",
"im-new": "I'm new",
"scan-or-enter-sync-code-seen-on-this-device": "Scan or enter sync code seen on this device",
"press": "Press",
"tap": "Tap",
"or-tap": "or tap",
"tap-on": "Tap on",
"and-go-to": "and go to",
"scan-or-enter-a-sync-code": "Scan or enter a sync code",
"scan-the-qr-code-or-copy-the-sync-code": "Scan the QR code or copy the sync code",
"already-logged-in-on-the-other-device": "Already logged in on the other device",
"how-to-pair": "How to pair",
"mobile": "Mobile",
"desktop": "Desktop",
"error-loading-audio": "Error while loading audio",
"you-colon": "You:",
"you-replied": "You replied",