Compare commits

..
Author SHA1 Message Date
Omar Basem ace10fb517 modal comp 2023-05-18 10:26:48 +04:00
Omar Basem 873ad986be android blur view 2023-05-18 10:25:45 +04:00
Siddarth KumarandChurikova Tetiana eea55b22d7 Dont check app-state for Android when evaluating universal links routing (#15896)
* Dont check app-state for android before routing PN

On Android devices, right after account creation and before a log out, the :app-state value in reframe db is set to background. The value becomes active after a log out and then log in which causes tapping on push notifications to do nothing.

This behaviour breaks E2E and hence in this PR, I remove the checking of this case only for Android platforms.
More work should be done to figure out why the app state event handler is behaving this way on Android but for now I would like to unblock E2E.

* e2e: remove xfailed tests

---------

Co-authored-by: Churikova Tetiana <tatiana@status.im>
2023-05-18 00:20:38 +05:30
Andrea Maria Piana 7efd1e5d6b Bump status-go version to 0.151.8 2023-05-17 18:24:42 +01:00
Omar Basem b264d25b69 fix: composer opacity (#15929) 2023-05-17 19:43:42 +04:00
Omar Basem 03f501ca7b fix: image flicker (#15932) 2023-05-17 15:52:23 +04:00
Ulises Manuel Cárdenas 6598873343 [#15779] Disable input and buttons while loggin in 2023-05-17 00:13:10 -06:00
Siddarth Kumar a60235abf3 (feat) : add share capabilities in shell (#15782)
fixes: #13439

Summary
- Update `quo2/qr-code` component to use `fast-image` instead of `rn/image`
- Adds profile tab in share section, wallet tab is marked as WIP
- Shows profile QR code along with link to user's profile and the user's emoji hash.
- profile link is shareable and copyable on tap.
- emoji hash is shareable and copyable on tap.
- fixed weird android issues
2023-05-16 21:57:34 +05:30
flexsurfer b5a8f0a127 [15611] Lock orientation (#15915) 2023-05-16 16:54:21 +02:00
Mohamed Javidandpavloburykh 7d0dfec22c Design Feedbacks on "Create profile password" screen (#15863)
Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
Co-authored-by: pavloburykh <pavlo@status.im>
2023-05-16 18:03:45 +05:30
Mohamed Javid 51fa99affa Add Accessiblity for Image Container in Chat (#15913)
Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
2023-05-16 17:27:02 +05:30
codemaster 369aed389c feat: new keycard component (#15892) 2023-05-16 13:37:14 +02:00
jakub 10d9c34559 ci: fix Alchemy and Infura env vars for iOS
Signed-off-by: Jakub Sokołowski <jakub@status.im>
2023-05-16 13:06:37 +02:00
jakub 8ccf2d1596 ci: fix missing Alchemy and Infura env vars
Depends on:
https://github.com/status-im/status-jenkins-lib/pull/65

Signed-off-by: Jakub Sokołowski <jakub@status.im>
2023-05-16 11:20:29 +02: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
88 changed files with 1548 additions and 663 deletions
+1 -1
View File
@@ -41,7 +41,7 @@ KEYSTORE_KEY_PASSWORD=password
# By default we build a mostly universal APK
ANDROID_ABI_SPLIT=false
# Some platforms are excluded though
ANDROID_ABI_INCLUDE=armeabi-v7a;arm64-v8a;x86_64
ANDROID_ABI_INCLUDE=armeabi-v7a;arm64-v8a;x86
org.gradle.jvmargs=-Xmx8704M
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.5'
/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.5'
pipeline {
agent { label 'linux' }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.5'
/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.5'
pipeline {
agent { label params.AGENT_LABEL }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.5'
/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.5'
pipeline {
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.5'
pipeline {
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.5'
pipeline {
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.5'
pipeline {
agent { label 'macos' }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.5'
pipeline {
agent { label 'linux' }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.3'
library 'status-jenkins-lib@v1.7.5'
pipeline {
agent {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 944 B

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

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: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 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

+1 -1
View File
@@ -8,7 +8,7 @@
{:label (colors/theme-colors colors/neutral-80-opa-40 colors/white-opa-40 override-theme)
:icon (colors/theme-colors colors/neutral-80-opa-70 colors/white-opa-70 override-theme)
:button-border (colors/theme-colors colors/neutral-80-opa-30 colors/white-opa-10 override-theme)
:password-icon (colors/theme-colors colors/neutral-100 colors/white override-theme)
:password-icon (colors/theme-colors colors/neutral-100 colors/white-opa-70 override-theme)
:clear-icon (colors/theme-colors colors/neutral-80-opa-30 colors/white-opa-10 override-theme)
:cursor (colors/theme-colors (colors/custom-color :blue 50)
colors/white
@@ -0,0 +1,20 @@
(ns quo2.components.keycard.component-spec
(:require [quo2.components.keycard.view :as keycard]
[test-helpers.component :as h]
[utils.i18n :as i18n]))
(h/describe "keycard component"
(h/test "Render of keycard component when: Status = Empty, Locked = False"
(h/render [keycard/keycard])
(-> (h/expect (h/query-by-label-text :holder-name))
(h/is-equal (i18n/label :t/empty-keycard))))
(h/test "Render of keycard component when: Status = Filled, Locked = False"
(h/render [keycard/keycard {:holder-name? "Alisha"}])
(-> (h/expect (h/query-by-label-text :holder-name))
(h/is-equal (i18n/label :t/user-keycard {:name :holder-name}))))
(h/test "Render of keycard component when: Status = Filled, Locked = True"
(h/render [keycard/keycard {:holder-name? "Alisha"}])
(-> (h/expect (h/query-by-label-text :holder-name))
(h/is-equal (i18n/label :t/user-keycard {:name :holder-name})))))
+42
View File
@@ -0,0 +1,42 @@
(ns quo2.components.keycard.style
(:require [quo2.foundations.colors :as colors]))
(def keycard-height 210)
(def keycard-chip-height 28)
(defn card-container
[locked?]
{:overflow :hidden
:height keycard-height
:align-items :flex-start
:justify-content :space-between
:border-width 1
:border-color (if locked?
colors/white-opa-5
(colors/theme-colors colors/neutral-20 colors/neutral-80))
:border-style :solid
:border-radius 16
:padding 16
:background-color (if locked?
(colors/theme-colors colors/danger colors/danger-opa-40)
(colors/theme-colors colors/neutral-5 colors/neutral-90))})
(defn keycard-logo
[locked?]
{:tint-color (if locked? colors/white (colors/theme-colors colors/neutral-100 colors/white))})
(defn keycard-watermark
[locked?]
{:position :absolute
:tint-color (if locked? colors/white-opa-5 (colors/theme-colors colors/neutral-100 colors/white))
:align-self :center
:height 280.48
:transform [{:rotate "-30deg"} {:translateY -30}]
:opacity (when-not locked? 0.02)
:z-index 1})
(def keycard-chip
{:height keycard-chip-height
:position :absolute
:right 16
:top (/ (- keycard-height 28) 2)})
+38
View File
@@ -0,0 +1,38 @@
(ns quo2.components.keycard.view
(:require [react-native.core :as rn]
[quo2.components.keycard.style :as style]
[quo2.components.tags.tag :as tag]
[quo2.foundations.colors :as colors]
[quo2.foundations.resources :as resources]
[utils.i18n :as i18n]))
(defn keycard
"This component based on the following properties:
- :holder-name - Can be owner's name. Default is Empty
- :locked? - Boolean to specify whether the keycard is locked or not
"
[{:keys [holder-name locked?]}]
(let [label (if (boolean holder-name)
(i18n/label :t/user-keycard {:name holder-name})
(i18n/label :t/empty-keycard))]
[rn/view {:style (style/card-container locked?)}
[rn/image
{:source (resources/get-image :keycard-logo)
:style (style/keycard-logo locked?)}]
[rn/image
{:source (resources/get-image
(if (or locked? (colors/dark?)) :keycard-chip-dark :keycard-chip-light))
:style style/keycard-chip}]
[rn/image
{:source (resources/get-image :keycard-watermark)
:style (style/keycard-watermark locked?)}]
[tag/tag
{:size 32
:type (when locked? :icon)
:label label
:labelled? true
:blurred? true
:resource (when locked? :i/locked)
:accessibility-label :holder-name
:icon-color colors/white-70-blur
:override-theme (when locked? :dark)}]]))
@@ -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}
+3 -2
View File
@@ -1,11 +1,12 @@
(ns quo2.components.share.qr-code.view
(:require [quo2.components.share.qr-code.style :as style]
[react-native.core :as rn]))
[react-native.core :as rn]
[react-native.fast-image :as fast-image]))
(defn qr-code
[{:keys [source width height]}]
[rn/view
{:style style/container}
[rn/image
[fast-image/fast-image
{:source source
:style (style/image width height)}]])
+2
View File
@@ -38,6 +38,7 @@
quo2.components.inputs.recovery-phrase.view
quo2.components.inputs.search-input.view
quo2.components.inputs.title-input.view
quo2.components.keycard.view
quo2.components.links.url-preview-list.view
quo2.components.links.url-preview.view
quo2.components.links.link-preview.view
@@ -126,6 +127,7 @@
;;;; CARDS
(def small-option-card quo2.components.onboarding.small-option-card.view/small-option-card)
(def keycard quo2.components.keycard.view/keycard)
;;;; COLORS
(def color-picker quo2.components.colors.color-picker.view/view)
+11
View File
@@ -0,0 +1,11 @@
(ns quo2.foundations.resources)
(def ui
{:keycard-logo (js/require "../resources/images/ui2/keycard-logo.png")
:keycard-chip-light (js/require "../resources/images/ui2/keycard-chip-light.png")
:keycard-chip-dark (js/require "../resources/images/ui2/keycard-chip-dark.png")
:keycard-watermark (js/require "../resources/images/ui2/keycard-watermark.png")})
(defn get-image
[k]
(get ui k))
+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))))
@@ -13,7 +13,8 @@
[status-im.wallet.choose-recipient.core :as choose-recipient]
[status-im2.navigation.events :as navigation]
[taoensso.timbre :as log]
[native-module.core :as native-module]))
[native-module.core :as native-module]
[react-native.platform :as platform]))
;; TODO(yenda) investigate why `handle-universal-link` event is
;; dispatched 7 times for the same link
@@ -175,10 +176,15 @@
(rf/defn handle-url
"Store url in the database if the user is not logged in, to be processed
on login, otherwise just handle it"
on login, otherwise just handle it. On Android platform the app-state value
in db is `background` immediately after account creation, hence we avoid the
app-state check for android platforms to fix this e2e blocker :
https://github.com/status-im/status-mobile/issues/15859
"
{:events [:universal-links/handle-url]}
[{:keys [db] :as cofx} url]
(if (and (multiaccounts.model/logged-in? db) (= (:app-state db) "active"))
(if (and (multiaccounts.model/logged-in? db)
(or platform/android? (= (:app-state db) "active")))
(route-url cofx url)
(store-url-for-later cofx url)))
+43 -3
View File
@@ -3,9 +3,13 @@
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[react-native.core :as rn]
[react-native.navigation :as navigation]
[reagent.core :as reagent]
[status-im2.common.home.style :as style]
[status-im2.common.plus-button.view :as plus-button]
[status-im2.constants :as constants]
[status-im2.contexts.activity-center.view :as ac]
[status-im2.contexts.shell.animation :as shell]
[utils.re-frame :as rf]))
(defn title-column
@@ -66,11 +70,43 @@
[quo/text {:accessibility-label :peers-network-type-text} (str "NETWORK TYPE: " network-type)]
[quo/text {:accessibility-label :peers-count-text} (str "PEERS COUNT: " peers-count)]]))
(defn ac-modal
[visible? view-id]
(let [status-bar-style (if (or (colors/dark?)
(not (shell/home-stack-open?)))
:light
:dark)
close-modal (fn []
(reset! visible? false)
(navigation/merge-options (clj->js view-id)
(clj->js {:statusBar {:style status-bar-style}})))]
[rn/modal
{:visible @visible?
:cover-screen true
:transparent true
:status-bar-translucent true
:hardware-accelerated true
:animation-type :slide
:style {:margin 0
:width "100%"}
:on-back-button-press close-modal}
[ac/view close-modal]]))
(defn- right-section
[{:keys [button-type search?]}]
(let [button-common-props (get-button-common-props button-type)
network-type (rf/sub [:network/type])]
network-type (rf/sub [:network/type])
visible? (reagent/atom false)
view-id (rf/sub [:view-id])
open-ac (fn []
;; delaying status-bar style update looks nicer
(js/setTimeout
#(navigation/merge-options (clj->js view-id)
(clj->js {:statusBar {:style :light}}))
20)
(reset! visible? (not @visible?)))]
[rn/view {:style style/right-section}
[ac-modal visible? view-id]
(when (= network-type "cellular")
[quo/button
(merge button-common-props
@@ -95,14 +131,18 @@
(assoc button-common-props :accessibility-label :open-scanner-button)
:i/scan]
[quo/button
(assoc button-common-props :accessibility-label :show-qr-button)
(merge button-common-props
{:accessibility-label :show-qr-button
:on-press #(rf/dispatch [:open-modal :share-shell])})
:i/qr-code]
[rn/view
[unread-indicator]
[quo/button
(merge button-common-props
{:accessibility-label :open-activity-center-button
:on-press #(rf/dispatch [:activity-center/open])})
;:on-press #(rf/dispatch [:activity-center/open])
:on-press open-ac
})
:i/activity-center]]]))
(defn top-nav
+18 -10
View File
@@ -1,16 +1,24 @@
(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")
:keycard-logo (js/require "../resources/images/ui2/keycard-logo.png")
:keycard-chip-light (js/require "../resources/images/ui2/keycard-chip-light.png")
:keycard-chip-dark (js/require "../resources/images/ui2/keycard-chip-dark.png")
:keycard-watermark (js/require "../resources/images/ui2/keycard-watermark.png")})
(def mock-images
{:coinbase (js/require "../resources/images/mock2/coinbase.png")
@@ -140,7 +140,7 @@
(contains? types-with-unread types/system))}]}]))
(defn header
[]
[close]
[rn/view
[rn/view {:style style/header-container}
[quo/button
@@ -149,7 +149,8 @@
:size 32
:accessibility-label :close-activity-center
:override-theme :dark
:on-press #(rf/dispatch [:navigate-back])}
:on-press (fn []
(if close (close) (rf/dispatch [:navigate-back])))}
:i/close]
[quo/button
{:icon true
@@ -213,14 +214,14 @@
nil)]))))
(defn view
[]
[close]
(let [active-swipeable (atom nil)]
(rf/dispatch [:activity-center.notifications/fetch-first-page])
(fn []
(let [notifications (rf/sub [:activity-center/notifications])]
[rn/view {:flex 1 :padding-top (navigation/status-bar-height)}
[blur/view style/blur]
[header]
[header close]
[rn/flat-list
{:data notifications
:render-data active-swipeable
@@ -64,7 +64,7 @@
[:f> f-send-button state animations window-height images? btn-opacity z-index]))
(defn audio-button
[{:keys [record-reset-fn]}
[{:keys [record-reset-fn input-ref]}
{:keys [record-permission? recording? gesture-enabled? focused?]}
{:keys [container-opacity]}]
(let [audio (rf/sub [:chats/sending-audio])]
@@ -86,17 +86,22 @@
(reset! recording? false)
(reset! gesture-enabled? true)
(rf/dispatch [:chat/send-audio file-path duration])
(when-not @focused?
(if-not @focused?
(reanimated/animate container-opacity
constants/empty-opacity))
constants/empty-opacity)
(js/setTimeout #(when @input-ref (.focus ^js @input-ref))
300))
(rf/dispatch [:chat.ui/set-input-audio nil]))
:on-cancel (fn []
(when @recording?
(reset! recording? false)
(reset! gesture-enabled? true)
(when-not @focused?
(if-not @focused?
(reanimated/animate container-opacity
constants/empty-opacity))
constants/empty-opacity)
(js/setTimeout #(when @input-ref
(.focus ^js @input-ref))
300))
(rf/dispatch [:chat.ui/set-input-audio nil])))
:on-check-audio-permissions (fn []
(permissions/permission-granted?
@@ -149,11 +154,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
@@ -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?]}
@@ -159,7 +159,7 @@
(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}
@@ -35,29 +36,30 @@
(defn blur
[{:keys [text-value focused? lock-selection? cursor-position saved-cursor-position gradient-z-index
maximized?]}
maximized? recording?]}
{:keys [height saved-height last-height gradient-opacity container-opacity opacity background-y]}
{:keys [content-height max-height window-height]}
{: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)]
(reset! focused? false)
(rf/dispatch [:chat.ui/set-input-focused false])
(reanimated/set-shared-value last-height reopen-height)
(reanimated/animate height min-height)
(reanimated/set-shared-value saved-height min-height)
(reanimated/animate opacity 0)
(js/setTimeout #(reanimated/set-shared-value background-y (- window-height)) 300)
(when (utils/empty-input? @text-value images reply nil)
(reanimated/animate container-opacity constants/empty-opacity))
(reanimated/animate gradient-opacity 0)
(reset! lock-selection? true)
(reset! saved-cursor-position @cursor-position)
(reset! gradient-z-index (if (= (reanimated/get-shared-value gradient-opacity) 1) -1 0))
(when (not= reopen-height max-height)
(reset! maximized? false)
(rf/dispatch [:chat.ui/set-input-maximized false]))))
(when-not @recording?
(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)]
(reset! focused? false)
(rf/dispatch [:chat.ui/set-input-focused false])
(reanimated/set-shared-value last-height reopen-height)
(reanimated/animate height min-height)
(reanimated/set-shared-value saved-height min-height)
(reanimated/animate opacity 0)
(js/setTimeout #(reanimated/set-shared-value background-y (- window-height)) 300)
(when (utils/empty-input? @text-value images reply nil)
(reanimated/animate container-opacity constants/empty-opacity))
(reanimated/animate gradient-opacity 0)
(reset! lock-selection? true)
(reset! saved-cursor-position @cursor-position)
(reset! gradient-z-index (if (= (reanimated/get-shared-value gradient-opacity) 1) -1 0))
(when (not= reopen-height max-height)
(reset! maximized? false)
(rf/dispatch [:chat.ui/set-input-maximized false])))))
(defn content-size-change
[event
@@ -118,7 +120,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
@@ -34,7 +34,9 @@
[:<>
[rn/view {:style {:margin-bottom 10}} [text/text-content first-image context]]
[rn/view
{:style (style/album-container portrait?)}
{:style (style/album-container portrait?)
:accessible true
:accessibility-label :image-container}
(map-indexed
(fn [index item]
(let [images-size-key (if (< images-count constants/max-album-photos)
@@ -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
@@ -18,18 +18,19 @@
(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)}]]))
[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}])}
[fast-image/fast-image
{:source {:uri (:image content)}
:style (merge dimensions {:border-radius 12})
:native-ID (when (= shared-element-id message-id) :shared-element)}]]]))
@@ -1,19 +1,7 @@
(ns status-im2.contexts.onboarding.create-password.style
(:require [quo2.foundations.colors :as colors]))
(def image-background
{:height "100%"
:width "100%"})
(def overlay
{:position :absolute
:top 0
:bottom 0
:left 0
:right 0
:background-color colors/neutral-80-opa-80-blur})
(def content-style {:flex-grow 1})
(def flex-fill {:flex 1})
(def heading {:margin-bottom 20})
(def heading-subtitle {:color colors/white})
@@ -1,5 +1,6 @@
(ns status-im2.contexts.onboarding.create-password.view
(:require
[oops.core :refer [ocall]]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[react-native.core :as rn]
@@ -66,7 +67,8 @@
:shown true}
:placeholder (i18n/label :t/password-creation-placeholder-1)
:on-change-text on-change-password
:on-focus on-input-focus}]
:on-focus on-input-focus
:auto-focus true}]
[rn/view {:style style/space-between-inputs}]
[password-with-hint
{:hint {:text hint-2-text
@@ -120,7 +122,7 @@
(count)))
(defn password-form
[{:keys [scroll-to-end-fn]}]
[]
(let [password (reagent/atom "")
repeat-password (reagent/atom "")
accepts-disclaimer? (reagent/atom false)
@@ -134,7 +136,7 @@
:as validations} (password-validations @password)
password-strength (calc-password-strength validations)
empty-password? (empty? @password)
same-passwords? (= @password @repeat-password)
same-passwords? (and (not empty-password?) (= @password @repeat-password))
meet-requirements? (and (not empty-password?)
(utils.string/at-least-n-chars? @password 10)
same-passwords?
@@ -147,9 +149,7 @@
:passwords-match? same-passwords?
:empty-password? empty-password?
:show-password-validation? @show-password-validation?
:on-input-focus (fn []
(scroll-to-end-fn)
(reset! focused-input :password))
:on-input-focus #(reset! focused-input :password)
:on-change-password (fn [new-value]
(reset! password new-value)
(when (same-password-length?)
@@ -163,14 +163,14 @@
(reset! show-password-validation? true))}]]
[rn/view {:style style/bottom-part}
[rn/view {:style style/disclaimer-container}
[quo/disclaimer
{:blur? true
:on-change #(swap! accepts-disclaimer? not)
:checked? @accepts-disclaimer?}
(i18n/label :t/password-creation-disclaimer)]]
(when (= @focused-input :password)
(when same-passwords?
[rn/view {:style style/disclaimer-container}
[quo/disclaimer
{:blur? true
:on-change #(swap! accepts-disclaimer? not)
:checked? @accepts-disclaimer?}
(i18n/label :t/password-creation-disclaimer)]])
(when (and (= @focused-input :password) (not same-passwords?))
[help
{:validations validations
:password-strength password-strength}])
@@ -196,25 +196,45 @@
(i18n/label
:t/create-profile-password-info-box-description)]]])
(defn- f-create-password
[]
(let [keyboard-shown? (reagent/atom false)
{:keys [top bottom]} (safe-area/get-insets)]
(fn []
(rn/use-effect
(let [will-show-listener (ocall rn/keyboard
"addListener"
"keyboardWillShow"
#(reset! keyboard-shown? true))
will-hide-listener (ocall rn/keyboard
"addListener"
"keyboardWillHide"
#(reset! keyboard-shown? false))]
(fn []
(fn []
(ocall will-show-listener "remove")
(ocall will-hide-listener "remove"))))
[])
[rn/touchable-without-feedback
{:on-press rn/dismiss-keyboard!
:accessible false}
[rn/view {:style style/flex-fill}
[rn/keyboard-avoiding-view {:style style/flex-fill}
[navigation-bar/navigation-bar
{:top top
:right-section-buttons [{:type :blur-bg
:icon :i/info
:icon-override-theme :dark
:on-press (fn []
(rn/dismiss-keyboard!)
(rf/dispatch [:show-bottom-sheet
{:content create-password-doc
:shell? true}]))}]}]
[password-form]
[rn/view {:style {:height (if-not @keyboard-shown? bottom 0)}}]]]])))
(defn create-password
[]
(let [scroll-view-ref (atom nil)
scroll-to-end-fn #(js/setTimeout ^js/Function (.-scrollToEnd @scroll-view-ref) 250)]
(fn []
(let [{:keys [top]} (safe-area/get-insets)]
[:<>
[background/view true]
[rn/scroll-view
{:ref #(reset! scroll-view-ref %)
:style style/overlay
:content-container-style style/content-style}
[navigation-bar/navigation-bar
{:top top
:right-section-buttons [{:type :blur-bg
:icon :i/info
:icon-override-theme :dark
:on-press #(rf/dispatch
[:show-bottom-sheet
{:content create-password-doc
:shell? true}])}]}]
[password-form {:scroll-to-end-fn scroll-to-end-fn}]]]))))
[:<>
[background/view true]
[:f> f-create-password]])
+11 -14
View File
@@ -1,17 +1,16 @@
(ns status-im2.contexts.onboarding.events
(:require
[utils.re-frame :as rf]
[taoensso.timbre :as log]
[clojure.string :as string]
[native-module.core :as native-module]
[re-frame.core :as re-frame]
[status-im.ethereum.core :as ethereum]
[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]))
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.security.core :as security]))
(re-frame/reg-fx
:multiaccount/create-account-and-login
@@ -140,12 +139,10 @@
{:title (i18n/label :t/multiaccount-exists-title)
:content (i18n/label :t/multiaccount-exists-content)
:confirm-button-text (i18n/label :t/unlock)
:on-accept #(do
(re-frame/dispatch [:pop-to-root :profiles])
;; FIXME(rasom): obviously not cool
(reset! profiles.view/show-profiles? false)
(re-frame/dispatch
[:multiaccounts.login.ui/multiaccount-selected key-uid]))
:on-accept (fn []
(re-frame/dispatch [:pop-to-root :profiles])
(re-frame/dispatch
[:multiaccounts.login.ui/multiaccount-selected key-uid]))
:on-cancel #(re-frame/dispatch [:pop-to-root :multiaccounts])}}
{:db (assoc-in db [:onboarding-2/profile :seed-phrase] seed-phrase)
:dispatch [:navigate-to :create-profile]}))
@@ -12,8 +12,6 @@
[utils.security.core :as security]
[utils.transforms :as types]))
(def show-profiles? (reagent/atom false))
(defn login-multiaccount
[]
(rf/dispatch [:multiaccounts.login.ui/password-input-submitted]))
@@ -43,14 +41,14 @@
:context context
:button-text (i18n/label :t/remove)
:close-button-text (i18n/label :t/cancel)
:on-press #(do
(rf/dispatch [:hide-bottom-sheet])
(native-module/delete-multiaccount
key-uid
(fn [result]
(let [{:keys [error]} (types/json->clj result)]
(rf/dispatch [:onboarding-2/on-delete-profile-success key-uid])
(log/info "profile deleted: error" error)))))}])
:on-press (fn []
(rf/dispatch [:hide-bottom-sheet])
(native-module/delete-multiaccount
key-uid
(fn [result]
(let [{:keys [error]} (types/json->clj result)]
(rf/dispatch [:onboarding-2/on-delete-profile-success key-uid])
(log/info "profile deleted: error" error)))))}])
(defn show-confirmation
[key-uid context]
@@ -72,7 +70,9 @@
{:content (fn [] [profile-options key-uid context])}]))
(defn profile-card
[{:keys [name key-uid customization-color keycard-pairing last-index] :as multiaccount} index]
[{:keys [name key-uid customization-color keycard-pairing last-index set-hide-profiles]
:as multiaccount}
index]
(let [last-item? (= last-index index)
profile-picture (:uri (first (:images multiaccount)))]
[quo/profile-card
@@ -89,15 +89,18 @@
{:name name
:color customization-color
:profile-picture profile-picture})
:on-card-press #(do
(rf/dispatch
[:multiaccounts.login.ui/multiaccount-selected key-uid])
(when-not keycard-pairing (reset! show-profiles? false)))}]))
:on-card-press (fn []
(rf/dispatch
[:multiaccounts.login.ui/multiaccount-selected key-uid])
(when-not keycard-pairing (set-hide-profiles)))}]))
(defn profiles-section
[]
[{:keys [set-hide-profiles]}]
(let [multiaccounts (vals (rf/sub [:multiaccounts/multiaccounts]))
multiaccounts (map #(assoc % :last-index (- (count multiaccounts) 1)) multiaccounts)]
profiles-data (map #(assoc %
:last-index (- (count multiaccounts) 1)
:set-hide-profiles set-hide-profiles)
multiaccounts)]
[rn/view
{:style style/profiles-container}
[rn/view
@@ -116,7 +119,7 @@
:override-theme :dark}
:main-icons/add]]
[rn/flat-list
{:data (sort-by :timestamp > multiaccounts)
{:data (sort-by :timestamp > profiles-data)
:key-fn :key-uid
:content-container-style {:padding-bottom 20}
:render-fn profile-card}]]))
@@ -169,8 +172,8 @@
(i18n/label :t/forgot-your-password-info-create-new-password-description)]]]]])
(defn login-section
[]
(let [{:keys [name customization-color error processing]
[{:keys [set-show-profiles]}]
(let [{:keys [name customization-color error processing password]
:as multiaccount} (rf/sub [:multiaccounts/login])
sign-in-enabled? (rf/sub [:sign-in-enabled?])
profile-picture (:uri (first (:images multiaccount)))]
@@ -180,7 +183,8 @@
{:size 32
:type :blur-bg
:icon true
:on-press #(reset! show-profiles? true)
:on-press set-show-profiles
:disabled processing
:override-theme :dark
:width 32
:accessibility-label :show-profiles
@@ -197,18 +201,18 @@
[quo/input
{:type :password
:blur? true
:override-theme :dark
:disabled? processing
:placeholder (i18n/label :t/type-your-password)
:auto-focus true
:error? (when (not-empty error) error)
:error? (seq error)
:label (i18n/label :t/profile-password)
:secure-text-entry true
:on-change-text (fn [password]
(rf/dispatch [:set-in [:multiaccounts/login :password]
(security/mask-data password)])
(rf/dispatch [:set-in [:multiaccounts/login :error] ""]))
:default-value (security/safe-unmask-data password)
:on-submit-editing (when sign-in-enabled? login-multiaccount)}]
(when (not-empty error)
(when (seq error)
[quo/info-message
{:type :error
:size :default
@@ -219,6 +223,7 @@
{:size 40
:type :ghost
:before :i/info
:disabled processing
:accessibility-label :forget-password-button
:override-theme :dark
:style style/forget-password-button
@@ -239,10 +244,12 @@
(defn views
[]
(reset! show-profiles? false)
(fn []
[:<>
[background/view true]
(if @show-profiles?
[profiles-section]
[login-section])]))
(let [show-profiles? (reagent/atom false)
set-show-profiles #(reset! show-profiles? true)
set-hide-profiles #(reset! show-profiles? false)]
(fn []
[:<>
[background/view true]
(if @show-profiles?
[profiles-section {:set-hide-profiles set-hide-profiles}]
[login-section {:set-show-profiles set-show-profiles}])])))
@@ -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]}])
@@ -0,0 +1,37 @@
(ns status-im2.contexts.quo-preview.keycard.keycard
(:require [status-im2.contexts.quo-preview.preview :as preview]
[react-native.core :as rn]
[quo2.foundations.colors :as colors]
[reagent.core :as reagent]
[quo2.core :as quo]))
(def descriptor
[{:label "Holder"
:key :holder-name
:type :text}
{:label "Locked?"
:key :locked?
:type :boolean}])
(defn cool-preview
[]
(let [state (reagent/atom {:holder-name nil
:locked? true})]
(fn
[]
[rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!}
[rn/view [preview/customizer state descriptor]
[quo/keycard
{:holder-name? (:holder-name @state)
:locked? (:locked? @state)}]]])))
(defn preview-keycard
[]
[rn/view
{:style {:background-color (colors/theme-colors colors/white colors/neutral-95)
:flex 1}}
[rn/flat-list
{:style {:flex 1}
:keyboard-should-persist-taps :always
:header [cool-preview]
:key-fn str}]])
@@ -90,7 +90,8 @@
[status-im2.contexts.quo-preview.wallet.lowest-price :as lowest-price]
[status-im2.contexts.quo-preview.wallet.network-amount :as network-amount]
[status-im2.contexts.quo-preview.wallet.network-breakdown :as network-breakdown]
[status-im2.contexts.quo-preview.wallet.token-overview :as token-overview]))
[status-im2.contexts.quo-preview.wallet.token-overview :as token-overview]
[status-im2.contexts.quo-preview.keycard.keycard :as keycard]))
(def screens-categories
{:foundations [{:name :shadows
@@ -344,7 +345,10 @@
:component network-breakdown/preview-network-breakdown}
{:name :network-amount
:options {:topBar {:visible true}}
:component network-amount/preview}]})
:component network-amount/preview}]
:keycard [{:name :keycard-component
:options {:topBar {:visible true}}
:component keycard/preview-keycard}]})
(def screens (flatten (map val screens-categories)))
+15
View File
@@ -0,0 +1,15 @@
(ns status-im2.contexts.share.events
(:require [utils.re-frame :as rf]
[status-im2.common.toasts.events :as toasts]
[quo2.foundations.colors :as colors]))
(rf/defn copy-text-and-show-toast
{:events [:share/copy-text-and-show-toast]}
[cofx text-to-copy post-copy-message]
(rf/merge cofx
{:copy-to-clipboard text-to-copy}
(toasts/upsert
{:icon :correct
:icon-color colors/success-50
:override-theme :dark
:text post-copy-message})))
+102
View File
@@ -0,0 +1,102 @@
(ns status-im2.contexts.share.style
(:require [quo2.foundations.colors :as colors]
[react-native.platform :as platform]))
(def screen-padding 20)
(def blur
{:style {:position :absolute
:top 0
:left 0
:right 0
:bottom 0}
:overlay-color colors/neutral-80-opa-80
:blur-amount 20})
(def header-button
{:margin-bottom 12
:margin-left screen-padding})
(def header-heading
{:padding-horizontal screen-padding
:padding-vertical 12
:color colors/white})
(def qr-code-container
{:padding 12
:border-radius 16
:margin-top 12
:margin-bottom 4
:margin-horizontal screen-padding
:background-color colors/white-opa-5
:flex-direction :column
:justify-content :center})
(def emoji-hash-container
{:border-radius 16
:margin-top 12
:margin-horizontal screen-padding
:background-color colors/white-opa-5
:flex-direction :row
:justify-content :flex-start
:align-items :flex-start})
(def profile-address-column
{:flex-direction :column})
(def emoji-address-column
{:flex-direction :column})
(def profile-address-label
{:color colors/white-opa-40
:padding-top 10})
(def profile-address-content
{:color colors/white
:align-self :flex-start
:padding-top 2})
(def profile-address-container
{:flex-direction :row
:justify-content :flex-start
:margin-top 6
:margin-horizontal 4})
(def emoji-address-container
{:flex-direction :row
:justify-content :flex-start
:margin-top 6
:margin-horizontal 4})
(def emoji-hash-label
{:color colors/white-opa-40
:margin-top 8
:padding-bottom (if platform/ios? 2 0)
:padding-left 12})
(def share-button-container
{:position :absolute
:right 0
:top 16})
(def emoji-share-button-container
{:position :absolute
:right 4
:top 16})
(def emoji-hash-content
{:color colors/white
:align-self :flex-start
:padding-top 4
:padding-bottom 12
:padding-left 12
:font-size 14})
(def tabs-container
{:padding-horizontal screen-padding
:margin-vertical 8})
(def wip-style
{:color colors/white
:text-align :center})
+162
View File
@@ -0,0 +1,162 @@
(ns status-im2.contexts.share.view
(:require [utils.i18n :as i18n]
[quo2.core :as quo]
[react-native.core :as rn]
[status-im2.contexts.share.style :as style]
[utils.re-frame :as rf]
[reagent.core :as reagent]
[quo2.foundations.colors :as colors]
[clojure.string :as string]
[react-native.blur :as blur]
[status-im.ui.components.list-selection :as list-selection]
[utils.image-server :as image-server]
[react-native.navigation :as navigation]))
(defn header
[]
[:<>
[quo/button
{:icon true
:type :blur-bg
:size 32
:accessibility-label :close-shell-share-tab
:override-theme :dark
:style style/header-button
:on-press #(rf/dispatch [:navigate-back])}
:i/close]
[quo/text
{:size :heading-1
:weight :semi-bold
:style style/header-heading}
(i18n/label :t/share)]])
(defn abbreviated-url
"The goal here is to generate a string that begins with
join.status.im/u/ joined with the 1st 5 characters
of the compressed public key followed by an ellipsis followed by
the last 12 characters of the compressed public key"
[base-url public-pk]
(let [first-part-of-public-pk (subs public-pk 0 5)
ellipsis "..."
public-pk-size (count public-pk)
last-part-of-public-pk (subs public-pk (- public-pk-size 12) (- public-pk-size 1))
abbreviated-url (str base-url first-part-of-public-pk ellipsis last-part-of-public-pk)]
abbreviated-url))
(defn profile-tab
[window-width]
(let [multiaccount (rf/sub [:multiaccount])
emoji-hash (string/join (get multiaccount :emoji-hash))
qr-size (int (- window-width 64))
public-pk (get multiaccount :compressed-key)
abbreviated-url (abbreviated-url image-server/status-profile-base-url-without-https public-pk)
profile-url (str image-server/status-profile-base-url public-pk)
port (rf/sub [:mediaserver/port])
key-uid (get multiaccount :key-uid)
source-uri (image-server/get-account-qr-image-uri {:key-uid key-uid
:public-key public-pk
:port port
:qr-size qr-size})]
[:<>
[rn/view {:style style/qr-code-container}
[quo/qr-code
{:source {:uri source-uri}
:width qr-size
:height qr-size}]
[rn/view {:style style/profile-address-container}
[rn/view {:style style/profile-address-column}
[quo/text
{:size :paragraph-2
:weight :medium
:style style/profile-address-label}
(i18n/label :t/link-to-profile)]
[rn/touchable-highlight
{:active-opacity 1
:underlay-color colors/neutral-80-opa-1-blur
:background-color :transparent
:on-press #(rf/dispatch [:share/copy-text-and-show-toast profile-url
(i18n/label :t/link-to-profile-copied)])
:on-long-press #(rf/dispatch [:share/copy-text-and-show-toast profile-url
(i18n/label :t/link-to-profile-copied)])}
[quo/text
{:style style/profile-address-content
:size :paragraph-1
:weight :medium
:ellipsize-mode :middle
:number-of-lines 1}
abbreviated-url]]]
[rn/view {:style style/share-button-container}
[quo/button
{:icon true
:type :blur-bg
:size 32
:accessibility-label :link-to-profile
:override-theme :dark
:on-press #(list-selection/open-share {:message profile-url})}
:i/share]]]]
[rn/view {:style style/emoji-hash-container}
[rn/view {:style style/emoji-address-container}
[rn/view {:style style/emoji-address-column}
[quo/text
{:size :paragraph-2
:weight :medium
:style style/emoji-hash-label}
(i18n/label :t/emoji-hash)]
[rn/touchable-highlight
{:active-opacity 1
:underlay-color colors/neutral-80-opa-1-blur
:background-color :transparent
:on-press #(rf/dispatch [:share/copy-text-and-show-toast emoji-hash
(i18n/label :t/emoji-hash-copied)])
:on-long-press #(rf/dispatch [:share/copy-text-and-show-toast emoji-hash
(i18n/label :t/emoji-hash-copied)])}
[rn/text {:style style/emoji-hash-content} emoji-hash]]]]
[rn/view {:style style/emoji-share-button-container}
[quo/button
{:icon true
:type :blur-bg
:size 32
:accessibility-label :link-to-profile
:override-theme :dark
:style {:margin-right 12}
:on-press #(rf/dispatch [:share/copy-text-and-show-toast emoji-hash
(i18n/label :t/emoji-hash-copied)])
:on-long-press #(rf/dispatch [:share/copy-text-and-show-toast emoji-hash
(i18n/label :t/emoji-hash-copied)])}
:i/copy]]]]))
(defn wallet-tab
[]
[rn/text {:style style/wip-style} "not implemented"])
(defn tab-content
[window-width]
(let [selected-tab (reagent/atom :profile)]
(fn []
[:<>
[header]
[rn/view {:style style/tabs-container}
[quo/segmented-control
{:size 28
:blur? true
:override-theme :dark
:on-change #(reset! selected-tab %)
:default-active :profile
:data [{:id :profile
:label (i18n/label :t/profile)}
{:id :wallet
:label (i18n/label :t/wallet)}]}]]
(if (= @selected-tab :profile)
[profile-tab window-width]
[wallet-tab])])))
(defn view
[]
(let [window-width (rf/sub [:dimensions/window-width])]
(fn []
[rn/view
{:flex 1
:padding-top (navigation/status-bar-height)}
[blur/view style/blur]
[tab-content window-width]])))
@@ -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)))
+1
View File
@@ -14,6 +14,7 @@
[status-im2.db :as db]
[utils.re-frame :as rf]
[utils.datetime :as datetime]
status-im2.contexts.share.events
status-im2.contexts.syncing.events
status-im2.contexts.chat.events))
+4 -6
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
@@ -89,7 +87,6 @@
{:component {:id comp
:name comp
:options (merge (options/statusbar-and-navbar)
{:layout {:orientation :portrait}}
options
(if (:topBar options)
(options/merge-top-bar (options/topbar-options) options)
@@ -169,7 +166,7 @@
:id comp
:options (merge (options/statusbar)
{:layout {:componentBackgroundColor :transparent
:orientation :portrait}
:orientation ["portrait"]}
:overlay {:interceptTouchOutside true}}
opts)}})))
@@ -180,7 +177,8 @@
(fn []
(show-overlay "toasts"
{:overlay {:interceptTouchOutside false}
:layout {:componentBackgroundColor :transparent}})))
:layout {:componentBackgroundColor :transparent
:orientation ["portrait"]}})))
(re-frame/reg-fx :hide-toasts (fn [] (dissmiss-overlay "toasts")))
;; bottom sheet
+15 -6
View File
@@ -4,7 +4,7 @@
(defn default-options
[]
{:layout {:orientation :portrait}
{:layout {:orientation ["portrait"]}
:topBar {:visible false}})
;; Note: Currently, the status bar style provided while setting the root has a high preference,
@@ -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)
:orientation :portrait
:backgroundColor (colors/theme-colors colors/white colors/neutral-100)}}))
:layout {:componentBackgroundColor (or background-color
(colors/theme-colors colors/white colors/neutral-100))
:orientation ["portrait"]
: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?]
@@ -67,7 +75,7 @@
(merge
{:modalPresentationStyle :overCurrentContext
:layout {:componentBackgroundColor :transparent
:orientation :portrait
:orientation ["portrait"]
:backgroundColor :transparent}}
(if platform/android?
{:statusBar {:backgroundColor :transparent
@@ -77,6 +85,7 @@
(def sheet-options
{:layout {:componentBackgroundColor :transparent
:orientation ["portrait"]
:backgroundColor :transparent}
:modalPresentationStyle :overCurrentContext})
+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
+34 -9
View File
@@ -24,25 +24,29 @@
[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]
[status-im2.contexts.communities.actions.request-to-join.view :as join-menu]
[status-im2.contexts.syncing.setup-syncing.view :as settings-setup-syncing]))
[status-im2.contexts.syncing.setup-syncing.view :as settings-setup-syncing]
[status-im2.contexts.share.view :as share]))
(defn screens
[]
(concat
(old-screens/screens)
[{:name :intro
:component intro/view}
{:name :activity-center
[{:name :activity-center
:options options/transparent-screen-options
:component activity-center/view}
{:name :share-shell
:options options/transparent-screen-options
:component share/view}
{:name :shell-stack
:component shell/shell-stack}
@@ -74,6 +78,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 +98,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?
+3 -1
View File
@@ -992,7 +992,9 @@
:your-data-belongs-to-you
:your-data-belongs-to-you-description
:your-recovery-phrase
:your-recovery-phrase-description})
:your-recovery-phrase-description
:empty-keycard
:user-keycard})
;; NOTE: the rest checkpoints are based on the previous one, defined
;; like this:
+1
View File
@@ -6,6 +6,7 @@
(def ^:const contact-images-action "/contactImages")
(def ^:const generate-qr-action "/GenerateQRCode")
(def ^:const status-profile-base-url "https://join.status.im/u/")
(def ^:const status-profile-base-url-without-https "join.status.im/u/")
(defn timestamp [] (datetime/timestamp))
+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.8",
"commit-sha1": "c26f45ae3d238bb8ef6e2771eb2a6c2672f5188f",
"src-sha256": "1sygdm09h3rdgb6hyzhlma32kibmp7yissch2w0y65j7bm88m9mq"
}
@@ -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)
@@ -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)
@@ -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")
+1 -1
View File
@@ -207,7 +207,7 @@ class SignInView(BaseView):
self.profile_repeat_password_edit_box.click()
self.profile_repeat_password_edit_box.send_keys(password)
self.checkbox_button.scroll_to_element()
self.checkbox_button.double_click()
self.checkbox_button.click()
self.profile_confirm_password_button.click()
def set_profile(self, username: str, set_image=False):
+23 -2
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",
@@ -1687,6 +1687,8 @@
"database-reset-content": "Chats, contacts and settings have been deleted. You can use your account with your Keycard",
"database-reset-warning": "Database will be reset. Chats, contacts and settings will be deleted",
"empty-keycard-required": "Requires an empty Keycard",
"empty-keycard": "Empty Keycard",
"user-keycard": "{{name}} Card",
"current": "Current",
"choose-storage": "Choose storage",
"choose-new-location-for-keystore": "Choose a new location to save your keystore file",
@@ -2108,6 +2110,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",
@@ -2135,5 +2152,9 @@
"user-shared-a-community": "{{user}} shared a community",
"you-deleted-a-message": "You deleted a message",
"this-message-was-deleted": "This message was deleted",
"user-deleted-a-message": "{{user}} deleted a message"
"user-deleted-a-message": "{{user}} deleted a message",
"link-to-profile": "Link to profile",
"emoji-hash": "Emoji Hash",
"emoji-hash-copied":"Emojihash copied to clipboard",
"link-to-profile-copied":"Link to Profile copied to clipboard"
}